diff --git a/crates/oxc_ast/src/ast/ts.rs b/crates/oxc_ast/src/ast/ts.rs index facf842c1..f7636c56a 100644 --- a/crates/oxc_ast/src/ast/ts.rs +++ b/crates/oxc_ast/src/ast/ts.rs @@ -1335,6 +1335,12 @@ impl<'a> Modifiers<'a> { list.retain(|m| !m.kind.is_typescript_syntax()); } } + + pub fn add_modifier(&mut self, modifier: Modifier) { + if let Some(list) = self.0.as_mut() { + list.push(modifier); + } + } } /// Export Assignment in non-module files diff --git a/crates/oxc_transformer_dts/src/class.rs b/crates/oxc_transformer_dts/src/class.rs index 3e6b57953..f1aa8d33d 100644 --- a/crates/oxc_transformer_dts/src/class.rs +++ b/crates/oxc_transformer_dts/src/class.rs @@ -242,14 +242,9 @@ impl<'a> TransformerDts<'a> { let body = self.ctx.ast.class_body(decl.body.span, elements); - let modifiers = if decl.modifiers.is_contains_abstract() { - let modifiers = self.ctx.ast.new_vec_from_iter([ - Modifier { span: SPAN, kind: ModifierKind::Declare }, - Modifier { span: SPAN, kind: ModifierKind::Abstract }, - ]); - Modifiers::new(modifiers) - } else { - self.modifiers_declare() + let mut modifiers = self.modifiers_declare(); + if decl.modifiers.is_contains_abstract() { + modifiers.add_modifier(Modifier { span: SPAN, kind: ModifierKind::Abstract }); }; Some(self.ctx.ast.class( diff --git a/crates/oxc_transformer_dts/src/declaration.rs b/crates/oxc_transformer_dts/src/declaration.rs index 4799d4a09..354212995 100644 --- a/crates/oxc_transformer_dts/src/declaration.rs +++ b/crates/oxc_transformer_dts/src/declaration.rs @@ -2,8 +2,10 @@ use oxc_ast::ast::*; use oxc_allocator::Box; +use oxc_ast::Visit; use oxc_diagnostics::OxcDiagnostic; use oxc_span::{GetSpan, SPAN}; +use oxc_syntax::scope::ScopeFlags; use crate::TransformerDts; @@ -116,6 +118,53 @@ impl<'a> TransformerDts<'a> { ) } + fn transform_ts_module_block( + &mut self, + block: &Box<'a, TSModuleBlock<'a>>, + ) -> Box<'a, TSModuleBlock<'a>> { + // We need to enter a new scope for the module block, avoid add binding to the parent scope + self.scope.enter_scope(ScopeFlags::TsModuleBlock); + let stmts = self.transform_statements_on_demand(&block.body); + self.scope.leave_scope(); + self.ctx.ast.ts_module_block(SPAN, stmts) + } + + pub fn transform_ts_module_declaration( + &mut self, + decl: &Box<'a, TSModuleDeclaration<'a>>, + ) -> Box<'a, TSModuleDeclaration<'a>> { + if decl.modifiers.is_contains_declare() { + return self.ctx.ast.copy(decl); + } + + let Some(body) = &decl.body else { + return self.ctx.ast.copy(decl); + }; + + match body { + TSModuleDeclarationBody::TSModuleDeclaration(decl) => { + let inner = self.transform_ts_module_declaration(decl); + return self.ctx.ast.ts_module_declaration( + decl.span, + self.ctx.ast.copy(&decl.id), + Some(TSModuleDeclarationBody::TSModuleDeclaration(inner)), + decl.kind, + self.modifiers_declare(), + ); + } + TSModuleDeclarationBody::TSModuleBlock(block) => { + let body = self.transform_ts_module_block(block); + return self.ctx.ast.ts_module_declaration( + decl.span, + self.ctx.ast.copy(&decl.id), + Some(TSModuleDeclarationBody::TSModuleBlock(body)), + decl.kind, + self.modifiers_declare(), + ); + } + } + } + pub fn transform_declaration( &mut self, decl: &Declaration<'a>, @@ -175,7 +224,9 @@ impl<'a> TransformerDts<'a> { if self.scope.has_reference(&ident.name) ) { - Some(Declaration::TSModuleDeclaration(self.ctx.ast.copy(decl))) + Some(Declaration::TSModuleDeclaration( + self.transform_ts_module_declaration(decl), + )) } else { None } diff --git a/crates/oxc_transformer_dts/src/inferrer.rs b/crates/oxc_transformer_dts/src/inferrer.rs index 71c3aa158..622990874 100644 --- a/crates/oxc_transformer_dts/src/inferrer.rs +++ b/crates/oxc_transformer_dts/src/inferrer.rs @@ -4,7 +4,7 @@ use oxc_ast::ast::{ TSType, TSTypeAnnotation, }; use oxc_diagnostics::OxcDiagnostic; -use oxc_span::SPAN; +use oxc_span::{GetSpan, SPAN}; use crate::{return_type::FunctionReturnType, TransformerDts}; @@ -78,7 +78,10 @@ impl<'a> TransformerDts<'a> { } else { if let Expression::TSAsExpression(expr) = &pattern.right { if !expr.type_annotation.is_keyword_or_literal() { - self.ctx.error(OxcDiagnostic::error("Parameter must have an explicit type annotation with --isolatedDeclarations.")); + self.ctx.error( + OxcDiagnostic::error("Parameter must have an explicit type annotation with --isolatedDeclarations.") + .with_label(expr.type_annotation.span()) + ); } } diff --git a/crates/oxc_transformer_dts/src/lib.rs b/crates/oxc_transformer_dts/src/lib.rs index 8345a8c12..58ee3e333 100644 --- a/crates/oxc_transformer_dts/src/lib.rs +++ b/crates/oxc_transformer_dts/src/lib.rs @@ -46,13 +46,31 @@ impl<'a> TransformerDts<'a> { _trivias: Trivias, ) -> Self { let ctx = Rc::new(TransformDtsCtx::new(allocator)); - Self { ctx, scope: ScopeTree::new() } + Self { ctx, scope: ScopeTree::new(allocator) } } /// # Errors /// /// Returns `Vec` if any errors were collected during the transformation. pub fn build(mut self, program: &Program<'a>) -> TransformerDtsReturn { + let source_type = SourceType::default().with_module(true).with_typescript_definition(true); + let directives = self.ctx.ast.new_vec(); + let stmts = self.transform_program(program); + let program = self.ctx.ast.program(SPAN, source_type, directives, None, stmts); + let source_text = + Codegen::::new("", "", Trivias::default(), CodegenOptions::default()) + .build(&program) + .source_text; + + TransformerDtsReturn { source_text, errors: self.ctx.take_errors() } + } +} + +impl<'a> TransformerDts<'a> { + pub fn transform_program( + &mut self, + program: &Program<'a>, + ) -> oxc_allocator::Vec<'a, Statement<'a>> { let has_import_or_export = program.body.iter().any(|stmt| { matches!( stmt, @@ -63,30 +81,24 @@ impl<'a> TransformerDts<'a> { ) }); - let stmts = if has_import_or_export { - self.transform_program(program) + if has_import_or_export { + self.transform_statements_on_demand(&program.body) } else { self.transform_program_without_module_declaration(program) - }; - - let source_type = SourceType::default().with_module(true).with_typescript_definition(true); - let directives = self.ctx.ast.new_vec(); - let program = self.ctx.ast.program(SPAN, source_type, directives, None, stmts); - let source_text = - Codegen::::new("", "", Trivias::default(), CodegenOptions::default()) - .build(&program) - .source_text; - TransformerDtsReturn { source_text, errors: self.ctx.take_errors() } + } } pub fn modifiers_declare(&self) -> Modifiers<'a> { - Modifiers::new( - self.ctx.ast.new_vec_single(Modifier { span: SPAN, kind: ModifierKind::Declare }), - ) + if self.scope.is_ts_module_block_flag() { + // If we are in a module block, we don't need to add declare + Modifiers::empty() + } else { + Modifiers::new( + self.ctx.ast.new_vec_single(Modifier { span: SPAN, kind: ModifierKind::Declare }), + ) + } } -} -impl<'a> TransformerDts<'a> { pub fn transform_program_without_module_declaration( &mut self, program: &Program<'a>, @@ -104,9 +116,9 @@ impl<'a> TransformerDts<'a> { new_ast_stmts } - pub fn transform_program( + pub fn transform_statements_on_demand( &mut self, - program: &Program<'a>, + stmts: &oxc_allocator::Vec<'a, Statement<'a>>, ) -> oxc_allocator::Vec<'a, Statement<'a>> { let mut new_stmts = Vec::new(); let mut variables_declarations = VecDeque::new(); @@ -116,7 +128,7 @@ impl<'a> TransformerDts<'a> { // 2. Transform export declarations // 3. Collect all bindings / reference from module declarations // 4. Collect transformed indexes - program.body.iter().for_each(|stmt| match stmt { + stmts.iter().for_each(|stmt| match stmt { match_declaration!(Statement) => { match stmt.to_declaration() { Declaration::VariableDeclaration(decl) => { @@ -229,9 +241,9 @@ impl<'a> TransformerDts<'a> { } // 6. Transform variable/using declarations, import statements, remove unused imports - // 7. Generate code - let mut new_ast_stmts = self.ctx.ast.new_vec::>(); - for (index, stmt) in new_stmts.drain(..).enumerate() { + // 7. Return transformed statements + let mut new_ast_stmts = self.ctx.ast.new_vec_with_capacity(transformed_indexes.len()); + for (index, stmt) in new_stmts.into_iter().enumerate() { match stmt { _ if transformed_indexes.contains(&index) => { new_ast_stmts.push(stmt); @@ -283,7 +295,7 @@ impl<'a> TransformerDts<'a> { if decl.specifiers.is_none() { new_ast_stmts.push(Statement::ImportDeclaration(decl)); } else if let Some(decl) = self.transform_import_declaration(&decl) { - new_ast_stmts.push(Statement::ImportDeclaration(self.ctx.ast.alloc(decl))); + new_ast_stmts.push(Statement::ImportDeclaration(decl)); } } _ => {} diff --git a/crates/oxc_transformer_dts/src/module.rs b/crates/oxc_transformer_dts/src/module.rs index 718243627..bc390a66d 100644 --- a/crates/oxc_transformer_dts/src/module.rs +++ b/crates/oxc_transformer_dts/src/module.rs @@ -1,6 +1,7 @@ #[allow(clippy::wildcard_imports)] use oxc_ast::ast::*; +use oxc_allocator::Box; use oxc_span::{GetSpan, SPAN}; use crate::TransformerDts; @@ -90,7 +91,7 @@ impl<'a> TransformerDts<'a> { pub fn transform_import_declaration( &self, decl: &ImportDeclaration<'a>, - ) -> Option> { + ) -> Option>> { let specifiers = decl.specifiers.as_ref()?; let mut specifiers = self.ctx.ast.copy(specifiers); @@ -109,13 +110,13 @@ impl<'a> TransformerDts<'a> { // We don't need to print this import statement None } else { - Some(ImportDeclaration { - span: decl.span, - specifiers: Some(specifiers), - source: self.ctx.ast.copy(&decl.source), - with_clause: self.ctx.ast.copy(&decl.with_clause), - import_kind: decl.import_kind, - }) + Some(self.ctx.ast.import_declaration( + decl.span, + Some(specifiers), + self.ctx.ast.copy(&decl.source), + self.ctx.ast.copy(&decl.with_clause), + decl.import_kind, + )) } } } diff --git a/crates/oxc_transformer_dts/src/scope.rs b/crates/oxc_transformer_dts/src/scope.rs index 9bd0c9f30..d40c9e368 100644 --- a/crates/oxc_transformer_dts/src/scope.rs +++ b/crates/oxc_transformer_dts/src/scope.rs @@ -1,46 +1,298 @@ +use oxc_allocator::{Allocator, Vec}; #[allow(clippy::wildcard_imports)] use oxc_ast::ast::*; -use oxc_ast::{visit::walk::walk_export_default_declaration, Visit}; +use oxc_ast::AstBuilder; +#[allow(clippy::wildcard_imports)] +use oxc_ast::{visit::walk::*, Visit}; use oxc_span::Atom; +use oxc_syntax::scope::ScopeFlags; use rustc_hash::FxHashSet; -#[derive(Debug)] pub struct ScopeTree<'a> { - references: FxHashSet>, + type_bindings: Vec<'a, FxHashSet>>, + value_bindings: Vec<'a, FxHashSet>>, + type_references: Vec<'a, FxHashSet>>, + value_references: Vec<'a, FxHashSet>>, + flags: Vec<'a, ScopeFlags>, } impl<'a> ScopeTree<'a> { - pub fn new() -> Self { - Self { references: FxHashSet::default() } + pub fn new(allocator: &'a Allocator) -> Self { + let ast = AstBuilder::new(allocator); + let mut scope = Self { + type_bindings: ast.new_vec(), + value_bindings: ast.new_vec(), + type_references: ast.new_vec(), + value_references: ast.new_vec(), + flags: ast.new_vec(), + }; + scope.enter_scope(ScopeFlags::Top); + scope + } + + pub fn is_ts_module_block_flag(&self) -> bool { + self.flags.last().unwrap().contains(ScopeFlags::TsModuleBlock) } pub fn has_reference(&self, name: &Atom<'a>) -> bool { - self.references.contains(name) + self.value_references.last().is_some_and(|rs| rs.contains(name)) + || self.type_references.last().is_some_and(|rs| rs.contains(name)) } pub fn references_len(&self) -> usize { - self.references.len() + self.value_references.last().unwrap().len() + self.type_references.last().unwrap().len() + } + + fn add_value_binding(&mut self, ident: &Atom<'a>) { + self.value_bindings.last_mut().unwrap().insert(ident.clone()); + } + + fn add_type_binding(&mut self, ident: &Atom<'a>) { + self.type_bindings.last_mut().unwrap().insert(ident.clone()); + } + + fn add_unresolved_value_reference(&mut self, ident: &Atom<'a>) { + self.value_references.last_mut().unwrap().insert(ident.clone()); + } + + fn add_unresolved_type_reference(&mut self, ident: &Atom<'a>) { + self.type_references.last_mut().unwrap().insert(ident.clone()); + } + + /// resolve references in the current scope + /// and merge unresolved references to the parent scope + /// and remove the current scope + fn resolve_references(&mut self) { + let current_value_bindings = self.value_bindings.pop().unwrap_or_default(); + let current_value_references = self.value_references.pop().unwrap_or_default(); + + self.type_references + .last_mut() + .unwrap() + .extend(current_value_references.difference(¤t_value_bindings).cloned()); + + let current_type_bindings = self.type_bindings.pop().unwrap_or_default(); + let current_type_references = self.type_references.pop().unwrap_or_default(); + self.type_references + .last_mut() + .unwrap() + .extend(current_type_references.difference(¤t_type_bindings).cloned()); } } impl<'a> Visit<'a> for ScopeTree<'a> { + fn enter_scope(&mut self, flags: ScopeFlags) { + self.flags.push(flags); + self.value_bindings.push(FxHashSet::default()); + self.type_bindings.push(FxHashSet::default()); + self.type_references.push(FxHashSet::default()); + self.value_references.push(FxHashSet::default()); + } + + fn leave_scope(&mut self) { + self.resolve_references(); + self.flags.pop(); + } + fn visit_identifier_reference(&mut self, ident: &IdentifierReference<'a>) { - self.references.insert(ident.name.clone()); + self.add_unresolved_value_reference(&ident.name); + } + + fn visit_binding_pattern(&mut self, pattern: &BindingPattern<'a>) { + if let BindingPatternKind::BindingIdentifier(ident) = &pattern.kind { + self.add_value_binding(&ident.name); + } + walk_binding_pattern(self, pattern); + } + + fn visit_ts_type_name(&mut self, name: &TSTypeName<'a>) { + if let TSTypeName::IdentifierReference(ident) = name { + self.add_unresolved_type_reference(&ident.name); + } else { + walk_ts_type_name(self, name); + } + } + + fn visit_ts_type_query(&mut self, ty: &TSTypeQuery<'a>) { + if let Some(type_name) = ty.expr_name.as_ts_type_name() { + let ident = TSTypeName::get_first_name(type_name); + self.add_unresolved_value_reference(&ident.name); + } else { + walk_ts_type_query(self, ty); + } } fn visit_export_named_declaration(&mut self, decl: &ExportNamedDeclaration<'a>) { for specifier in &decl.specifiers { if let ModuleExportName::Identifier(ident) = &specifier.local { - self.references.insert(ident.name.clone()); + self.add_type_binding(&ident.name); + self.add_value_binding(&ident.name); } } } fn visit_export_default_declaration(&mut self, decl: &ExportDefaultDeclaration<'a>) { if let ExportDefaultDeclarationKind::Identifier(ident) = &decl.declaration { - self.references.insert(ident.name.clone()); + self.add_type_binding(&ident.name); + self.add_value_binding(&ident.name); } else { walk_export_default_declaration(self, decl); } } + + fn visit_declaration(&mut self, declaration: &Declaration<'a>) { + match declaration { + Declaration::VariableDeclaration(_) | Declaration::UsingDeclaration(_) => { + // add binding in BindingPattern + } + Declaration::FunctionDeclaration(decl) => { + if let Some(id) = decl.id.as_ref() { + self.add_value_binding(&id.name); + } + } + Declaration::ClassDeclaration(decl) => { + if let Some(id) = decl.id.as_ref() { + self.add_value_binding(&id.name); + } + } + Declaration::TSTypeAliasDeclaration(decl) => { + self.add_type_binding(&decl.id.name); + } + Declaration::TSInterfaceDeclaration(decl) => { + self.add_type_binding(&decl.id.name); + } + Declaration::TSEnumDeclaration(decl) => { + self.add_value_binding(&decl.id.name); + self.add_type_binding(&decl.id.name); + } + Declaration::TSModuleDeclaration(decl) => { + if let TSModuleDeclarationName::Identifier(ident) = &decl.id { + self.add_value_binding(&ident.name); + self.add_type_binding(&ident.name); + } + } + Declaration::TSImportEqualsDeclaration(decl) => { + self.add_value_binding(&decl.id.name); + } + } + walk_declaration(self, declaration); + } + + // // TODO: handle ts infer and ts mapped type + + // ==================== TSTypeParameter ==================== + + /// ```ts + /// function foo(x: T): T { + /// ^^^ + /// `T` is a type parameter + /// return x; + /// } + /// ``` + /// We should create a new scope for TSTypeParameterDeclaration + /// Because the type parameter is can be used in following nodes + /// until the end of the function. So we leave the scope in the parent node (Function) + fn visit_ts_type_parameter_declaration(&mut self, decl: &TSTypeParameterDeclaration<'a>) { + self.enter_scope(ScopeFlags::empty()); + walk_ts_type_parameter_declaration(self, decl); + // exit scope in parent AST node + } + + fn visit_class(&mut self, class: &Class<'a>) { + walk_class(self, class); + if class.type_parameters.is_some() { + self.leave_scope(); + } + } + + fn visit_function(&mut self, func: &Function<'a>, flags: Option) { + walk_function(self, func, flags); + if func.type_parameters.is_some() { + self.leave_scope(); + } + } + + fn visit_arrow_expression(&mut self, expr: &ArrowFunctionExpression<'a>) { + walk_arrow_expression(self, expr); + if expr.type_parameters.is_some() { + self.leave_scope(); + } + } + + fn visit_ts_type_alias_declaration(&mut self, decl: &TSTypeAliasDeclaration<'a>) { + walk_ts_type_alias_declaration(self, decl); + if decl.type_parameters.is_some() { + self.leave_scope(); + } + } + + fn visit_ts_interface_declaration(&mut self, decl: &TSInterfaceDeclaration<'a>) { + walk_ts_interface_declaration(self, decl); + if decl.type_parameters.is_some() { + self.leave_scope(); + } + } + + fn visit_ts_call_signature_declaration(&mut self, signature: &TSCallSignatureDeclaration<'a>) { + walk_ts_call_signature_declaration(self, signature); + if signature.type_parameters.is_some() { + self.leave_scope(); + } + } + + fn visit_ts_method_signature(&mut self, signature: &TSMethodSignature<'a>) { + walk_ts_method_signature(self, signature); + if signature.type_parameters.is_some() { + self.leave_scope(); + } + } + + fn visit_ts_construct_signature_declaration( + &mut self, + signature: &TSConstructSignatureDeclaration<'a>, + ) { + walk_ts_construct_signature_declaration(self, signature); + if signature.type_parameters.is_some() { + self.leave_scope(); + } + } + + fn visit_ts_function_type(&mut self, signature: &TSFunctionType<'a>) { + walk_ts_function_type(self, signature); + if signature.type_parameters.is_some() { + self.leave_scope(); + } + } + + /// `type D = { [K in keyof T]: K };` + /// ^^^^^^^^^^^^^^^^^^^^ + /// `K` is a type parameter + /// We need to add `K` to the scope + fn visit_ts_mapped_type(&mut self, ty: &TSMappedType<'a>) { + // copy from walk_ts_mapped_type + self.enter_scope(ScopeFlags::empty()); + self.add_type_binding(&ty.type_parameter.name.name); + if let Some(name) = &ty.name_type { + self.visit_ts_type(name); + } + if let Some(type_annotation) = &ty.type_annotation { + self.visit_ts_type(type_annotation); + } + self.leave_scope(); + } + + /// `export type Flatten = Type extends Array ? Item : Type;` + /// ^^^^^^^^^^ + /// `Item` is a type parameter + /// We need to add `Item` to the scope + fn visit_conditional_expression(&mut self, expr: &ConditionalExpression<'a>) { + self.enter_scope(ScopeFlags::empty()); + walk_conditional_expression(self, expr); + self.leave_scope(); + } + + fn visit_ts_infer_type(&mut self, ty: &TSInferType<'a>) { + // copy from walk_ts_infer_type + self.add_type_binding(&ty.type_parameter.name.name); + } }