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