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}