1use itertools::Itertools;
2
3use crate::{
4 builder::TypesBuilder,
5 kind::{TagType, TypeKind, VariantKind},
6 DynTapi,
7};
8
9use super::ts;
10
11pub fn builder() -> TypesBuilder {
12 TypesBuilder {
13 prelude: include_str!("./prelude.js").to_string() + "\n",
14 start_namespace: Box::new(|_, _| "".to_string()),
17 end_namespace: Box::new(|_, _| "".to_string()),
18 decl: Box::new(ty_decl),
19 }
20}
21
22pub fn full_ty_name(ty: DynTapi) -> String {
23 ts::full_ty_name(ty)
24}
25
26pub fn ty_name(ty: DynTapi) -> String {
27 ts::ty_name(ty)
28}
29
30pub fn ty_decl(ty: DynTapi) -> Option<String> {
31 use std::fmt::Write;
32 fn inner(ty: DynTapi) -> Result<Option<String>, std::fmt::Error> {
33 Ok(Some(match ty.kind() {
34 TypeKind::Struct(s) => {
35 if s.attr.transparent {
36 format!(
37 "export type {} = {};",
38 s.attr.name.serialize_name,
39 ty_name(
40 s.fields
41 .iter()
42 .find(|f| !f.attr.skip_serializing)
43 .unwrap()
44 .ty
45 ),
46 )
47 } else {
48 let js_fields = js_fields(false, &s.fields);
49 format!(
50 "/**\n * @typedef {{{{ {js_fields} }}}} {} */",
51 full_ty_name(ty),
52 )
53 }
54 }
55 TypeKind::TupleStruct(s) => {
56 let js_fields = js_tuple(&s.fields.iter().map(|f| f.ty).collect_vec());
57 format!("export type {} = {js_fields};", s.attr.name.serialize_name,)
58 }
59 TypeKind::Enum(e) => {
60 let mut out = String::new();
61
62 let has_data = e
63 .variants
64 .iter()
65 .any(|v| matches!(&v.kind, VariantKind::Tuple(_) | VariantKind::Struct(_)));
66
67 let variants = e.variants.iter().map(|v| match &v.kind {
68 VariantKind::Unit => match &e.attr.tag {
69 TagType::External => format!("{:?}", v.name),
70 TagType::Internal { tag } | TagType::Adjacent { tag, content: _ } => {
71 format!("{{ {tag:?}: {:?} }}", v.name)
72 }
73 TagType::None => todo!("{}:{}", file!(), line!()),
74 },
75 VariantKind::Tuple(fields) => match &e.attr.tag {
76 TagType::External => {
77 format!("{{ {:?}: {} }}", v.name, js_tuple(fields))
78 }
79 TagType::Internal { tag: _ } => {
80 unreachable!("tagged tuples are not allowed by serde")
81 }
82 TagType::Adjacent { tag, content } => {
83 format!(
84 "{{ {tag:?}: {:?}, {content:?}: {} }}",
85 v.name,
86 js_tuple(fields),
87 )
88 }
89 TagType::None => todo!("{}:{}", file!(), line!()),
90 },
91 VariantKind::Struct(fields) => match &e.attr.tag {
92 TagType::External => {
93 let js_fields = js_fields(false, fields);
94 format!("{{ {:?}: {{ {js_fields} }} }}", v.name)
95 }
96 TagType::Internal { tag } => {
97 let js_fields = js_fields(false, fields);
98 format!("{{ {tag:?}: {:?}, {js_fields} }}", v.name)
99 }
100 TagType::Adjacent { tag, content } => {
101 let js_fields = js_fields(false, fields);
102 format!(
103 "{{ {tag:?}: {:?}, {content:?}: {{ {js_fields} }} }}",
104 v.name
105 )
106 }
107 TagType::None => todo!("TagType::None @ {}:{}", file!(), line!()),
108 },
109 });
110
111 write!(
112 out,
113 "/** @typedef {{{}}} {} */",
114 variants.clone().clone().format(" | "),
115 full_ty_name(ty),
116 )?;
117
118 if !has_data {
120 write!(
121 out,
122 "\nexport const {} = /** @type {{{}[]}} */ ([{}]);",
123 heck::AsShoutySnakeCase(&e.attr.name.serialize_name),
124 full_ty_name(ty),
125 variants.format(", "),
126 )?;
127 }
128 out
129 }
130 TypeKind::List(_)
131 | TypeKind::Option(_)
132 | TypeKind::Tuple(_)
133 | TypeKind::Record(_, _)
134 | TypeKind::Any
135 | TypeKind::Builtin(_) => return Ok(None),
136 }))
137 }
138 inner(ty).unwrap()
139}
140
141fn js_tuple(fields: &[DynTapi]) -> String {
142 ts::ts_tuple(fields)
143}
144
145fn js_fields(multi_line: bool, fields: &[crate::kind::Field]) -> impl std::fmt::Display + '_ {
146 ts::ts_fields(multi_line, fields)
147}