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}