swc_typescript/fast_dts/
class.rs

1use rustc_hash::FxHashMap;
2use swc_atoms::atom;
3use swc_common::{util::take::Take, Spanned, SyntaxContext, DUMMY_SP};
4use swc_ecma_ast::{
5    Accessibility, BindingIdent, Class, ClassMember, ClassProp, Expr, Key, Lit, MethodKind, Param,
6    ParamOrTsParamProp, Pat, PrivateName, PrivateProp, PropName, TsParamProp, TsParamPropParam,
7    TsTypeAnn,
8};
9
10use super::{
11    type_ann,
12    util::ast_ext::{ExprExit, PatExt, PropNameExit, StaticProp},
13    FastDts,
14};
15
16impl FastDts {
17    pub(crate) fn transform_class(&mut self, class: &mut Class) {
18        if let Some(super_class) = &class.super_class {
19            let is_not_allowed = super_class.get_root_ident().is_none();
20            if is_not_allowed {
21                self.extends_clause_expression(super_class.span());
22            }
23        }
24
25        let setter_getter_annotations = self.collect_getter_or_setter_annotations(class);
26        let mut is_function_overloads = false;
27        let mut has_private_key = false;
28        let body = class.body.take();
29
30        for mut member in body {
31            match &mut member {
32                ClassMember::Constructor(constructor) => {
33                    if self.has_internal_annotation(constructor.span_lo()) {
34                        continue;
35                    }
36
37                    let private_constructor =
38                        constructor.accessibility == Some(Accessibility::Private);
39
40                    // Transform parameters
41                    class.body.splice(
42                        0..0,
43                        self.transform_constructor_params(
44                            &mut constructor.params,
45                            private_constructor,
46                        ),
47                    );
48
49                    if !(constructor.is_optional) && constructor.body.is_none() {
50                        is_function_overloads = true;
51                    } else if is_function_overloads {
52                        is_function_overloads = false;
53                        continue;
54                    }
55
56                    if private_constructor {
57                        constructor.params.clear();
58                    }
59
60                    constructor.body = None;
61                    constructor.accessibility =
62                        self.transform_accessibility(constructor.accessibility);
63                    class.body.push(member);
64                }
65                ClassMember::Method(method) => {
66                    if self.has_internal_annotation(method.span_lo()) {
67                        continue;
68                    }
69                    if !(method.is_abstract || method.is_optional) && method.function.body.is_none()
70                    {
71                        is_function_overloads = true;
72                    } else if is_function_overloads {
73                        is_function_overloads = false;
74                        continue;
75                    }
76
77                    if self.report_property_key(&method.key) {
78                        continue;
79                    }
80
81                    // Transform parameters
82                    match method.kind {
83                        MethodKind::Method => {
84                            if method.accessibility.is_some_and(|accessibility| {
85                                accessibility == Accessibility::Private
86                            }) {
87                                class.body.push(ClassMember::ClassProp(ClassProp {
88                                    span: method.span,
89                                    key: method.key.clone(),
90                                    value: None,
91                                    type_ann: None,
92                                    is_static: method.is_static,
93                                    decorators: Vec::new(),
94                                    accessibility: self
95                                        .transform_accessibility(method.accessibility),
96                                    is_abstract: method.is_abstract,
97                                    is_optional: method.is_optional,
98                                    is_override: false,
99                                    readonly: false,
100                                    declare: false,
101                                    definite: false,
102                                }));
103                                continue;
104                            }
105                            self.transform_fn_params(&mut method.function.params);
106                        }
107                        MethodKind::Getter => {
108                            if method.accessibility.is_some_and(|accessibility| {
109                                accessibility == Accessibility::Private
110                            }) {
111                                method.function.params.clear();
112                                method.function.return_type = None;
113                                method.function.decorators.clear();
114                                method.function.body = None;
115                                method.function.is_generator = false;
116                                method.function.is_async = false;
117                                method.accessibility =
118                                    self.transform_accessibility(method.accessibility);
119                                class.body.push(member);
120                                continue;
121                            }
122                            self.transform_fn_params(&mut method.function.params);
123                        }
124                        MethodKind::Setter => {
125                            if method.accessibility.is_some_and(|accessibility| {
126                                accessibility == Accessibility::Private
127                            }) {
128                                method.function.params = vec![Param {
129                                    span: DUMMY_SP,
130                                    decorators: Vec::new(),
131                                    pat: Pat::Ident(BindingIdent {
132                                        id: atom!("value").into(),
133                                        type_ann: None,
134                                    }),
135                                }];
136                                method.function.decorators.clear();
137                                method.function.body = None;
138                                method.function.is_generator = false;
139                                method.function.is_async = false;
140                                method.function.return_type = None;
141                                method.accessibility =
142                                    self.transform_accessibility(method.accessibility);
143                                class.body.push(member);
144                                continue;
145                            }
146
147                            if method.function.params.is_empty() {
148                                method.function.params.push(Param {
149                                    span: DUMMY_SP,
150                                    decorators: Vec::new(),
151                                    pat: Pat::Ident(BindingIdent {
152                                        id: atom!("value").into(),
153                                        type_ann: None,
154                                    }),
155                                });
156                            } else {
157                                method.function.params.truncate(1);
158                                let param = method.function.params.first_mut().unwrap();
159                                let static_prop = method.key.static_prop(self.unresolved_mark);
160
161                                if let Some(type_ann) = static_prop
162                                    .and_then(|prop| setter_getter_annotations.get(&prop))
163                                {
164                                    param.pat.set_type_ann(Some(type_ann.clone()));
165                                }
166                            }
167                        }
168                        #[cfg(swc_ast_unknown)]
169                        _ => panic!("unable to access unknown nodes"),
170                    }
171
172                    // Transform return
173                    match method.kind {
174                        MethodKind::Method => {
175                            self.transform_fn_return_type(&mut method.function);
176                            if method.function.return_type.is_none() {
177                                self.method_must_have_explicit_return_type(method.key.span());
178                            }
179                        }
180                        MethodKind::Getter => {
181                            self.transform_fn_return_type(&mut method.function);
182                            if method.function.return_type.is_none() {
183                                method.function.return_type = method
184                                    .key
185                                    .static_prop(self.unresolved_mark)
186                                    .and_then(|prop| setter_getter_annotations.get(&prop))
187                                    .cloned();
188                            }
189                            if method.function.return_type.is_none() {
190                                self.accessor_must_have_explicit_return_type(method.key.span());
191                            }
192                        }
193                        MethodKind::Setter => method.function.return_type = None,
194                        #[cfg(swc_ast_unknown)]
195                        _ => panic!("unable to access unknown nodes"),
196                    }
197
198                    method.function.body = None;
199                    method.function.is_async = false;
200                    method.function.is_generator = false;
201                    method.accessibility = self.transform_accessibility(method.accessibility);
202                    class.body.push(member);
203                }
204                ClassMember::ClassProp(prop) => {
205                    if self.has_internal_annotation(prop.span_lo()) {
206                        continue;
207                    }
208                    if self.report_property_key(&prop.key) {
209                        continue;
210                    }
211
212                    self.transform_class_property(prop);
213                    class.body.push(member);
214                }
215                ClassMember::PrivateMethod(_) | ClassMember::PrivateProp(_) => {
216                    has_private_key = true;
217                }
218                ClassMember::TsIndexSignature(ts_index_signature) => {
219                    if self.has_internal_annotation(ts_index_signature.span_lo()) {
220                        continue;
221                    }
222                    class.body.push(member);
223                }
224                ClassMember::AutoAccessor(auto_accessor) => {
225                    if self.has_internal_annotation(auto_accessor.span_lo()) {
226                        continue;
227                    }
228                    let Key::Public(prop_name) = &auto_accessor.key else {
229                        has_private_key = true;
230                        continue;
231                    };
232
233                    if self.report_property_key(prop_name) {
234                        continue;
235                    }
236
237                    if auto_accessor
238                        .accessibility
239                        .is_some_and(|accessibility| accessibility == Accessibility::Private)
240                    {
241                        auto_accessor.decorators.clear();
242                        auto_accessor.definite = false;
243                        auto_accessor.type_ann = None;
244                        auto_accessor.accessibility =
245                            self.transform_accessibility(auto_accessor.accessibility);
246                    }
247
248                    auto_accessor.is_override = false;
249                    auto_accessor.value = None;
250                    class.body.push(member);
251                }
252                ClassMember::Empty(_) | ClassMember::StaticBlock(_) => {}
253                #[cfg(swc_ast_unknown)]
254                _ => panic!("unable to access unknown nodes"),
255            }
256        }
257
258        if has_private_key {
259            class.body.insert(
260                0,
261                ClassMember::PrivateProp(PrivateProp {
262                    span: DUMMY_SP,
263                    ctxt: SyntaxContext::empty(),
264                    key: PrivateName {
265                        span: DUMMY_SP,
266                        name: atom!("private"),
267                    },
268                    value: None,
269                    type_ann: None,
270                    is_static: false,
271                    decorators: Vec::new(),
272                    accessibility: None,
273                    is_optional: false,
274                    is_override: false,
275                    readonly: false,
276                    definite: false,
277                }),
278            );
279        }
280    }
281
282    pub(crate) fn transform_constructor_params(
283        &mut self,
284        params: &mut [ParamOrTsParamProp],
285        private_constructor: bool,
286    ) -> Vec<ClassMember> {
287        let mut is_required = false;
288        let mut private_properties = Vec::new();
289        for param in params.iter_mut().rev() {
290            match param {
291                ParamOrTsParamProp::TsParamProp(ts_param_prop) => {
292                    is_required |= match &ts_param_prop.param {
293                        TsParamPropParam::Ident(binding_ident) => !binding_ident.optional,
294                        TsParamPropParam::Assign(_) => false,
295                        #[cfg(swc_ast_unknown)]
296                        _ => panic!("unable to access unknown nodes"),
297                    };
298                    if let Some(private_prop) =
299                        self.transform_constructor_ts_param(ts_param_prop, is_required)
300                    {
301                        private_properties.push(private_prop);
302                    }
303                    ts_param_prop.readonly = false;
304                    ts_param_prop.accessibility = None;
305                }
306                ParamOrTsParamProp::Param(param) => {
307                    if private_constructor {
308                        continue;
309                    }
310
311                    self.transform_fn_param(param, is_required);
312                    is_required |= match &param.pat {
313                        Pat::Ident(binding_ident) => !binding_ident.optional,
314                        Pat::Array(array_pat) => !array_pat.optional,
315                        Pat::Object(object_pat) => !object_pat.optional,
316                        Pat::Assign(_) | Pat::Invalid(_) | Pat::Expr(_) | Pat::Rest(_) => false,
317                        #[cfg(swc_ast_unknown)]
318                        _ => panic!("unable to access unknown nodes"),
319                    }
320                }
321                #[cfg(swc_ast_unknown)]
322                _ => panic!("unable to access unknown nodes"),
323            }
324        }
325        private_properties.reverse();
326        private_properties
327    }
328
329    pub(crate) fn transform_constructor_ts_param(
330        &mut self,
331        ts_param_prop: &mut TsParamProp,
332        is_required: bool,
333    ) -> Option<ClassMember> {
334        // We do almost same thing as self::transform_fn_param
335        // 1. Check assign pat type
336        if let TsParamPropParam::Assign(assign_pat) = &mut ts_param_prop.param {
337            if self.check_assign_pat_param(assign_pat) {
338                self.parameter_must_have_explicit_type(ts_param_prop.span);
339                return None;
340            }
341        }
342
343        // 2. Infer type annotation, and record whether there is an explicit '?'
344        let (is_optional, type_ann) = match &mut ts_param_prop.param {
345            TsParamPropParam::Ident(binding_ident) => {
346                if binding_ident.type_ann.is_none() {
347                    self.parameter_must_have_explicit_type(ts_param_prop.span);
348                }
349
350                (binding_ident.optional, &mut binding_ident.type_ann)
351            }
352            TsParamPropParam::Assign(assign_pat) => {
353                // Capture whether there was an explicit '?' on the left pattern.
354                let is_optional = match assign_pat.left.as_ref() {
355                    Pat::Ident(ident) => ident.optional,
356                    Pat::Array(array_pat) => array_pat.optional,
357                    Pat::Object(object_pat) => object_pat.optional,
358                    _ => false,
359                };
360
361                if !self.transform_assign_pat(assign_pat, is_required) {
362                    self.parameter_must_have_explicit_type(ts_param_prop.span);
363                }
364
365                (
366                    is_optional,
367                    match assign_pat.left.as_mut() {
368                        Pat::Ident(ident) => &mut ident.type_ann,
369                        Pat::Array(array_pat) => &mut array_pat.type_ann,
370                        Pat::Object(object_pat) => &mut object_pat.type_ann,
371                        Pat::Assign(_) | Pat::Rest(_) | Pat::Invalid(_) | Pat::Expr(_) => {
372                            return None
373                        }
374                        #[cfg(swc_ast_unknown)]
375                        _ => panic!("unable to access unknown nodes"),
376                    },
377                )
378            }
379            #[cfg(swc_ast_unknown)]
380            _ => panic!("unable to access unknown nodes"),
381        };
382
383        // 3. Add undefined type if needed: only when explicit '?' is present
384        if let Some(type_ann) = type_ann {
385            if is_optional && self.add_undefined_type_for_param(type_ann) {
386                self.implicitly_adding_undefined_to_type(ts_param_prop.span);
387            }
388        }
389
390        // 4. Flat param pat
391        if let Some(assign_pat) = ts_param_prop.param.as_assign() {
392            // A parameter property may not be declared using a binding pattern.
393            if let Some(ident) = assign_pat.left.as_ident() {
394                ts_param_prop.param = TsParamPropParam::Ident(ident.clone());
395            }
396        }
397
398        // Create private property
399        let ident = (match &ts_param_prop.param {
400            TsParamPropParam::Ident(binding_ident) => Some(binding_ident.id.clone()),
401            TsParamPropParam::Assign(assign_pat) => assign_pat
402                .left
403                .as_ident()
404                .map(|binding_ident| binding_ident.id.clone()),
405            #[cfg(swc_ast_unknown)]
406            _ => panic!("unable to access unknown nodes"),
407        })?;
408
409        let type_ann = if ts_param_prop
410            .accessibility
411            .is_some_and(|accessibility| accessibility == Accessibility::Private)
412        {
413            None
414        } else {
415            match &ts_param_prop.param {
416                TsParamPropParam::Ident(binding_ident) => binding_ident.type_ann.clone(),
417                TsParamPropParam::Assign(assign_pat) => match assign_pat.left.as_ref() {
418                    Pat::Ident(binding_ident) => binding_ident.type_ann.clone(),
419                    Pat::Array(array_pat) => array_pat.type_ann.clone(),
420                    Pat::Object(object_pat) => object_pat.type_ann.clone(),
421                    Pat::Assign(_) | Pat::Rest(_) | Pat::Invalid(_) | Pat::Expr(_) => None,
422                    #[cfg(swc_ast_unknown)]
423                    _ => panic!("unable to access unknown nodes"),
424                },
425                #[cfg(swc_ast_unknown)]
426                _ => panic!("unable to access unknown nodes"),
427            }
428        };
429
430        Some(ClassMember::ClassProp(ClassProp {
431            span: DUMMY_SP,
432            key: PropName::Ident(ident.into()),
433            value: None,
434            type_ann,
435            is_static: false,
436            decorators: Vec::new(),
437            accessibility: self.transform_accessibility(ts_param_prop.accessibility),
438            is_abstract: false,
439            is_optional,
440            is_override: ts_param_prop.is_override,
441            readonly: ts_param_prop.readonly,
442            declare: false,
443            definite: false,
444        }))
445    }
446
447    pub(crate) fn transform_class_property(&mut self, prop: &mut ClassProp) {
448        if prop.accessibility != Some(Accessibility::Private) {
449            if prop.type_ann.is_none() {
450                if let Some(value) = prop.value.as_ref() {
451                    if prop.readonly {
452                        if Self::need_to_infer_type_from_expression(value) {
453                            prop.type_ann = self.transform_expr_to_ts_type(value).map(type_ann);
454                            prop.value = None;
455                        } else if let Some(tpl) = value.as_tpl() {
456                            prop.value = self
457                                .tpl_to_string(tpl)
458                                .map(|s| Box::new(Expr::Lit(Lit::Str(s))));
459                        }
460                    } else {
461                        prop.type_ann = self.infer_type_from_expr(value).map(type_ann);
462                        prop.value = None;
463                    }
464                }
465            } else {
466                prop.value = None;
467            }
468
469            if prop.type_ann.is_none() && prop.value.is_none() {
470                self.property_must_have_explicit_type(prop.key.span());
471            }
472        } else {
473            prop.type_ann = None;
474            prop.value = None;
475        }
476
477        prop.declare = false;
478        prop.decorators.clear();
479        prop.accessibility = self.transform_accessibility(prop.accessibility);
480    }
481
482    pub(crate) fn transform_accessibility(
483        &mut self,
484        accessibility: Option<Accessibility>,
485    ) -> Option<Accessibility> {
486        if accessibility.is_none()
487            || accessibility.is_some_and(|accessibility| accessibility == Accessibility::Public)
488        {
489            None
490        } else {
491            accessibility
492        }
493    }
494
495    pub(crate) fn collect_getter_or_setter_annotations(
496        &mut self,
497        class: &Class,
498    ) -> FxHashMap<StaticProp, Box<TsTypeAnn>> {
499        let mut annotations = FxHashMap::default();
500        for member in &class.body {
501            let ClassMember::Method(method) = member else {
502                continue;
503            };
504
505            if method
506                .accessibility
507                .is_some_and(|accessibility| accessibility == Accessibility::Private)
508                || (method
509                    .key
510                    .as_computed()
511                    .is_some_and(|computed| Self::is_literal(&computed.expr)))
512            {
513                continue;
514            }
515
516            let Some(static_prop) = method.key.static_prop(self.unresolved_mark) else {
517                continue;
518            };
519
520            match method.kind {
521                MethodKind::Getter => {
522                    if let Some(type_ann) = method
523                        .function
524                        .return_type
525                        .clone()
526                        .or_else(|| self.infer_function_return_type(&method.function))
527                    {
528                        annotations.insert(static_prop, type_ann);
529                    }
530                }
531                MethodKind::Setter => {
532                    let Some(first_param) = method.function.params.first() else {
533                        continue;
534                    };
535
536                    if let Some(type_ann) = first_param.pat.get_type_ann() {
537                        annotations.insert(static_prop, type_ann.clone());
538                    }
539                }
540                _ => continue,
541            }
542        }
543
544        annotations
545    }
546
547    pub(crate) fn report_property_key(&mut self, key: &PropName) -> bool {
548        if let Some(computed) = key.as_computed() {
549            let is_symbol = self.is_global_symbol_object(&computed.expr);
550            let is_literal = Self::is_literal(&computed.expr);
551            if !is_symbol && !is_literal {
552                self.computed_property_name(key.span());
553            }
554            return !is_symbol && !is_literal;
555        }
556        false
557    }
558
559    pub(crate) fn is_global_symbol_object(&self, expr: &Expr) -> bool {
560        expr.get_global_symbol_prop(self.unresolved_mark).is_some()
561    }
562}