swc_typescript/fast_dts/
types.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
use swc_common::{BytePos, Span, Spanned, DUMMY_SP};
use swc_ecma_ast::{
    ArrayLit, ArrowExpr, Expr, Function, Lit, ObjectLit, Param, Pat, Prop, PropName, PropOrSpread,
    Str, Tpl, TsFnOrConstructorType, TsFnParam, TsFnType, TsKeywordTypeKind, TsLit,
    TsMethodSignature, TsPropertySignature, TsTupleElement, TsTupleType, TsType, TsTypeElement,
    TsTypeLit, TsTypeOperator, TsTypeOperatorOp, UnaryOp,
};

use super::{
    inferrer::ReturnTypeInferrer,
    type_ann,
    util::{
        ast_ext::PatExt,
        types::{ts_keyword_type, ts_lit_type},
    },
    FastDts,
};

impl FastDts {
    pub(crate) fn transform_expr_to_ts_type(&mut self, expr: &Expr) -> Option<Box<TsType>> {
        match expr {
            Expr::Ident(ident) if ident.sym == "undefined" => {
                Some(ts_keyword_type(TsKeywordTypeKind::TsAnyKeyword))
            }
            Expr::Lit(lit) => match lit {
                Lit::Str(string) => Some(ts_lit_type(TsLit::Str(string.clone()))),
                Lit::Bool(b) => Some(ts_lit_type(TsLit::Bool(*b))),
                Lit::Null(_) => Some(ts_keyword_type(TsKeywordTypeKind::TsAnyKeyword)),
                Lit::Num(number) => Some(ts_lit_type(TsLit::Number(number.clone()))),
                Lit::BigInt(big_int) => Some(ts_lit_type(TsLit::BigInt(big_int.clone()))),
                Lit::Regex(_) | Lit::JSXText(_) => None,
            },
            Expr::Tpl(tpl) => self
                .tpl_to_string(tpl)
                .map(|string| ts_lit_type(TsLit::Str(string))),
            Expr::Unary(unary) if Self::can_infer_unary_expr(unary) => {
                let mut expr = self.transform_expr_to_ts_type(&unary.arg)?;
                if unary.op == UnaryOp::Minus {
                    match &mut expr.as_mut_ts_lit_type()?.lit {
                        TsLit::Number(number) => {
                            number.value = -number.value;
                            number.raw = None;
                        }
                        TsLit::BigInt(big_int) => {
                            *big_int.value = -*big_int.value.clone();
                            big_int.raw = None;
                        }
                        _ => {}
                    }
                };
                Some(expr)
            }
            Expr::Array(array) => self.transform_array_to_ts_type(array),
            Expr::Object(obj) => self.transform_object_to_ts_type(obj, true),
            Expr::Fn(fn_expr) => self.transform_fn_to_ts_type(
                &fn_expr.function,
                fn_expr.ident.as_ref().map(|ident| ident.span),
            ),
            Expr::Arrow(arrow) => self.transform_arrow_expr_to_ts_type(arrow),
            Expr::TsConstAssertion(assertion) => self.transform_expr_to_ts_type(&assertion.expr),
            Expr::TsAs(ts_as) => Some(ts_as.type_ann.clone()),
            _ => None,
        }
    }

    pub(crate) fn transform_fn_to_ts_type(
        &mut self,
        function: &Function,
        ident_span: Option<Span>,
    ) -> Option<Box<TsType>> {
        let return_type = self.infer_function_return_type(function);
        if return_type.is_none() {
            self.function_must_have_explicit_return_type(
                ident_span
                    .unwrap_or_else(|| Span::new(function.span_lo(), function.body.span_lo())),
            );
        }

        return_type.map(|return_type| {
            Box::new(TsType::TsFnOrConstructorType(
                TsFnOrConstructorType::TsFnType(TsFnType {
                    span: DUMMY_SP,
                    params: self.transform_fn_params_to_ts_type(&function.params),
                    type_params: function.type_params.clone(),
                    type_ann: return_type,
                }),
            ))
        })
    }

