diff --git a/crates/swc/src/lib.rs b/crates/swc/src/lib.rs index bb28020beeee9..006b7fc120a6d 100644 --- a/crates/swc/src/lib.rs +++ b/crates/swc/src/lib.rs @@ -988,10 +988,8 @@ impl Compiler { let issues = checker.transform(&mut program); for issue in issues { - let range = issue.range(); - handler - .struct_span_err(range.span, &issue.to_string()) + .struct_span_err(issue.range.span, &issue.message) .emit(); } diff --git a/crates/swc_typescript/src/diagnostic.rs b/crates/swc_typescript/src/diagnostic.rs index 45665fcd6ba9e..413e45585492b 100644 --- a/crates/swc_typescript/src/diagnostic.rs +++ b/crates/swc_typescript/src/diagnostic.rs @@ -1,35 +1,184 @@ -use std::sync::Arc; +//! References +//! * + +use std::{borrow::Cow, sync::Arc}; use swc_common::{FileName, Span}; +use crate::fast_dts::FastDts; + #[derive(Debug, Clone)] pub struct SourceRange { pub filename: Arc, pub span: Span, } -#[derive(Debug, Clone, thiserror::Error)] -pub enum DtsIssue { - #[error("unable to infer type from expression or declaration")] - UnableToInferType { range: SourceRange }, - #[error("unable to infer type, falling back to any type")] - UnableToInferTypeFallbackAny { range: SourceRange }, - #[error("unable to infer type from object property, skipping")] - UnableToInferTypeFromProp { range: SourceRange }, - #[error("unable to infer type from spread, skipping")] - UnableToInferTypeFromSpread { range: SourceRange }, - #[error("cannot infer type from using, skipping")] - UnsupportedUsing { range: SourceRange }, +#[derive(Debug, Clone)] +pub struct DtsIssue { + pub range: SourceRange, + pub message: Cow<'static, str>, } -impl DtsIssue { - pub fn range(&self) -> &SourceRange { - match self { - DtsIssue::UnableToInferType { range } => range, - DtsIssue::UnableToInferTypeFallbackAny { range } => range, - DtsIssue::UnableToInferTypeFromProp { range } => range, - DtsIssue::UnableToInferTypeFromSpread { range } => range, - DtsIssue::UnsupportedUsing { range } => range, - } +impl FastDts { + pub fn function_must_have_explicit_return_type(&mut self, span: Span) { + self.mark_diagnostic( + "TS9007: Function must have an explicit return type annotation with \ + --isolatedDeclarations.", + span, + ); + } + + pub fn method_must_have_explicit_return_type(&mut self, span: Span) { + self.mark_diagnostic( + "TS9008: Method must have an explicit return type annotation with \ + --isolatedDeclarations.", + span, + ); + } + + pub fn accessor_must_have_explicit_return_type(&mut self, span: Span) { + self.mark_diagnostic( + "TS9009: At least one accessor must have an explicit return type annotation with \ + --isolatedDeclarations.", + span, + ); + } + + pub fn variable_must_have_explicit_type(&mut self, span: Span) { + self.mark_diagnostic( + "TS9010: Variable must have an explicit type annotation with --isolatedDeclarations.", + span, + ); + } + + pub fn parameter_must_have_explicit_type(&mut self, span: Span) { + self.mark_diagnostic( + "TS9011: Parameter must have an explicit type annotation with --isolatedDeclarations.", + span, + ); + } + + pub fn property_must_have_explicit_type(&mut self, span: Span) { + self.mark_diagnostic( + "TS9012: Property must have an explicit type annotation with --isolatedDeclarations.", + span, + ); + } + + pub fn inferred_type_of_expression(&mut self, span: Span) { + self.mark_diagnostic( + "TS9013: Expression type can't be inferred with --isolatedDeclarations.", + span, + ); + } + + pub fn signature_computed_property_name(&mut self, span: Span) { + self.mark_diagnostic( + "TS9014: Computed properties must be number or string literals, variables or dotted \ + expressions with --isolatedDeclarations.", + span, + ); + } + + pub fn object_with_spread_assignments(&mut self, span: Span) { + self.mark_diagnostic( + "TS9015: Objects that contain spread assignments can't be inferred with \ + --isolatedDeclarations.", + span, + ); + } + + pub fn shorthand_property(&mut self, span: Span) { + self.mark_diagnostic( + "TS9016: Objects that contain shorthand properties can't be inferred with \ + --isolatedDeclarations.", + span, + ); + } + + pub fn array_inferred(&mut self, span: Span) { + self.mark_diagnostic( + "TS9017: Only const arrays can be inferred with --isolatedDeclarations.", + span, + ); + } + + pub fn arrays_with_spread_elements(&mut self, span: Span) { + self.mark_diagnostic( + "TS9018: Arrays with spread elements can't inferred with --isolatedDeclarations.", + span, + ); + } + + pub fn binding_element_export(&mut self, span: Span) { + self.mark_diagnostic( + "TS9019: Binding elements can't be exported directly with --isolatedDeclarations.", + span, + ); + } + + pub fn enum_member_initializers(&mut self, span: Span) { + self.mark_diagnostic( + "TS9020: Enum member initializers must be computable without references to external \ + symbols with --isolatedDeclarations.", + span, + ); + } + + pub fn extends_clause_expression(&mut self, span: Span) { + self.mark_diagnostic( + "TS9021: Extends clause can't contain an expression with --isolatedDeclarations.", + span, + ); + } + + pub fn inferred_type_of_class_expression(&mut self, span: Span) { + self.mark_diagnostic( + "TS9022: Inference from class expressions is not supported with \ + --isolatedDeclarations.", + span, + ); + } + + pub fn implicitly_adding_undefined_to_type(&mut self, span: Span) { + self.mark_diagnostic( + "TS9025: Declaration emit for this parameter requires implicitly adding undefined to \ + it's type. This is not supported with --isolatedDeclarations.", + span, + ); + } + + pub fn function_with_assigning_properties(&mut self, span: Span) { + self.mark_diagnostic( + "TS9023: Assigning properties to functions without declaring them is not supported \ + with --isolatedDeclarations. Add an explicit declaration for the properties assigned \ + to this function.", + span, + ); + } + + pub fn default_export_inferred(&mut self, span: Span) { + self.mark_diagnostic( + "TS9037: Default exports can't be inferred with --isolatedDeclarations.", + span, + ); + } + + pub fn computed_property_name(&mut self, span: Span) { + self.mark_diagnostic( + "TS9038: Computed property names on class or object literals cannot be inferred with \ + --isolatedDeclarations.", + span, + ); + } + + pub fn type_containing_private_name(&mut self, name: &str, span: Span) { + self.mark_diagnostic( + format!( + "TS9039: Type containing private name '{name}' can't be used with \ + --isolatedDeclarations." + ), + span, + ); } } diff --git a/crates/swc_typescript/src/fast_dts/class.rs b/crates/swc_typescript/src/fast_dts/class.rs new file mode 100644 index 0000000000000..1124bd4d382fe --- /dev/null +++ b/crates/swc_typescript/src/fast_dts/class.rs @@ -0,0 +1,109 @@ +use swc_common::util::take::Take; +use swc_ecma_ast::{Class, ClassMember, MethodKind}; + +use super::{any_type_ann, type_ann, valid_prop_name, FastDts}; + +impl FastDts { + pub(crate) fn transform_class(&mut self, class: &mut Class) { + // Track if the previous member was an overload signature or not. + // When overloads are present the last item has the implementation + // body. For declaration files the implementation always needs to + // be dropped. Needs to be unique for each class because another + // class could be created inside a class method. + let mut prev_is_overload = false; + + let new_body = class + .body + .take() + .into_iter() + .filter(|member| match member { + ClassMember::Constructor(class_constructor) => { + let is_overload = + class_constructor.body.is_none() && !class_constructor.is_optional; + if !prev_is_overload || is_overload { + prev_is_overload = is_overload; + true + } else { + prev_is_overload = false; + false + } + } + ClassMember::Method(method) => { + let is_overload = method.function.body.is_none() + && !(method.is_abstract || method.is_optional); + if !prev_is_overload || is_overload { + prev_is_overload = is_overload; + true + } else { + prev_is_overload = false; + false + } + } + ClassMember::TsIndexSignature(_) + | ClassMember::ClassProp(_) + | ClassMember::PrivateProp(_) + | ClassMember::Empty(_) + | ClassMember::StaticBlock(_) + | ClassMember::AutoAccessor(_) + | ClassMember::PrivateMethod(_) => { + prev_is_overload = false; + true + } + }) + .filter_map(|member| match member { + ClassMember::Constructor(mut class_constructor) => { + class_constructor.body = None; + self.handle_ts_param_props(&mut class_constructor.params); + Some(ClassMember::Constructor(class_constructor)) + } + ClassMember::Method(mut method) => { + if let Some(new_prop_name) = valid_prop_name(&method.key) { + method.key = new_prop_name; + } else { + return None; + } + + method.function.body = None; + if method.kind == MethodKind::Setter { + method.function.return_type = None; + } + self.transform_fn_params(&mut method.function.params); + Some(ClassMember::Method(method)) + } + ClassMember::ClassProp(mut prop) => { + if let Some(new_prop_name) = valid_prop_name(&prop.key) { + prop.key = new_prop_name; + } else { + return None; + } + + if prop.type_ann.is_none() { + if let Some(value) = prop.value { + prop.type_ann = self + .infer_type_from_expr(&value, false, false) + .map(type_ann) + .or_else(|| Some(any_type_ann())); + } + } + prop.value = None; + prop.definite = false; + prop.declare = false; + + Some(ClassMember::ClassProp(prop)) + } + ClassMember::TsIndexSignature(index_sig) => { + Some(ClassMember::TsIndexSignature(index_sig)) + } + + // These can be removed as they are not relevant for types + ClassMember::PrivateMethod(_) + | ClassMember::PrivateProp(_) + | ClassMember::Empty(_) + | ClassMember::StaticBlock(_) + | ClassMember::AutoAccessor(_) => None, + }) + .collect(); + + class.body = new_body; + } +} diff --git a/crates/swc_typescript/src/fast_dts/decl.rs b/crates/swc_typescript/src/fast_dts/decl.rs new file mode 100644 index 0000000000000..70b6c9c1b8bb4 --- /dev/null +++ b/crates/swc_typescript/src/fast_dts/decl.rs @@ -0,0 +1,96 @@ +use swc_common::Spanned; +use swc_ecma_ast::{Decl, DefaultDecl, Pat, TsNamespaceBody}; + +use super::{any_type_ann, type_ann, FastDts}; + +impl FastDts { + pub(crate) fn transform_decl(&mut self, decl: &mut Decl) { + // let is_declare = self.is_top_level; + let is_declare = true; + match decl { + Decl::Class(class_decl) => { + class_decl.declare = is_declare; + self.transform_class(&mut class_decl.class); + } + Decl::Fn(fn_decl) => { + fn_decl.declare = is_declare; + self.transform_fn(&mut fn_decl.function); + } + Decl::Var(_) | Decl::Using(_) => { + let decls = match decl { + Decl::Var(var_decl) => { + var_decl.declare = is_declare; + &mut var_decl.decls + } + Decl::Using(using_decl) => &mut using_decl.decls, + _ => todo!(), + }; + + for decl in decls.iter_mut() { + if let Pat::Ident(ident) = &mut decl.name { + if ident.type_ann.is_some() { + decl.init = None; + continue; + } + + let ts_type = decl + .init + .take() + .and_then(|init| self.infer_type_from_expr(&init, false, true)) + .map(type_ann) + .or_else(|| { + self.variable_must_have_explicit_type(ident.span()); + Some(any_type_ann()) + }); + ident.type_ann = ts_type; + } else { + self.binding_element_export(decl.name.span()); + } + + decl.init = None; + } + } + Decl::TsEnum(ts_enum) => { + ts_enum.declare = is_declare; + self.transform_enum(ts_enum.as_mut()); + } + Decl::TsModule(ts_module) => { + ts_module.declare = is_declare; + if let Some(body) = ts_module.body.as_mut() { + self.transform_ts_namespace_decl(body); + } + } + Decl::TsInterface(_) | Decl::TsTypeAlias(_) => {} + } + } + + pub(crate) fn transform_default_decl(&mut self, decl: &mut DefaultDecl) { + match decl { + DefaultDecl::Class(class_expr) => { + self.transform_class(&mut class_expr.class); + } + DefaultDecl::Fn(fn_expr) => { + fn_expr.function.body = None; + if fn_expr.function.return_type.is_none() { + // self.mark_diagnostic(DtsIssueKind::FunctionExplicitType, + // fn_expr.span()); + } + } + DefaultDecl::TsInterfaceDecl(_) => {} + }; + } + + pub(crate) fn transform_ts_namespace_decl(&mut self, body: &mut TsNamespaceBody) { + // let original_is_top_level = self.is_top_level; + // self.is_top_level = false; + match body { + TsNamespaceBody::TsModuleBlock(ts_module_block) => { + self.transform_module_items(&mut ts_module_block.body); + } + TsNamespaceBody::TsNamespaceDecl(ts_ns) => { + self.transform_ts_namespace_decl(&mut ts_ns.body) + } + }; + // self.is_top_level = original_is_top_level; + } +} diff --git a/crates/swc_typescript/src/fast_dts/enum.rs b/crates/swc_typescript/src/fast_dts/enum.rs new file mode 100644 index 0000000000000..02e53b462abe4 --- /dev/null +++ b/crates/swc_typescript/src/fast_dts/enum.rs @@ -0,0 +1,19 @@ +use swc_ecma_ast::TsEnumDecl; + +use super::FastDts; + +impl FastDts { + pub(crate) fn transform_enum(&mut self, decl: &mut TsEnumDecl) { + for member in &mut decl.members { + if let Some(init) = &member.init { + // Support for expressions is limited in enums, + // see https://www.typescriptlang.org/docs/handbook/enums.html + member.init = if self.valid_enum_init_expr(init) { + Some(init.clone()) + } else { + None + }; + } + } + } +} diff --git a/crates/swc_typescript/src/fast_dts/function.rs b/crates/swc_typescript/src/fast_dts/function.rs new file mode 100644 index 0000000000000..0ce80b4db0391 --- /dev/null +++ b/crates/swc_typescript/src/fast_dts/function.rs @@ -0,0 +1,179 @@ +use swc_atoms::Atom; +use swc_common::{util::take::Take, Spanned}; +use swc_ecma_ast::{ + AssignPat, BindingIdent, Decl, ExportDecl, Function, Module, ModuleDecl, ModuleItem, Param, + ParamOrTsParamProp, Pat, Script, Stmt, TsParamPropParam, +}; + +use super::FastDts; + +impl FastDts { + pub(crate) fn transform_fn(&mut self, func: &mut Function) { + self.transform_fn_return_type(func); + if func.return_type.is_none() { + self.function_must_have_explicit_return_type(func.span()); + } + self.transform_fn_params(&mut func.params); + func.body = None + } + + pub(crate) fn transform_fn_return_type(&self, func: &mut Function) { + if func.return_type.is_none() && !func.is_async && !func.is_generator { + // TODO: infer from function body + } + } + + pub(crate) fn transform_fn_params(&mut self, params: &mut Vec) { + for param in params { + self.transform_fn_param(param); + } + } + + pub(crate) fn transform_fn_param(&mut self, param: &mut Param) { + match &mut param.pat { + Pat::Ident(ident) => { + self.handle_func_param_ident(ident); + } + Pat::Assign(assign_pat) => { + if let Some(new_pat) = self.handle_func_param_assign(assign_pat) { + param.pat = new_pat; + } + } + Pat::Array(_) | Pat::Rest(_) | Pat::Object(_) | Pat::Invalid(_) | Pat::Expr(_) => {} + } + } + + pub(crate) fn handle_func_param_ident(&mut self, ident: &mut BindingIdent) { + if ident.type_ann.is_none() { + // self.mark_diagnostic_any_fallback(ident.span()); + // ident.type_ann = Some(any_type_ann()); + } + } + + pub(crate) fn handle_func_param_assign(&mut self, assign_pat: &mut AssignPat) -> Option { + match &mut *assign_pat.left { + Pat::Ident(ident) => { + if ident.type_ann.is_none() { + ident.type_ann = + self.infer_expr_fallback_any(assign_pat.right.take(), false, false); + } + + ident.optional = true; + Some(Pat::Ident(ident.clone())) + } + Pat::Array(arr_pat) => { + if arr_pat.type_ann.is_none() { + arr_pat.type_ann = + self.infer_expr_fallback_any(assign_pat.right.take(), false, false); + } + + arr_pat.optional = true; + Some(Pat::Array(arr_pat.clone())) + } + Pat::Object(obj_pat) => { + if obj_pat.type_ann.is_none() { + obj_pat.type_ann = + self.infer_expr_fallback_any(assign_pat.right.take(), false, false); + } + + obj_pat.optional = true; + Some(Pat::Object(obj_pat.clone())) + } + Pat::Rest(_) | Pat::Assign(_) | Pat::Expr(_) | Pat::Invalid(_) => None, + } + } + + pub(crate) fn handle_ts_param_props(&mut self, param_props: &mut Vec) { + for param in param_props { + match param { + ParamOrTsParamProp::TsParamProp(param) => { + match &mut param.param { + TsParamPropParam::Ident(ident) => { + self.handle_func_param_ident(ident); + } + TsParamPropParam::Assign(assign) => { + if let Some(new_pat) = self.handle_func_param_assign(assign) { + match new_pat { + Pat::Ident(new_ident) => { + param.param = TsParamPropParam::Ident(new_ident) + } + Pat::Assign(new_assign) => { + param.param = TsParamPropParam::Assign(new_assign) + } + Pat::Rest(_) + | Pat::Object(_) + | Pat::Array(_) + | Pat::Invalid(_) + | Pat::Expr(_) => { + // should never happen for parameter properties + unreachable!(); + } + } + } + } + } + } + ParamOrTsParamProp::Param(param) => self.transform_fn_param(param), + } + } + } + + pub(crate) fn remove_module_function_overloads(module: &mut Module) { + let mut last_function_name: Option = None; + let mut is_export_default_function_overloads = false; + + module.body.retain(|item| match item { + ModuleItem::Stmt(Stmt::Decl(Decl::Fn(fn_decl))) + | ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl { + decl: Decl::Fn(fn_decl), + .. + })) => { + if fn_decl.function.body.is_some() { + if last_function_name + .as_ref() + .is_some_and(|last_name| last_name == &fn_decl.ident.sym) + { + return false; + } + } else { + last_function_name = Some(fn_decl.ident.sym.clone()); + } + true + } + ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(export)) => { + if let Some(fn_expr) = export.decl.as_fn_expr() { + if is_export_default_function_overloads && fn_expr.function.body.is_some() { + is_export_default_function_overloads = false; + return false; + } else { + is_export_default_function_overloads = true; + true + } + } else { + is_export_default_function_overloads = false; + true + } + } + _ => true, + }); + } + + pub(crate) fn remove_script_function_overloads(script: &mut Script) { + let mut last_function_name: Option = None; + script.body.retain(|stmt| { + if let Some(fn_decl) = stmt.as_decl().and_then(|decl| decl.as_fn_decl()) { + if fn_decl.function.body.is_some() { + if last_function_name + .as_ref() + .is_some_and(|last_name| last_name == &fn_decl.ident.sym) + { + return false; + } + } else { + last_function_name = Some(fn_decl.ident.sym.clone()); + } + } + true + }); + } +} diff --git a/crates/swc_typescript/src/fast_dts/inferrer.rs b/crates/swc_typescript/src/fast_dts/inferrer.rs new file mode 100644 index 0000000000000..52e8d82b42ba9 --- /dev/null +++ b/crates/swc_typescript/src/fast_dts/inferrer.rs @@ -0,0 +1,255 @@ +use swc_common::{Spanned, DUMMY_SP}; +use swc_ecma_ast::{ + BindingIdent, Expr, Ident, Lit, Pat, Prop, PropName, PropOrSpread, TsFnOrConstructorType, + TsFnParam, TsFnType, TsKeywordType, TsKeywordTypeKind, TsPropertySignature, TsTupleElement, + TsTupleType, TsType, TsTypeElement, TsTypeLit, +}; + +use super::{ + any_type_ann, maybe_lit_to_ts_type, maybe_lit_to_ts_type_const, ts_readonly, ts_tuple_element, + type_ann, FastDts, +}; + +impl FastDts { + pub(crate) fn infer_type_from_expr( + &mut self, + e: &Expr, + as_const: bool, + as_readonly: bool, + ) -> Option> { + match e { + Expr::Array(arr) => { + let mut elem_types: Vec = Vec::new(); + + for elems in &arr.elems { + if let Some(expr_or_spread) = elems { + let span = expr_or_spread.span(); + if let Some(ts_expr) = + self.infer_type_from_expr(&expr_or_spread.expr, as_const, as_readonly) + { + elem_types.push(ts_tuple_element(ts_expr)); + } else { + self.array_inferred(span); + } + } else { + // TypeScript converts holey arrays to any + // Example: const a = [,,] -> const a = [any, any, any] + elem_types.push(ts_tuple_element(Box::new(TsType::TsKeywordType( + TsKeywordType { + kind: TsKeywordTypeKind::TsAnyKeyword, + span: DUMMY_SP, + }, + )))) + } + } + + let mut result = Box::new(TsType::TsTupleType(TsTupleType { + span: arr.span, + elem_types, + })); + + if as_readonly { + result = ts_readonly(result); + } + Some(result) + } + + Expr::Object(obj) => { + let mut members: Vec = Vec::new(); + + // TODO: Prescan all object properties to know which ones + // have a getter or a setter. This allows us to apply + // TypeScript's `readonly` keyword accordingly. + + for item in &obj.props { + match item { + PropOrSpread::Prop(prop) => { + match prop.as_ref() { + Prop::KeyValue(key_value) => { + let (key, computed) = match &key_value.key { + PropName::Ident(ident) => { + (Expr::Ident(ident.clone().into()), false) + } + PropName::Str(str_prop) => { + (Lit::Str(str_prop.clone()).into(), false) + } + PropName::Num(num) => (Lit::Num(num.clone()).into(), true), + PropName::Computed(computed) => { + (*computed.expr.clone(), true) + } + PropName::BigInt(big_int) => { + (Lit::BigInt(big_int.clone()).into(), true) + } + }; + + let init_type = self + .infer_type_from_expr( + &key_value.value, + as_const, + as_readonly, + ) + .map(type_ann); + + members.push(TsTypeElement::TsPropertySignature( + TsPropertySignature { + span: DUMMY_SP, + readonly: as_readonly, + key: Box::new(key), + computed, + optional: false, + type_ann: init_type, + }, + )); + } + Prop::Shorthand(_) + | Prop::Assign(_) + | Prop::Getter(_) + | Prop::Setter(_) + | Prop::Method(_) => { + // self.mark_diagnostic_unsupported_prop(prop.span()); + } + } + } + PropOrSpread::Spread(_) => { + // self.mark_diagnostic(DtsIssue::UnableToInferTypeFromSpread { + // range: + // self.source_range_to_range(item.span()), + // }) + } + } + } + + Some(Box::new(TsType::TsTypeLit(TsTypeLit { + span: obj.span, + members, + }))) + } + Expr::Lit(lit) => { + if as_const { + maybe_lit_to_ts_type_const(&lit) + } else { + maybe_lit_to_ts_type(&lit) + } + } + Expr::TsConstAssertion(ts_const) => { + self.infer_type_from_expr(&ts_const.expr, true, true) + } + Expr::TsSatisfies(satisifies) => { + self.infer_type_from_expr(&satisifies.expr, as_const, as_readonly) + } + Expr::TsAs(ts_as) => Some(ts_as.type_ann.clone()), + Expr::Fn(fn_expr) => { + let return_type = fn_expr + .function + .return_type + .as_ref() + .map_or(any_type_ann(), |val| val.clone()); + + let params: Vec = fn_expr + .function + .as_ref() + .params + .iter() + .filter_map(|param| self.pat_to_ts_fn_param(param.pat.clone())) + .collect(); + + Some(Box::new(TsType::TsFnOrConstructorType( + TsFnOrConstructorType::TsFnType(TsFnType { + span: fn_expr.function.span, + params, + type_ann: return_type, + type_params: fn_expr.function.type_params.clone(), + }), + ))) + } + Expr::Arrow(arrow_expr) => { + let return_type = arrow_expr + .return_type + .as_ref() + .map_or(any_type_ann(), |val| val.clone()); + + let params = arrow_expr + .params + .iter() + .filter_map(|pat| self.pat_to_ts_fn_param(pat.clone())) + .collect(); + + Some(Box::new(TsType::TsFnOrConstructorType( + TsFnOrConstructorType::TsFnType(TsFnType { + span: arrow_expr.span, + params, + type_ann: return_type, + type_params: arrow_expr.type_params.clone(), + }), + ))) + } + // Since fast check requires explicit type annotations these + // can be dropped as they are not part of an export declaration + Expr::This(_) + | Expr::Unary(_) + | Expr::Update(_) + | Expr::Bin(_) + | Expr::Assign(_) + | Expr::Member(_) + | Expr::SuperProp(_) + | Expr::Cond(_) + | Expr::Call(_) + | Expr::New(_) + | Expr::Seq(_) + | Expr::Ident(_) + | Expr::Tpl(_) + | Expr::TaggedTpl(_) + | Expr::Class(_) + | Expr::Yield(_) + | Expr::MetaProp(_) + | Expr::Await(_) + | Expr::Paren(_) + | Expr::JSXMember(_) + | Expr::JSXNamespacedName(_) + | Expr::JSXEmpty(_) + | Expr::JSXElement(_) + | Expr::JSXFragment(_) + | Expr::TsTypeAssertion(_) + | Expr::TsNonNull(_) + | Expr::TsInstantiation(_) + | Expr::PrivateName(_) + | Expr::OptChain(_) + | Expr::Invalid(_) => None, + } + } + + pub(crate) fn pat_to_ts_fn_param(&mut self, pat: Pat) -> Option { + match pat { + Pat::Ident(binding_id) => Some(TsFnParam::Ident(binding_id)), + Pat::Array(arr_pat) => Some(TsFnParam::Array(arr_pat)), + Pat::Rest(rest_pat) => Some(TsFnParam::Rest(rest_pat)), + Pat::Object(obj) => Some(TsFnParam::Object(obj)), + Pat::Assign(assign_pat) => self + .infer_type_from_expr(&assign_pat.right, false, false) + .map(|param| { + let name = if let Pat::Ident(ident) = *assign_pat.left { + ident.sym.clone() + } else { + self.gen_unique_name("p") + }; + + TsFnParam::Ident(BindingIdent { + id: Ident { + span: assign_pat.span, + ctxt: Default::default(), + sym: name, + optional: false, + }, + type_ann: Some(type_ann(param)), + }) + }), + Pat::Expr(_expr) => { + // self.mark_diagnostic_unable_to_infer(expr.span()); + None + } + // Invalid code is invalid, not sure why SWC doesn't throw + // a parse error here. + Pat::Invalid(_) => None, + } + } +} diff --git a/crates/swc_typescript/src/fast_dts/mod.rs b/crates/swc_typescript/src/fast_dts/mod.rs index 0ebe2c5ef2132..0ae24dd69bb37 100644 --- a/crates/swc_typescript/src/fast_dts/mod.rs +++ b/crates/swc_typescript/src/fast_dts/mod.rs @@ -1,21 +1,23 @@ -use std::{mem::take, sync::Arc}; +use std::{borrow::Cow, mem::take, sync::Arc}; use swc_atoms::Atom; -use swc_common::{util::take::Take, FileName, Span, Spanned, DUMMY_SP}; +use swc_common::{FileName, Span, Spanned, DUMMY_SP}; use swc_ecma_ast::{ - AssignPat, BindingIdent, ClassMember, ComputedPropName, Decl, DefaultDecl, ExportDecl, - ExportDefaultExpr, Expr, Ident, Lit, MethodKind, ModuleDecl, ModuleItem, OptChainBase, Param, - ParamOrTsParamProp, Pat, Program, Prop, PropName, PropOrSpread, Stmt, TsEntityName, - TsFnOrConstructorType, TsFnParam, TsFnType, TsKeywordType, TsKeywordTypeKind, TsLit, TsLitType, - TsNamespaceBody, TsParamPropParam, TsPropertySignature, TsTupleElement, TsTupleType, TsType, - TsTypeAnn, TsTypeElement, TsTypeLit, TsTypeOperator, TsTypeOperatorOp, TsTypeRef, VarDecl, - VarDeclKind, VarDeclarator, + BindingIdent, ComputedPropName, ExportDefaultExpr, Expr, Ident, Lit, ModuleDecl, ModuleItem, + OptChainBase, Pat, Program, PropName, TsEntityName, TsKeywordType, TsKeywordTypeKind, TsLit, + TsLitType, TsTupleElement, TsType, TsTypeAnn, TsTypeOperator, TsTypeOperatorOp, TsTypeRef, + VarDecl, VarDeclKind, VarDeclarator, }; use swc_ecma_visit::{VisitMutWith, VisitWith}; use type_usage::{TypeRemover, TypeUsageAnalyzer}; use crate::diagnostic::{DtsIssue, SourceRange}; +mod class; +mod decl; +mod r#enum; +mod function; +mod inferrer; mod type_usage; /// TypeScript Isolated Declaration support. @@ -29,9 +31,9 @@ mod type_usage; /// The original code is MIT licensed. pub struct FastDts { filename: Arc, - is_top_level: bool, - id_counter: u32, diagnostics: Vec, + id_counter: u32, + // TODO: strip_internal: bool, } /// Diagnostics @@ -39,70 +41,43 @@ impl FastDts { pub fn new(filename: Arc) -> Self { Self { filename, - is_top_level: false, - id_counter: 0, diagnostics: Vec::new(), + id_counter: 0, } } - fn mark_diagnostic(&mut self, diagnostic: DtsIssue) { - self.diagnostics.push(diagnostic) - } - - fn source_range_to_range(&self, range: Span) -> SourceRange { - SourceRange { - filename: self.filename.clone(), - span: range, - } - } - - fn mark_diagnostic_unable_to_infer(&mut self, range: Span) { - self.mark_diagnostic(DtsIssue::UnableToInferType { - range: self.source_range_to_range(range), - }) - } - - fn mark_diagnostic_any_fallback(&mut self, range: Span) { - self.mark_diagnostic(DtsIssue::UnableToInferTypeFallbackAny { - range: self.source_range_to_range(range), - }) - } - - fn mark_diagnostic_unsupported_prop(&mut self, range: Span) { - self.mark_diagnostic(DtsIssue::UnableToInferTypeFromProp { - range: self.source_range_to_range(range), + pub fn mark_diagnostic>>(&mut self, message: T, range: Span) { + self.diagnostics.push(DtsIssue { + message: message.into(), + range: SourceRange { + filename: self.filename.clone(), + span: range, + }, }) } } impl FastDts { pub fn transform(&mut self, program: &mut Program) -> Vec { - self.is_top_level = true; - + // 1. Transform. We only keep decls. match program { Program::Module(module) => { + module + .body + .retain(|item| item.as_stmt().map(|stmt| stmt.is_decl()).unwrap_or(true)); + Self::remove_module_function_overloads(module); self.transform_module_items(&mut module.body); } Program::Script(script) => { - let mut last_function_name: Option = None; - script.body.retain_mut(|stmt| { - if let Some(fn_decl) = stmt.as_decl().and_then(|decl| decl.as_fn_decl()) { - if fn_decl.function.body.is_some() { - if last_function_name - .as_ref() - .is_some_and(|last_name| last_name == &fn_decl.ident.sym) - { - return false; - } - } else { - last_function_name = Some(fn_decl.ident.sym.clone()); - } - } - self.transform_module_stmt(stmt) - }) + script.body.retain(|stmt| stmt.is_decl()); + Self::remove_script_function_overloads(script); + for stmt in script.body.iter_mut() { + self.transform_decl(stmt.as_mut_decl().unwrap()); + } } } + // 2. Remove unused imports and decls let mut type_usage_analyzer = TypeUsageAnalyzer::default(); program.visit_with(&mut type_usage_analyzer); program.visit_mut_with(&mut TypeRemover::new( @@ -117,12 +92,8 @@ impl FastDts { let orig_items = take(items); let mut new_items = Vec::with_capacity(orig_items.len()); - let mut last_function_name: Option = None; - let mut is_export_default_function_overloads = false; - for mut item in orig_items { match &mut item { - // Keep all these ModuleItem::ModuleDecl( ModuleDecl::Import(..) | ModuleDecl::TsImportEquals(_) @@ -131,439 +102,59 @@ impl FastDts { | ModuleDecl::ExportNamed(_) | ModuleDecl::ExportAll(_), ) => new_items.push(item), - ModuleItem::Stmt(stmt) => { - if let Some(fn_decl) = stmt.as_decl().and_then(|decl| decl.as_fn_decl()) { - if fn_decl.function.body.is_some() { - if last_function_name - .as_ref() - .is_some_and(|last_name| last_name == &fn_decl.ident.sym) - { - continue; - } - } else { - last_function_name = Some(fn_decl.ident.sym.clone()); - } - } - - if self.transform_module_stmt(stmt) { - new_items.push(item); - } + self.transform_decl(stmt.as_mut_decl().unwrap()); + new_items.push(item); } - - ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl { - span, decl, .. - })) => { - if let Some(fn_decl) = decl.as_fn_decl() { - if fn_decl.function.body.is_some() { - if last_function_name - .as_ref() - .is_some_and(|last_name| last_name == &fn_decl.ident.sym) - { - continue; - } - } else { - last_function_name = Some(fn_decl.ident.sym.clone()); - } - } - - if let Some(()) = self.decl_to_type_decl(decl) { - new_items.push( - ExportDecl { - decl: decl.take(), - span: *span, - } - .into(), - ); - } else { - self.mark_diagnostic(DtsIssue::UnableToInferType { - range: self.source_range_to_range(*span), - }) - } + ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(expor_decl)) => { + self.transform_decl(&mut expor_decl.decl); + new_items.push(item); } - ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(export)) => { - if let Some(fn_expr) = export.decl.as_fn_expr() { - if is_export_default_function_overloads && fn_expr.function.body.is_some() { - is_export_default_function_overloads = false; - continue; - } else { - is_export_default_function_overloads = true; - } - } else { - is_export_default_function_overloads = false; - } - - match &mut export.decl { - DefaultDecl::Class(class_expr) => { - self.class_body_to_type(&mut class_expr.class.body); - } - DefaultDecl::Fn(fn_expr) => { - fn_expr.function.body = None; - } - DefaultDecl::TsInterfaceDecl(_) => {} - }; - + self.transform_default_decl(&mut export.decl); new_items.push(item); } - ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(export)) => { - let name = self.gen_unique_name(); - let name_ident = Ident::new_no_ctxt(name, DUMMY_SP); + let name_ident = Ident::new_no_ctxt(self.gen_unique_name("_default"), DUMMY_SP); let type_ann = self - .expr_to_ts_type(export.expr.clone(), false, true) + .infer_type_from_expr(&export.expr, false, true) .map(type_ann); - if let Some(type_ann) = type_ann { - new_items.push( - VarDecl { - span: DUMMY_SP, - kind: VarDeclKind::Const, - declare: true, - decls: vec![VarDeclarator { - span: DUMMY_SP, - name: Pat::Ident(BindingIdent { - id: name_ident.clone(), - - type_ann: Some(type_ann), - }), - init: None, - definite: false, - }], - ..Default::default() - } - .into(), - ); - - new_items.push( - ExportDefaultExpr { - span: export.span, - expr: name_ident.into(), - } - .into(), - ) - } else { - new_items.push( - ExportDefaultExpr { - span: export.span, - expr: export.expr.take(), - } - .into(), - ) + if type_ann.is_none() { + self.default_export_inferred(export.expr.span()); } - } - } - } - - *items = new_items; - } - fn transform_module_stmt(&mut self, stmt: &mut Stmt) -> bool { - let Stmt::Decl(ref mut decl) = stmt else { - return false; - }; - - match decl { - Decl::TsEnum(_) | Decl::Class(_) | Decl::Fn(_) | Decl::Var(_) | Decl::TsModule(_) => { - if let Some(()) = self.decl_to_type_decl(decl) { - true - } else { - self.mark_diagnostic_unable_to_infer(decl.span()); - false - } - } - - Decl::TsInterface(_) | Decl::TsTypeAlias(_) | Decl::Using(_) => true, - } - } - - fn expr_to_ts_type( - &mut self, - e: Box, - as_const: bool, - as_readonly: bool, - ) -> Option> { - match *e { - Expr::Array(arr) => { - let mut elem_types: Vec = Vec::new(); - - for elems in arr.elems { - if let Some(expr_or_spread) = elems { - let span = expr_or_spread.span(); - if let Some(ts_expr) = - self.expr_to_ts_type(expr_or_spread.expr, as_const, as_readonly) - { - elem_types.push(ts_tuple_element(ts_expr)); - } else { - self.mark_diagnostic_unable_to_infer(span); - } - } else { - // TypeScript converts holey arrays to any - // Example: const a = [,,] -> const a = [any, any, any] - elem_types.push(ts_tuple_element(Box::new(TsType::TsKeywordType( - TsKeywordType { - kind: TsKeywordTypeKind::TsAnyKeyword, + new_items.push( + VarDecl { + span: DUMMY_SP, + kind: VarDeclKind::Const, + declare: true, + decls: vec![VarDeclarator { span: DUMMY_SP, - }, - )))) - } - } - - let mut result = Box::new(TsType::TsTupleType(TsTupleType { - span: arr.span, - elem_types, - })); - - if as_readonly { - result = ts_readonly(result); - } - Some(result) - } - - Expr::Object(obj) => { - let mut members: Vec = Vec::new(); - - // TODO: Prescan all object properties to know which ones - // have a getter or a setter. This allows us to apply - // TypeScript's `readonly` keyword accordingly. - - for item in obj.props { - match item { - PropOrSpread::Prop(prop_box) => { - let prop = *prop_box; - match prop { - Prop::KeyValue(key_value) => { - let (key, computed) = match key_value.key { - PropName::Ident(ident) => { - (Expr::Ident(ident.into()), false) - } - PropName::Str(str_prop) => { - (Lit::Str(str_prop).into(), false) - } - PropName::Num(num) => (Lit::Num(num).into(), true), - PropName::Computed(computed) => (*computed.expr, true), - PropName::BigInt(big_int) => { - (Lit::BigInt(big_int).into(), true) - } - }; - - let init_type = self - .expr_to_ts_type(key_value.value, as_const, as_readonly) - .map(type_ann); - - members.push(TsTypeElement::TsPropertySignature( - TsPropertySignature { - span: DUMMY_SP, - readonly: as_readonly, - key: Box::new(key), - computed, - optional: false, - type_ann: init_type, - }, - )); - } - Prop::Shorthand(_) - | Prop::Assign(_) - | Prop::Getter(_) - | Prop::Setter(_) - | Prop::Method(_) => { - self.mark_diagnostic_unsupported_prop(prop.span()); - } - } + name: Pat::Ident(BindingIdent { + id: name_ident.clone(), + type_ann, + }), + init: None, + definite: false, + }], + ..Default::default() } - PropOrSpread::Spread(_) => { - self.mark_diagnostic(DtsIssue::UnableToInferTypeFromSpread { - range: self.source_range_to_range(item.span()), - }) - } - } - } - - Some(Box::new(TsType::TsTypeLit(TsTypeLit { - span: obj.span, - members, - }))) - } - Expr::Lit(lit) => { - if as_const { - maybe_lit_to_ts_type_const(&lit) - } else { - maybe_lit_to_ts_type(&lit) - } - } - Expr::TsConstAssertion(ts_const) => self.expr_to_ts_type(ts_const.expr, true, true), - Expr::TsSatisfies(satisifies) => { - self.expr_to_ts_type(satisifies.expr, as_const, as_readonly) - } - Expr::TsAs(ts_as) => Some(ts_as.type_ann), - Expr::Fn(fn_expr) => { - let return_type = fn_expr - .function - .return_type - .map_or(any_type_ann(), |val| val); - - let params: Vec = fn_expr - .function - .params - .into_iter() - .filter_map(|param| self.pat_to_ts_fn_param(param.pat)) - .collect(); - - Some(Box::new(TsType::TsFnOrConstructorType( - TsFnOrConstructorType::TsFnType(TsFnType { - span: fn_expr.function.span, - params, - type_ann: return_type, - type_params: fn_expr.function.type_params, - }), - ))) - } - Expr::Arrow(arrow_expr) => { - let return_type = arrow_expr.return_type.map_or(any_type_ann(), |val| val); - - let params = arrow_expr - .params - .into_iter() - .filter_map(|pat| self.pat_to_ts_fn_param(pat)) - .collect(); + .into(), + ); - Some(Box::new(TsType::TsFnOrConstructorType( - TsFnOrConstructorType::TsFnType(TsFnType { - span: arrow_expr.span, - params, - type_ann: return_type, - type_params: arrow_expr.type_params, - }), - ))) - } - // Since fast check requires explicit type annotations these - // can be dropped as they are not part of an export declaration - Expr::This(_) - | Expr::Unary(_) - | Expr::Update(_) - | Expr::Bin(_) - | Expr::Assign(_) - | Expr::Member(_) - | Expr::SuperProp(_) - | Expr::Cond(_) - | Expr::Call(_) - | Expr::New(_) - | Expr::Seq(_) - | Expr::Ident(_) - | Expr::Tpl(_) - | Expr::TaggedTpl(_) - | Expr::Class(_) - | Expr::Yield(_) - | Expr::MetaProp(_) - | Expr::Await(_) - | Expr::Paren(_) - | Expr::JSXMember(_) - | Expr::JSXNamespacedName(_) - | Expr::JSXEmpty(_) - | Expr::JSXElement(_) - | Expr::JSXFragment(_) - | Expr::TsTypeAssertion(_) - | Expr::TsNonNull(_) - | Expr::TsInstantiation(_) - | Expr::PrivateName(_) - | Expr::OptChain(_) - | Expr::Invalid(_) => None, - } - } - - fn decl_to_type_decl(&mut self, decl: &mut Decl) -> Option<()> { - let is_declare = self.is_top_level; - match decl { - Decl::Class(class_decl) => { - self.class_body_to_type(&mut class_decl.class.body); - class_decl.declare = is_declare; - Some(()) - } - Decl::Fn(fn_decl) => { - fn_decl.function.body = None; - fn_decl.declare = is_declare; - self.handle_func_params(&mut fn_decl.function.params); - Some(()) - } - Decl::Var(var_decl) => { - var_decl.declare = is_declare; - - for decl in &mut var_decl.decls { - if let Pat::Ident(ident) = &mut decl.name { - if ident.type_ann.is_some() { - decl.init = None; - continue; + new_items.push( + ExportDefaultExpr { + span: export.span, + expr: name_ident.into(), } - - let ts_type = decl - .init - .take() - .and_then(|init| self.expr_to_ts_type(init, false, true)) - .map(type_ann) - .or_else(|| { - self.mark_diagnostic_any_fallback(ident.span()); - Some(any_type_ann()) - }); - ident.type_ann = ts_type; - } else { - self.mark_diagnostic_unable_to_infer(decl.span()); - } - - decl.init = None; - } - - Some(()) - } - Decl::TsEnum(ts_enum) => { - ts_enum.declare = is_declare; - - for member in &mut ts_enum.members { - if let Some(init) = &member.init { - // Support for expressions is limited in enums, - // see https://www.typescriptlang.org/docs/handbook/enums.html - member.init = if self.valid_enum_init_expr(init) { - Some(init.clone()) - } else { - None - }; - } + .into(), + ) } - - Some(()) - } - Decl::TsModule(ts_module) => { - ts_module.declare = is_declare; - - if let Some(body) = ts_module.body.take() { - ts_module.body = Some(self.transform_ts_ns_body(body)); - - Some(()) - } else { - Some(()) - } - } - Decl::TsInterface(_) | Decl::TsTypeAlias(_) => Some(()), - Decl::Using(_) => { - self.mark_diagnostic(DtsIssue::UnsupportedUsing { - range: self.source_range_to_range(decl.span()), - }); - None } } - } - fn transform_ts_ns_body(&mut self, ns: TsNamespaceBody) -> TsNamespaceBody { - let original_is_top_level = self.is_top_level; - self.is_top_level = false; - let body = match ns { - TsNamespaceBody::TsModuleBlock(mut ts_module_block) => { - self.transform_module_items(&mut ts_module_block.body); - TsNamespaceBody::TsModuleBlock(ts_module_block) - } - TsNamespaceBody::TsNamespaceDecl(ts_ns) => self.transform_ts_ns_body(*ts_ns.body), - }; - self.is_top_level = original_is_top_level; - body + *items = new_items; } // Support for expressions is limited in enums, @@ -705,252 +296,17 @@ impl FastDts { as_const: bool, as_readonly: bool, ) -> Option> { - let span = expr.span(); - - if let Some(ts_type) = self.expr_to_ts_type(expr, as_const, as_readonly) { + if let Some(ts_type) = self.infer_type_from_expr(&expr, as_const, as_readonly) { Some(type_ann(ts_type)) } else { - self.mark_diagnostic_any_fallback(span); + // self.mark_diagnostic_any_fallback(span); Some(any_type_ann()) } } - fn class_body_to_type(&mut self, body: &mut Vec) { - // Track if the previous member was an overload signature or not. - // When overloads are present the last item has the implementation - // body. For declaration files the implementation always needs to - // be dropped. Needs to be unique for each class because another - // class could be created inside a class method. - let mut prev_is_overload = false; - - let new_body = body - .take() - .into_iter() - .filter(|member| match member { - ClassMember::Constructor(class_constructor) => { - let is_overload = - class_constructor.body.is_none() && !class_constructor.is_optional; - if !prev_is_overload || is_overload { - prev_is_overload = is_overload; - true - } else { - prev_is_overload = false; - false - } - } - ClassMember::Method(method) => { - let is_overload = method.function.body.is_none() - && !(method.is_abstract || method.is_optional); - if !prev_is_overload || is_overload { - prev_is_overload = is_overload; - true - } else { - prev_is_overload = false; - false - } - } - ClassMember::TsIndexSignature(_) - | ClassMember::ClassProp(_) - | ClassMember::PrivateProp(_) - | ClassMember::Empty(_) - | ClassMember::StaticBlock(_) - | ClassMember::AutoAccessor(_) - | ClassMember::PrivateMethod(_) => { - prev_is_overload = false; - true - } - }) - .filter_map(|member| match member { - ClassMember::Constructor(mut class_constructor) => { - class_constructor.body = None; - self.handle_ts_param_props(&mut class_constructor.params); - Some(ClassMember::Constructor(class_constructor)) - } - ClassMember::Method(mut method) => { - if let Some(new_prop_name) = valid_prop_name(&method.key) { - method.key = new_prop_name; - } else { - return None; - } - - method.function.body = None; - if method.kind == MethodKind::Setter { - method.function.return_type = None; - } - self.handle_func_params(&mut method.function.params); - Some(ClassMember::Method(method)) - } - ClassMember::ClassProp(mut prop) => { - if let Some(new_prop_name) = valid_prop_name(&prop.key) { - prop.key = new_prop_name; - } else { - return None; - } - - if prop.type_ann.is_none() { - if let Some(value) = prop.value { - prop.type_ann = self - .expr_to_ts_type(value, false, false) - .map(type_ann) - .or_else(|| Some(any_type_ann())); - } - } - prop.value = None; - prop.definite = false; - prop.declare = false; - - Some(ClassMember::ClassProp(prop)) - } - ClassMember::TsIndexSignature(index_sig) => { - Some(ClassMember::TsIndexSignature(index_sig)) - } - - // These can be removed as they are not relevant for types - ClassMember::PrivateMethod(_) - | ClassMember::PrivateProp(_) - | ClassMember::Empty(_) - | ClassMember::StaticBlock(_) - | ClassMember::AutoAccessor(_) => None, - }) - .collect(); - - *body = new_body; - } - - fn handle_ts_param_props(&mut self, param_props: &mut Vec) { - for param in param_props { - match param { - ParamOrTsParamProp::TsParamProp(param) => { - match &mut param.param { - TsParamPropParam::Ident(ident) => { - self.handle_func_param_ident(ident); - } - TsParamPropParam::Assign(assign) => { - if let Some(new_pat) = self.handle_func_param_assign(assign) { - match new_pat { - Pat::Ident(new_ident) => { - param.param = TsParamPropParam::Ident(new_ident) - } - Pat::Assign(new_assign) => { - param.param = TsParamPropParam::Assign(new_assign) - } - Pat::Rest(_) - | Pat::Object(_) - | Pat::Array(_) - | Pat::Invalid(_) - | Pat::Expr(_) => { - // should never happen for parameter properties - unreachable!(); - } - } - } - } - } - } - ParamOrTsParamProp::Param(param) => self.handle_func_param(param), - } - } - } - - fn handle_func_params(&mut self, params: &mut Vec) { - for param in params { - self.handle_func_param(param); - } - } - - fn handle_func_param(&mut self, param: &mut Param) { - match &mut param.pat { - Pat::Ident(ident) => { - self.handle_func_param_ident(ident); - } - Pat::Assign(assign_pat) => { - if let Some(new_pat) = self.handle_func_param_assign(assign_pat) { - param.pat = new_pat; - } - } - Pat::Array(_) | Pat::Rest(_) | Pat::Object(_) | Pat::Invalid(_) | Pat::Expr(_) => {} - } - } - - fn handle_func_param_ident(&mut self, ident: &mut BindingIdent) { - if ident.type_ann.is_none() { - self.mark_diagnostic_any_fallback(ident.span()); - ident.type_ann = Some(any_type_ann()); - } - } - - fn handle_func_param_assign(&mut self, assign_pat: &mut AssignPat) -> Option { - match &mut *assign_pat.left { - Pat::Ident(ident) => { - if ident.type_ann.is_none() { - ident.type_ann = - self.infer_expr_fallback_any(assign_pat.right.take(), false, false); - } - - ident.optional = true; - Some(Pat::Ident(ident.clone())) - } - Pat::Array(arr_pat) => { - if arr_pat.type_ann.is_none() { - arr_pat.type_ann = - self.infer_expr_fallback_any(assign_pat.right.take(), false, false); - } - - arr_pat.optional = true; - Some(Pat::Array(arr_pat.clone())) - } - Pat::Object(obj_pat) => { - if obj_pat.type_ann.is_none() { - obj_pat.type_ann = - self.infer_expr_fallback_any(assign_pat.right.take(), false, false); - } - - obj_pat.optional = true; - Some(Pat::Object(obj_pat.clone())) - } - Pat::Rest(_) | Pat::Assign(_) | Pat::Expr(_) | Pat::Invalid(_) => None, - } - } - - fn pat_to_ts_fn_param(&mut self, pat: Pat) -> Option { - match pat { - Pat::Ident(binding_id) => Some(TsFnParam::Ident(binding_id)), - Pat::Array(arr_pat) => Some(TsFnParam::Array(arr_pat)), - Pat::Rest(rest_pat) => Some(TsFnParam::Rest(rest_pat)), - Pat::Object(obj) => Some(TsFnParam::Object(obj)), - Pat::Assign(assign_pat) => { - self.expr_to_ts_type(assign_pat.right, false, false) - .map(|param| { - let name = if let Pat::Ident(ident) = *assign_pat.left { - ident.sym.clone() - } else { - self.gen_unique_name() - }; - - TsFnParam::Ident(BindingIdent { - id: Ident { - span: assign_pat.span, - ctxt: Default::default(), - sym: name, - optional: false, - }, - type_ann: Some(type_ann(param)), - }) - }) - } - Pat::Expr(expr) => { - self.mark_diagnostic_unable_to_infer(expr.span()); - None - } - // Invalid code is invalid, not sure why SWC doesn't throw - // a parse error here. - Pat::Invalid(_) => None, - } - } - - fn gen_unique_name(&mut self) -> Atom { + fn gen_unique_name(&mut self, name: &str) -> Atom { self.id_counter += 1; - format!("_dts_{}", self.id_counter).into() + format!("{name}_{}", self.id_counter).into() } }