tapi/targets/
ts.rs

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}