tapi/targets/
js.rs

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(|_, name| format!("export namespace {} {{", name)),
15        // end_namespace: Box::new(|_, _| "}".to_string()),
16        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                // write!(out, "{};", variants.clone().format("\n  | "))?;
119                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}