swc_typescript/fast_dts/
types.rs

1use rustc_hash::{FxHashMap, FxHashSet};
2use swc_common::{BytePos, Span, Spanned, SyntaxContext, DUMMY_SP};
3use swc_ecma_ast::{
4    ArrayLit, ArrowExpr, Expr, Function, GetterProp, Lit, MemberExpr, ObjectLit, Param, Pat, Prop,
5    PropName, PropOrSpread, Str, Tpl, TsFnOrConstructorType, TsFnParam, TsFnType,
6    TsKeywordTypeKind, TsLit, TsMethodSignature, TsPropertySignature, TsTupleElement, TsTupleType,
7    TsType, TsTypeAnn, TsTypeElement, TsTypeLit, TsTypeOperator, TsTypeOperatorOp, UnaryOp,
8};
9use swc_ecma_utils::quote_ident;
10
11use super::{
12    inferrer::ReturnTypeInferrer,
13    type_ann,
14    util::{
15        ast_ext::{ExprExit, PatExt, StaticProp},
16        types::{ts_keyword_type, ts_lit_type},
17    },
18    FastDts,
19};
20use crate::fast_dts::util::ast_ext::PropNameExit;
21
22impl FastDts {
23    pub(crate) fn transform_expr_to_ts_type(&mut self, expr: &Expr) -> Option<Box<TsType>> {
24        match expr {
25            Expr::Ident(ident) if ident.sym == "undefined" => {
26                Some(ts_keyword_type(TsKeywordTypeKind::TsUndefinedKeyword))
27            }
28            Expr::Lit(lit) => match lit {
29                Lit::Str(string) => Some(ts_lit_type(TsLit::Str(string.clone()))),
30                Lit::Bool(b) => Some(ts_lit_type(TsLit::Bool(*b))),
31                Lit::Null(_) => Some(ts_keyword_type(TsKeywordTypeKind::TsNullKeyword)),
32                Lit::Num(number) => Some(ts_lit_type(TsLit::Number(number.clone()))),
33                Lit::BigInt(big_int) => Some(ts_lit_type(TsLit::BigInt(big_int.clone()))),
34                Lit::Regex(_) | Lit::JSXText(_) => None,
35            },
36            Expr::Tpl(tpl) => self
37                .tpl_to_string(tpl)
38                .map(|string| ts_lit_type(TsLit::Str(string))),
39            Expr::Unary(unary) if Self::can_infer_unary_expr(unary) => {
40                let mut expr = self.transform_expr_to_ts_type(&unary.arg)?;
41                if unary.op == UnaryOp::Minus {
42                    match &mut expr.as_mut_ts_lit_type()?.lit {
43                        TsLit::Number(number) => {
44                            number.value = -number.value;
45                            number.raw = None;
46                        }
47                        TsLit::BigInt(big_int) => {
48                            *big_int.value = -*big_int.value.clone();
49                            big_int.raw = None;
50                        }
51                        _ => {}
52                    }
53                };
54                Some(expr)
55            }
56            Expr::Array(array) => self.transform_array_to_ts_type(array),
57            Expr::Object(obj) => self.transform_object_to_ts_type(obj, true),
58            Expr::Fn(fn_expr) => self.transform_fn_to_ts_type(
59                &fn_expr.function,
60                fn_expr.ident.as_ref().map(|ident| ident.span),
61            ),
62            Expr::Arrow(arrow) => self.transform_arrow_expr_to_ts_type(arrow),
63            Expr::TsConstAssertion(assertion) => self.transform_expr_to_ts_type(&assertion.expr),
64            Expr::TsAs(ts_as) => Some(ts_as.type_ann.clone()),
65            _ => None,
66        }
67    }
68
69    pub(crate) fn transform_fn_to_ts_type(
70        &mut self,
71        function: &Function,
72        ident_span: Option<Span>,
73    ) -> Option<Box<TsType>> {
74        let return_type = self.infer_function_return_type(function);
75        if return_type.is_none() {
76            self.function_must_have_explicit_return_type(
77                ident_span
78                    .unwrap_or_else(|| Span::new(function.span_lo(), function.body.span_lo())),
79            );
80        }
81
82        return_type.map(|return_type| {
83            Box::new(TsType::TsFnOrConstructorType(
84                TsFnOrConstructorType::TsFnType(TsFnType {
85                    span: DUMMY_SP,
86                    params: self.transform_fn_params_to_ts_type(&function.params),
87                    type_params: function.type_params.clone(),
88                    type_ann: return_type,
89                }),
90            ))
91        })
92    }
93
94    pub(crate) fn transform_arrow_expr_to_ts_type(
95        &mut self,
96        arrow: &ArrowExpr,
97    ) -> Option<Box<TsType>> {
98        let return_type = self.infer_arrow_return_type(arrow);
99        if return_type.is_none() {
100            self.function_must_have_explicit_return_type(Span::new(
101                arrow.span_lo(),
102                arrow.body.span_lo() + BytePos(1),
103            ));
104        }
105
106        return_type.map(|return_type| {
107            Box::new(TsType::TsFnOrConstructorType(
108                TsFnOrConstructorType::TsFnType(TsFnType {
109                    span: DUMMY_SP,
110                    params: self.transform_fn_params_to_ts_type(
111                        &arrow
112                            .params
113                            .iter()
114                            .map(|pat| Param {
115                                span: pat.span(),
116                                decorators: Vec::new(),
117                                pat: pat.clone(),
118                            })
119                            .collect::<Vec<_>>(),
120                    ),
121                    type_params: arrow.type_params.clone(),
122                    type_ann: return_type,
123                }),
124            ))
125        })
126    }
127
128    pub(crate) fn transform_fn_params_to_ts_type(&mut self, params: &[Param]) -> Vec<TsFnParam> {
129        let mut params = params.to_owned().clone();
130        self.transform_fn_params(&mut params);
131        params
132            .into_iter()
133            .filter_map(|param| match param.pat {
134                Pat::Ident(binding_ident) => Some(TsFnParam::Ident(binding_ident)),
135                Pat::Array(array_pat) => Some(TsFnParam::Array(array_pat)),
136                Pat::Rest(rest_pat) => Some(TsFnParam::Rest(rest_pat)),
137                Pat::Object(object_pat) => Some(TsFnParam::Object(object_pat)),
138                Pat::Assign(_) | Pat::Invalid(_) | Pat::Expr(_) => None,
139            })
140            .collect()
141    }
142
143    pub(crate) fn transform_object_to_ts_type(
144        &mut self,
145        object: &ObjectLit,
146        is_const: bool,
147    ) -> Option<Box<TsType>> {
148        let mut members = Vec::new();
149        let (setter_getter_annotations, seen_setter) =
150            self.collect_object_getter_or_setter_annotations(object);
151        let mut has_seen = FxHashSet::default();
152
153        for prop in &object.props {
154            match prop {
155                PropOrSpread::Prop(prop) => match prop.as_ref() {
156                    Prop::Shorthand(_) => {
157                        self.shorthand_property(object.span);
158                        continue;
159                    }
160                    Prop::KeyValue(kv) => {
161                        if self.report_property_key(&kv.key) {
162                            continue;
163                        }
164
165                        let type_ann = if is_const {
166                            self.transform_expr_to_ts_type(&kv.value)
167                        } else {
168                            self.infer_type_from_expr(&kv.value)
169                        }
170                        .map(type_ann);
171
172                        if type_ann.is_none() {
173                            self.inferred_type_of_expression(kv.value.span());
174                        }
175
176                        let key = self.transform_property_name_to_expr(&kv.key);
177                        members.push(TsTypeElement::TsPropertySignature(TsPropertySignature {
178                            span: DUMMY_SP,
179                            readonly: is_const,
180                            key: Box::new(key),
181                            computed: kv.key.is_computed(),
182                            optional: false,
183                            type_ann,
184                        }));
185                    }
186                    Prop::Getter(getter) => {
187                        if self.report_property_key(&getter.key) {
188                            continue;
189                        }
190
191                        let mut getter_type_ann = None;
192
193                        let mut has_setter = false;
194
195                        if let Some(static_prop) = getter.key.static_prop(self.unresolved_mark) {
196                            if has_seen.contains(&static_prop) {
197                                continue;
198                            }
199                            has_setter = seen_setter.contains(&static_prop);
200                            if let Some(type_ann) = setter_getter_annotations.get(&static_prop) {
201                                getter_type_ann = Some(type_ann.clone());
202                            }
203
204                            has_seen.insert(static_prop);
205                        }
206
207                        if getter_type_ann.is_none() {
208                            self.accessor_must_have_explicit_return_type(getter.span);
209                        }
210
211                        let key = self.transform_property_name_to_expr(&getter.key);
212                        members.push(TsTypeElement::TsPropertySignature(TsPropertySignature {
213                            span: DUMMY_SP,
214                            readonly: !has_setter,
215                            key: Box::new(key),
216                            computed: getter.key.is_computed(),
217                            optional: false,
218                            type_ann: getter_type_ann,
219                        }));
220                    }
221                    Prop::Setter(setter) => {
222                        if self.report_property_key(&setter.key) {
223                            continue;
224                        }
225
226                        let mut setter_type_ann = None;
227
228                        if let Some(static_prop) = setter.key.static_prop(self.unresolved_mark) {
229                            if has_seen.contains(&static_prop) {
230                                continue;
231                            }
232                            if let Some(type_ann) = setter_getter_annotations.get(&static_prop) {
233                                setter_type_ann = Some(type_ann.clone());
234                            }
235
236                            has_seen.insert(static_prop);
237                        }
238
239                        if setter_type_ann.is_none() {
240                            setter_type_ann = setter.param.get_type_ann().clone();
241                        }
242
243                        if setter_type_ann.is_none() {
244                            self.accessor_must_have_explicit_return_type(setter.span);
245                        }
246
247                        let key = self.transform_property_name_to_expr(&setter.key);
248                        members.push(TsTypeElement::TsPropertySignature(TsPropertySignature {
249                            span: DUMMY_SP,
250                            readonly: false,
251                            key: Box::new(key),
252                            computed: setter.key.is_computed(),
253                            optional: false,
254                            type_ann: setter_type_ann,
255                        }));
256                    }
257                    Prop::Method(method) => {
258                        if self.report_property_key(&method.key) {
259                            continue;
260                        }
261
262                        if is_const {
263                            let key = self.transform_property_name_to_expr(&method.key);
264                            members.push(TsTypeElement::TsPropertySignature(TsPropertySignature {
265                                span: DUMMY_SP,
266                                readonly: is_const,
267                                key: Box::new(key),
268                                computed: method.key.is_computed(),
269                                optional: false,
270                                type_ann: self
271                                    .transform_fn_to_ts_type(
272                                        &method.function,
273                                        Some(method.key.span()),
274                                    )
275                                    .map(type_ann),
276                            }));
277                        } else {
278                            let return_type = self.infer_function_return_type(&method.function);
279                            let key = self.transform_property_name_to_expr(&method.key);
280                            members.push(TsTypeElement::TsMethodSignature(TsMethodSignature {
281                                span: DUMMY_SP,
282                                key: Box::new(key),
283                                computed: method.key.is_computed(),
284                                optional: false,
285                                params: self
286                                    .transform_fn_params_to_ts_type(&method.function.params),
287                                type_ann: return_type,
288                                type_params: method.function.type_params.clone(),
289                            }));
290                        }
291                    }
292                    Prop::Assign(_) => {}
293                },
294                PropOrSpread::Spread(spread_element) => {
295                    self.object_with_spread_assignments(spread_element.span());
296                }
297            }
298        }
299
300        Some(Box::new(TsType::TsTypeLit(TsTypeLit {
301            span: DUMMY_SP,
302            members,
303        })))
304    }
305
306    pub(crate) fn transform_array_to_ts_type(&mut self, array: &ArrayLit) -> Option<Box<TsType>> {
307        let mut elements = Vec::new();
308        for elem in &array.elems {
309            let Some(elem) = elem else {
310                elements.push(TsTupleElement {
311                    span: DUMMY_SP,
312                    label: None,
313                    ty: ts_keyword_type(TsKeywordTypeKind::TsUndefinedKeyword),
314                });
315                continue;
316            };
317
318            if let Some(spread_span) = elem.spread {
319                self.arrays_with_spread_elements(spread_span);
320                continue;
321            }
322
323            if let Some(type_ann) = self.transform_expr_to_ts_type(&elem.expr) {
324                elements.push(TsTupleElement {
325                    span: DUMMY_SP,
326                    label: None,
327                    ty: type_ann,
328                });
329            } else {
330                self.inferred_type_of_expression(elem.span());
331            }
332        }
333
334        Some(Box::new(TsType::TsTypeOperator(TsTypeOperator {
335            span: DUMMY_SP,
336            op: TsTypeOperatorOp::ReadOnly,
337            type_ann: Box::new(TsType::TsTupleType(TsTupleType {
338                span: DUMMY_SP,
339                elem_types: elements,
340            })),
341        })))
342    }
343
344    pub(crate) fn transform_property_name_to_expr(&mut self, name: &PropName) -> Expr {
345        match name {
346            PropName::Ident(ident) => ident.clone().into(),
347            PropName::Str(str_prop) => str_prop.clone().into(),
348            PropName::Num(num) => num.clone().into(),
349            PropName::BigInt(big_int) => big_int.clone().into(),
350            PropName::Computed(computed) => {
351                if let Some(prop) = computed.expr.get_global_symbol_prop(self.unresolved_mark) {
352                    let ctxt = SyntaxContext::empty().apply_mark(self.unresolved_mark);
353                    let symbol = quote_ident!(ctxt, "Symbol");
354
355                    return MemberExpr {
356                        span: name.span(),
357                        obj: symbol.into(),
358                        prop: prop.clone(),
359                    }
360                    .into();
361                }
362
363                if let Expr::Tpl(Tpl {
364                    span,
365                    exprs,
366                    quasis,
367                }) = computed.expr.as_ref()
368                {
369                    if exprs.is_empty() {
370                        let str_prop = quasis
371                            .first()
372                            .and_then(|el| el.cooked.as_ref())
373                            .unwrap()
374                            .clone();
375
376                        let str_prop = Str {
377                            span: *span,
378                            value: str_prop,
379                            raw: None,
380                        };
381
382                        return str_prop.into();
383                    }
384                }
385
386                computed.expr.as_ref().clone()
387            }
388        }
389    }
390
391    pub(crate) fn check_ts_signature(&mut self, signature: &TsTypeElement) {
392        match signature {
393            TsTypeElement::TsPropertySignature(ts_property_signature) => {
394                self.report_signature_property_key(
395                    &ts_property_signature.key,
396                    ts_property_signature.computed,
397                );
398            }
399            TsTypeElement::TsGetterSignature(ts_getter_signature) => {
400                self.report_signature_property_key(
401                    &ts_getter_signature.key,
402                    ts_getter_signature.computed,
403                );
404            }
405            TsTypeElement::TsSetterSignature(ts_setter_signature) => {
406                self.report_signature_property_key(
407                    &ts_setter_signature.key,
408                    ts_setter_signature.computed,
409                );
410            }
411            TsTypeElement::TsMethodSignature(ts_method_signature) => {
412                self.report_signature_property_key(
413                    &ts_method_signature.key,
414                    ts_method_signature.computed,
415                );
416            }
417            _ => {}
418        }
419    }
420
421    pub(crate) fn report_signature_property_key(&mut self, key: &Expr, computed: bool) {
422        if !computed {
423            return;
424        }
425
426        let is_not_allowed = match key {
427            Expr::Ident(_) | Expr::Member(_) | Expr::OptChain(_) => key.get_root_ident().is_none(),
428            _ => !Self::is_literal(key),
429        };
430
431        if is_not_allowed {
432            self.signature_computed_property_name(key.span());
433        }
434    }
435
436    pub(crate) fn tpl_to_string(&mut self, tpl: &Tpl) -> Option<Str> {
437        if !tpl.exprs.is_empty() {
438            return None;
439        }
440
441        tpl.quasis.first().map(|element| Str {
442            span: DUMMY_SP,
443            value: element.cooked.as_ref().unwrap_or(&element.raw).clone(),
444            raw: None,
445        })
446    }
447
448    pub(crate) fn is_literal(expr: &Expr) -> bool {
449        match expr {
450            Expr::Lit(_) => true,
451            Expr::Tpl(tpl) => tpl.exprs.is_empty(),
452            Expr::Unary(unary) => Self::can_infer_unary_expr(unary),
453            _ => false,
454        }
455    }
456
457    pub(crate) fn collect_object_getter_or_setter_annotations(
458        &mut self,
459        object: &ObjectLit,
460    ) -> (FxHashMap<StaticProp, Box<TsTypeAnn>>, FxHashSet<StaticProp>) {
461        let mut annotations = FxHashMap::default();
462        let mut seen_setter = FxHashSet::default();
463
464        for prop in &object.props {
465            let Some(prop) = prop.as_prop() else {
466                continue;
467            };
468
469            let Some(static_prop) = prop.static_prop(self.unresolved_mark) else {
470                continue;
471            };
472
473            match &**prop {
474                Prop::Getter(GetterProp {
475                    type_ann: ty,
476                    body: Some(body),
477                    ..
478                }) => {
479                    if let Some(type_ann) = ty
480                        .clone()
481                        .or_else(|| ReturnTypeInferrer::infer(self, &body.stmts).map(type_ann))
482                    {
483                        annotations.insert(static_prop, type_ann);
484                    }
485                }
486                Prop::Setter(setter) => {
487                    if let Some(type_ann) = setter.param.get_type_ann().clone() {
488                        annotations.insert(static_prop.clone(), type_ann);
489                    }
490                    seen_setter.insert(static_prop);
491                }
492                _ => {}
493            }
494        }
495
496        (annotations, seen_setter)
497    }
498}