    pub(crate) fn transform_arrow_expr_to_ts_type(
        &mut self,
        arrow: &ArrowExpr,
    ) -> Option<Box<TsType>> {
        let return_type = self.infer_arrow_return_type(arrow);
        if return_type.is_none() {
            self.function_must_have_explicit_return_type(Span::new(
                arrow.span_lo(),
                arrow.body.span_lo() + BytePos(1),
            ));
        }

        return_type.map(|return_type| {
            Box::new(TsType::TsFnOrConstructorType(
                TsFnOrConstructorType::TsFnType(TsFnType {
                    span: DUMMY_SP,
                    params: self.transform_fn_params_to_ts_type(
                        &arrow
                            .params
                            .iter()
                            .map(|pat| Param {
                                span: pat.span(),
                                decorators: Vec::new(),
                                pat: pat.clone(),
                            })
                            .collect::<Vec<_>>(),
                    ),
                    type_params: arrow.type_params.clone(),
                    type_ann: return_type,
                }),
            ))
        })
    }

    pub(crate) fn transform_fn_params_to_ts_type(&mut self, params: &[Param]) -> Vec<TsFnParam> {
        let mut params = params.to_owned().clone();
        self.transform_fn_params(&mut params);
        params
            .into_iter()
            .filter_map(|param| match param.pat {
                Pat::Ident(binding_ident) => Some(TsFnParam::Ident(binding_ident)),
                Pat::Array(array_pat) => Some(TsFnParam::Array(array_pat)),
                Pat::Rest(rest_pat) => Some(TsFnParam::Rest(rest_pat)),
                Pat::Object(object_pat) => Some(TsFnParam::Object(object_pat)),
                Pat::Assign(_) | Pat::Invalid(_) | Pat::Expr(_) => None,
            })
            .collect()
    }

    pub(crate) fn transform_object_to_ts_type(
        &mut self,
        object: &ObjectLit,
        is_const: bool,
    ) -> Option<Box<TsType>> {
        let mut members = Vec::new();
        for prop in &object.props {
            match prop {
                PropOrSpread::Prop(prop) => match prop.as_ref() {
                    Prop::Shorthand(_) => {
                        self.shorthand_property(object.span);
                        continue;
                    }
                    Prop::KeyValue(kv) => {
                        if self.report_property_key(&kv.key) {
                            continue;
                        }

                        let type_ann = if is_const {
                            self.transform_expr_to_ts_type(&kv.value)
                        } else {
                            self.infer_type_from_expr(&kv.value)
                        }
                        .map(type_ann);

                        if type_ann.is_none() {
                            self.inferred_type_of_expression(kv.value.span());
                        }

                        let (key, computed) = self.transform_property_name_to_expr(&kv.key);
                        members.push(TsTypeElement::TsPropertySignature(TsPropertySignature {
                            span: DUMMY_SP,
                            readonly: is_const,
                            key: Box::new(key),
                            computed,
                            optional: false,
                            type_ann,
                        }));
                    }
                    Prop::Getter(getter) => {
                        if self.report_property_key(&getter.key) {
                            continue;
                        }

                        let type_ann = getter.type_ann.clone().or_else(|| {
                            getter
                                .body
                                .as_ref()
                                .and_then(|body| ReturnTypeInferrer::infer(self, &body.stmts))
                                .map(type_ann)
                        });

                        if type_ann.is_none() {
                            self.accessor_must_have_explicit_return_type(getter.span);
                        }

                        let (key, computed) = self.transform_property_name_to_expr(&getter.key);
                        members.push(TsTypeElement::TsPropertySignature(TsPropertySignature {
                            span: DUMMY_SP,
                            readonly: is_const,
                            key: Box::new(key),
                            computed,
                            optional: false,
                            type_ann,
                        }));
                    }
                    Prop::Setter(setter) => {
                        if self.report_property_key(&setter.key) {
                            continue;
                        }

                        let (key, computed) = self.transform_property_name_to_expr(&setter.key);
                        members.push(TsTypeElement::TsPropertySignature(TsPropertySignature {
                            span: DUMMY_SP,
                            readonly: is_const,
                            key: Box::new(key),
                            computed,
                            optional: false,
                            type_ann: setter.param.get_type_ann().clone(),
                        }));
                    }
                    Prop::Method(method) => {
                        if self.report_property_key(&method.key) {
                            continue;
                        }

                        if is_const {
                            let (key, computed) = self.transform_property_name_to_expr(&method.key);
                            members.push(TsTypeElement::TsPropertySignature(TsPropertySignature {
                                span: DUMMY_SP,
                                readonly: is_const,
                                key: Box::new(key),
                                computed,
                                optional: false,
                                type_ann: self
                                    .transform_fn_to_ts_type(
                                        &method.function,
                                        Some(method.key.span()),
                                    )
                                    .map(type_ann),
                            }));
                        } else {
                            let return_type = self.infer_function_return_type(&method.function);
                            let (key, computed) = self.transform_property_name_to_expr(&method.key);
                            members.push(TsTypeElement::TsMethodSignature(TsMethodSignature {
                                span: DUMMY_SP,
                                key: Box::new(key),
                                computed,
                                optional: false,
                                params: self
                                    .transform_fn_params_to_ts_type(&method.function.params),
                                type_ann: return_type,
                                type_params: method.function.type_params.clone(),
                            }));
                        }
                    }
                    Prop::Assign(_) => {}
                },
                PropOrSpread::Spread(spread_element) => {
                    self.object_with_spread_assignments(spread_element.span());
                }
            }
        }

        Some(Box::new(TsType::TsTypeLit(TsTypeLit {
            span: DUMMY_SP,
            members,
        })))
    }

