1use itertools::Itertools;
2
3use crate::{
4 builder::TypesBuilder,
5 kind::{BuiltinTypeKind, TagType, TypeKind, VariantKind},
6 DynTapi,
7};
8
9pub fn builder() -> TypesBuilder {
10 TypesBuilder {
11 prelude: include_str!("./prelude.ts").to_string() + "\n",
12 start_namespace: Box::new(|_, name| format!("export namespace {} {{", name)),
13 end_namespace: Box::new(|_, _| "}".to_string()),
14 decl: Box::new(ty_decl),
15 }
16}
17
18pub fn full_ty_name(ty: DynTapi) -> String {
19 let mut name = ty_name(ty);
20 for p in ty.path().iter().rev() {
21 name = format!("{}.{}", p, name);
22 }
23 name
24}
25
26pub fn ty_name(ty: DynTapi) -> String {
27 match ty.kind() {
28 TypeKind::Struct(s) => s.attr.name.serialize_name,
29 TypeKind::TupleStruct(s) => s.attr.name.serialize_name,
30 TypeKind::Enum(e) => e.attr.name.serialize_name,
31 TypeKind::List(ty) => format!("{}[]", full_ty_name(ty)),
32 TypeKind::Option(ty) => format!("({} | null)", full_ty_name(ty)),
33 TypeKind::Tuple(fields) => ts_tuple(&fields),
34 TypeKind::Record(k, v) => format!("Record<{}, {}>", full_ty_name(k), full_ty_name(v)),
35 TypeKind::Any => "any".to_string(),
36 TypeKind::Builtin(b) => match b {
37 BuiltinTypeKind::U8
38 | BuiltinTypeKind::U16
39 | BuiltinTypeKind::U32
40 | BuiltinTypeKind::U64
41 | BuiltinTypeKind::U128
42 | BuiltinTypeKind::I8
43 | BuiltinTypeKind::I16
44 | BuiltinTypeKind::I32
45 | BuiltinTypeKind::I64
46 | BuiltinTypeKind::I128
47 | BuiltinTypeKind::F32
48 | BuiltinTypeKind::F64
49 | BuiltinTypeKind::Usize
50 | BuiltinTypeKind::Isize => "number".to_string(),
51 BuiltinTypeKind::Bool => "boolean".to_string(),
52 BuiltinTypeKind::Char | BuiltinTypeKind::String => "string".to_string(),
53 BuiltinTypeKind::Unit => "void".to_string(),
54 },
55 }
56}
57
58pub fn ty_decl(ty: DynTapi) -> Option<String> {
59 use std::fmt::Write;
60 fn inner(ty: DynTapi) -> Result<Option<String>, std::fmt::Error> {
61 Ok(Some(match ty.kind() {
62 TypeKind::Struct(s) => {
63 if s.attr.transparent {
64 format!(
65 "export type {} = {};",
66 s.attr.name.serialize_name,
67 ty_name(
68 s.fields
69 .iter()
70 .find(|f| !f.attr.skip_serializing)
71 .unwrap()
72 .ty
73 ),
74 )
75 } else {
76 let ts_fields = ts_fields(true, &s.fields);
77 format!(
78 "export type {} = {{\n{ts_fields}\n}};",
79 s.attr.name.serialize_name,
80 )
81 }
82 }
83 TypeKind::TupleStruct(s) => {
84 let ts_fields = ts_tuple(&s.fields.iter().map(|f| f.ty).collect_vec());
85 format!("export type {} = {ts_fields};", s.attr.name.serialize_name,)
86 }
87 TypeKind::Enum(e) => {
88 let mut out = String::new();
89 write!(out, "export type {} =\n | ", e.attr.name.serialize_name)?;
90
91 let has_data = e
92 .variants
93 .iter()
94 .any(|v| matches!(&v.kind, VariantKind::Tuple(_) | VariantKind::Struct(_)));
95
96 let variants = e.variants.iter().map(|v| match &v.kind {
97 VariantKind::Unit => match &e.attr.tag {
98 TagType::External => format!("{:?}", v.name),
99 TagType::Internal { tag } | TagType::Adjacent { tag, content: _ } => {
100 format!("{{ {tag:?}: {:?} }}", v.name)
101 }
102 TagType::None => todo!("{}:{}", file!(), line!()),
103 },
104 VariantKind::Tuple(fields) => match &e.attr.tag {
105 TagType::External => {
106 format!("{{ {:?}: {} }}", v.name, ts_tuple(fields))
107 }
108 TagType::Internal { tag: _ } => {
109 unreachable!("tagged tuples are not allowed by serde")
110 }
111 TagType::Adjacent { tag, content } => {
112 format!(
113 "{{ {tag:?}: {:?}, {content:?}: {} }}",
114 v.name,
115 ts_tuple(fields),
116 )
117 }
118 TagType::None => todo!("{}:{}", file!(), line!()),
119 },
120 VariantKind::Struct(fields) => match &e.attr.tag {
121 TagType::External => {
122 let ts_fields = ts_fields(false, fields);
123 format!("{{ {:?}: {{ {ts_fields} }} }}", v.name)
124 }
125 TagType::Internal { tag } => {
126 let ts_fields = ts_fields(false, fields);
127 format!("{{ {tag:?}: {:?}, {ts_fields} }}", v.name)
128 }
129 TagType::Adjacent { tag, content } => {
130 let ts_fields = ts_fields(false, fields);
131 format!(
132 "{{ {tag:?}: {:?}, {content:?}: {{ {ts_fields} }} }}",
133 v.name
134 )
135 }
136 TagType::None => todo!("TagType::None @ {}:{}", file!(), line!()),
137 },
138 });
139
140 write!(out, "{};", variants.clone().format("\n | "))?;
141 if !has_data {
142 write!(
143 out,
144 "\nexport const {}: {}[] = [{}];",
145 heck::AsShoutySnakeCase(&e.attr.name.serialize_name),
146 e.attr.name.serialize_name,
147 variants.format(", "),
148 )?;
149 }
150 out
151 }
152 TypeKind::List(_)
153 | TypeKind::Option(_)
154 | TypeKind::Tuple(_)
155 | TypeKind::Record(_, _)
156 | TypeKind::Any
157 | TypeKind::Builtin(_) => return Ok(None),
158 }))
159 }
160 inner(ty).unwrap()
161}
162
163pub fn ts_tuple(fields: &[DynTapi]) -> String {
164 if fields.len() == 1 {
165 format!("{}", fields.iter().map(|f| full_ty_name(*f)).format(", "))
166 } else {
167 format!("[{}]", fields.iter().map(|f| full_ty_name(*f)).format(", "))
168 }
169}
170
171pub fn ts_fields(multi_line: bool, fields: &[crate::kind::Field]) -> impl std::fmt::Display + '_ {
172 let fields = fields.iter().filter(|f| !f.attr.skip_serializing).map(|f| {
173 let name = match &f.name {
174 crate::kind::FieldName::Named(n) => &n.serialize_name,
175 crate::kind::FieldName::Index(_) => todo!(),
176 };
177 (name, full_ty_name(f.ty))
178 });
179 if multi_line {
180 fields
181 .map(|(name, ty)| format!(" {name}: {ty}"))
182 .join(",\n")
183 } else {
184 fields.map(|(name, ty)| format!("{name}: {ty}")).join(", ")
185 }
186}