    pub(crate) fn transform_array_to_ts_type(&mut self, array: &ArrayLit) -> Option<Box<TsType>> {
        let mut elements = Vec::new();
        for elem in &array.elems {
            let Some(elem) = elem else {
                elements.push(TsTupleElement {
                    span: DUMMY_SP,
                    label: None,
                    ty: ts_keyword_type(TsKeywordTypeKind::TsAnyKeyword),
                });
                continue;
            };

            if let Some(spread_span) = elem.spread {
                self.arrays_with_spread_elements(spread_span);
                continue;
            }

            if let Some(type_ann) = self.transform_expr_to_ts_type(&elem.expr) {
                elements.push(TsTupleElement {
                    span: DUMMY_SP,
                    label: None,
                    ty: type_ann,
                });
            } else {
                self.inferred_type_of_expression(elem.span());
            }
        }

        Some(Box::new(TsType::TsTypeOperator(TsTypeOperator {
            span: DUMMY_SP,
            op: TsTypeOperatorOp::ReadOnly,
            type_ann: Box::new(TsType::TsTupleType(TsTupleType {
                span: DUMMY_SP,
                elem_types: elements,
            })),
        })))
    }

    pub(crate) fn transform_property_name_to_expr(&mut self, name: &PropName) -> (Expr, bool) {
        match name {
            PropName::Ident(ident) => (Expr::Ident(ident.clone().into()), false),
            PropName::Str(str_prop) => (Lit::Str(str_prop.clone()).into(), false),
            PropName::Num(num) => (Lit::Num(num.clone()).into(), true),
            PropName::Computed(computed) => (*computed.expr.clone(), true),
            PropName::BigInt(big_int) => (Lit::BigInt(big_int.clone()).into(), true),
        }
    }

    pub(crate) fn tpl_to_string(&mut self, tpl: &Tpl) -> Option<Str> {
        if !tpl.exprs.is_empty() {
            return None;
        }

        tpl.quasis.first().map(|element| Str {
            span: DUMMY_SP,
            value: element.cooked.as_ref().unwrap_or(&element.raw).clone(),
            raw: None,
        })
    }

    pub(crate) fn is_literal(expr: &Expr) -> bool {
        match expr {
            Expr::Lit(_) => true,
            Expr::Unary(unary) => Self::can_infer_unary_expr(unary),
            _ => false,
        }
    }
}