diff --git a/crates/oxc_transformer/src/lib.rs b/crates/oxc_transformer/src/lib.rs index 307d96b546a31..44ee5dd5cccb0 100644 --- a/crates/oxc_transformer/src/lib.rs +++ b/crates/oxc_transformer/src/lib.rs @@ -597,9 +597,6 @@ impl<'a> Traverse<'a, TransformState<'a>> for TransformerImpl<'a> { stmts: &mut ArenaVec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>, ) { - if let Some(typescript) = self.x0_typescript.as_mut() { - typescript.exit_statements(stmts, ctx); - } self.common.exit_statements(stmts, ctx); } diff --git a/crates/oxc_transformer/src/typescript/annotations.rs b/crates/oxc_transformer/src/typescript/annotations.rs index 1d953eb8fec66..45ca8567d1749 100644 --- a/crates/oxc_transformer/src/typescript/annotations.rs +++ b/crates/oxc_transformer/src/typescript/annotations.rs @@ -1,7 +1,7 @@ use oxc_allocator::{TakeIn, Vec as ArenaVec}; use oxc_ast::ast::*; use oxc_diagnostics::OxcDiagnostic; -use oxc_semantic::SymbolFlags; +use oxc_semantic::{Reference, SymbolFlags}; use oxc_span::{Atom, GetSpan, SPAN, Span}; use oxc_syntax::{ operator::AssignmentOperator, @@ -362,19 +362,14 @@ impl<'a> Traverse<'a, TransformState<'a>> for TypeScriptAnnotations<'a> { fn enter_statements( &mut self, stmts: &mut ArenaVec<'a, Statement<'a>>, - _ctx: &mut TraverseCtx<'a>, + ctx: &mut TraverseCtx<'a>, ) { - // Remove TypeScript type-only declarations (interfaces, type aliases, etc.) - // but NOT declarations with `declare` keyword - those will be handled - // by their respective enter_* methods which will remove the `declare` flag - stmts.retain(|stmt| { - if let Some(decl) = stmt.as_declaration() { - // Only remove pure TypeScript type declarations - // Keep all other declarations including those with `declare` - !decl.is_type() - } else { - true + // Remove TS-only statements early to avoid traversing their children + stmts.retain(|stmt| match stmt { + match_declaration!(Statement) => { + self.should_keep_declaration(stmt.to_declaration(), ctx) } + _ => true, }); } @@ -399,39 +394,6 @@ impl<'a> Traverse<'a, TransformState<'a>> for TypeScriptAnnotations<'a> { self.has_super_call = true; } - fn exit_statements( - &mut self, - stmts: &mut ArenaVec<'a, Statement<'a>>, - _ctx: &mut TraverseCtx<'a>, - ) { - // Remove TS specific statements - stmts.retain_mut(|stmt| match stmt { - Statement::ExpressionStatement(s) => !s.expression.is_typescript_syntax(), - match_declaration!(Statement) => { - let decl = stmt.to_declaration_mut(); - match decl { - Declaration::VariableDeclaration(var_decl) => { - // Remove declare variable declarations entirely - !var_decl.declare - } - Declaration::FunctionDeclaration(func_decl) => { - // Remove declare function declarations and function overload signatures entirely - // Keep only function implementations (those with a body) - !func_decl.declare && func_decl.body.is_some() - } - Declaration::ClassDeclaration(class_decl) => { - // Remove declare class declarations entirely - !class_decl.declare - } - // Remove type-only declarations - _ => !decl.is_typescript_syntax(), - } - } - // Ignore ModuleDeclaration as it's handled in the program - _ => true, - }); - } - /// Transform if statement's consequent and alternate to block statements if they are super calls /// ```ts /// if (true) super() else super(); @@ -535,6 +497,51 @@ impl<'a> Traverse<'a, TransformState<'a>> for TypeScriptAnnotations<'a> { } impl<'a> TypeScriptAnnotations<'a> { + #[inline] + fn should_keep_declaration(&self, decl: &Declaration<'a>, ctx: &mut TraverseCtx<'a>) -> bool { + match decl { + // Remove type aliases, interfaces, and `declare global {}` + Declaration::TSTypeAliasDeclaration(_) + | Declaration::TSInterfaceDeclaration(_) + | Declaration::TSGlobalDeclaration(_) => false, + // Remove `declare var/let/const` + Declaration::VariableDeclaration(var_decl) => !var_decl.declare, + // Remove `declare function` and function overload signatures (no body) + Declaration::FunctionDeclaration(func_decl) => { + !func_decl.declare && func_decl.body.is_some() + } + // Remove `declare class` + Declaration::ClassDeclaration(class_decl) => !class_decl.declare, + // Remove `declare module` or uninstantiated namespace declarations. + // Keep instantiated `module` declarations — they have runtime + // representation and need to be transformed. + Declaration::TSModuleDeclaration(module_decl) => { + !module_decl.declare + && !matches!( + &module_decl.id, + TSModuleDeclarationName::Identifier(ident) + if ctx.scoping().symbol_flags(ident.symbol_id()).is_namespace_module() + ) + } + // Remove `declare enum` + Declaration::TSEnumDeclaration(enum_decl) => !enum_decl.declare, + // Remove unused import-equals (used ones are transformed by module transform) + Declaration::TSImportEqualsDeclaration(import_equals) => { + let keep = import_equals.import_kind.is_value() + && (self.only_remove_type_imports + || !ctx + .scoping() + .get_resolved_references(import_equals.id.symbol_id()) + .all(Reference::is_type)); + if !keep { + let scope_id = ctx.current_scope_id(); + ctx.scoping_mut().remove_binding(scope_id, import_equals.id.name); + } + keep + } + } + } + /// Check if the given name is a JSX pragma or fragment pragma import /// and if the file contains JSX elements or fragments fn is_jsx_imports(&self, name: &str) -> bool { diff --git a/crates/oxc_transformer/src/typescript/mod.rs b/crates/oxc_transformer/src/typescript/mod.rs index 8cb005e387cc5..205576d52bde2 100644 --- a/crates/oxc_transformer/src/typescript/mod.rs +++ b/crates/oxc_transformer/src/typescript/mod.rs @@ -214,14 +214,6 @@ impl<'a> Traverse<'a, TransformState<'a>> for TypeScript<'a> { self.annotations.enter_statements(stmts, ctx); } - fn exit_statements( - &mut self, - stmts: &mut ArenaVec<'a, Statement<'a>>, - ctx: &mut TraverseCtx<'a>, - ) { - self.annotations.exit_statements(stmts, ctx); - } - fn enter_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) { self.r#enum.enter_statement(stmt, ctx); self.module.enter_statement(stmt, ctx); diff --git a/tasks/coverage/snapshots/semantic_babel.snap b/tasks/coverage/snapshots/semantic_babel.snap index 30abf3fb880e3..e5a7efa103e0c 100644 --- a/tasks/coverage/snapshots/semantic_babel.snap +++ b/tasks/coverage/snapshots/semantic_babel.snap @@ -2,7 +2,7 @@ commit: 92c052dc semantic_babel Summary: AST Parsed : 2224/2224 (100.00%) -Positive Passed: 2024/2224 (91.01%) +Positive Passed: 2025/2224 (91.05%) semantic Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/comments/decorators/decorators-after-export/input.js Symbol span mismatch for "C": after transform: SymbolId(0): Span { start: 65, end: 66 } @@ -655,12 +655,7 @@ rebuilt : ScopeId(0): [] semantic Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/import/type-asi/input.ts Bindings mismatch: -after transform: ScopeId(0): ["A", "B", "a", "b"] -rebuilt : ScopeId(0): [] - -semantic Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/import/type-equals-require/input.ts -Bindings mismatch: -after transform: ScopeId(0): ["a"] +after transform: ScopeId(0): ["B", "b"] rebuilt : ScopeId(0): [] semantic Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/interface/extends/input.ts diff --git a/tasks/coverage/snapshots/semantic_typescript.snap b/tasks/coverage/snapshots/semantic_typescript.snap index 802f9a50ec033..d061bc2f04e09 100644 --- a/tasks/coverage/snapshots/semantic_typescript.snap +++ b/tasks/coverage/snapshots/semantic_typescript.snap @@ -2,7 +2,7 @@ commit: 95e3aaa9 semantic_typescript Summary: AST Parsed : 5482/5482 (100.00%) -Positive Passed: 2950/5482 (53.81%) +Positive Passed: 2951/5482 (53.83%) semantic Error: tasks/coverage/typescript/tests/cases/compiler/2dArrays.ts Symbol reference IDs mismatch for "Cell": after transform: SymbolId(0): [ReferenceId(1)] @@ -30085,11 +30085,6 @@ Bindings mismatch: after transform: ScopeId(0): ["from"] rebuilt : ScopeId(0): [] -semantic Error: tasks/coverage/typescript/tests/cases/conformance/externalModules/typeOnly/importDefaultNamedType3.ts -Bindings mismatch: -after transform: ScopeId(0): ["from"] -rebuilt : ScopeId(0): [] - semantic Error: tasks/coverage/typescript/tests/cases/conformance/externalModules/typeOnly/mergedWithLocalValue.ts Symbol flags mismatch for "A": after transform: SymbolId(0): SymbolFlags(BlockScopedVariable | ConstVariable | TypeImport) @@ -30231,7 +30226,7 @@ rebuilt : SymbolId(0): SymbolFlags(BlockScopedVariable) semantic Error: tasks/coverage/typescript/tests/cases/conformance/externalModules/verbatimModuleSyntaxRestrictionsESM.ts Bindings mismatch: -after transform: ScopeId(0): ["CJSy", "CJSy2", "CJSy3", "types"] +after transform: ScopeId(0): ["CJSy", "CJSy3", "types"] rebuilt : ScopeId(0): ["CJSy"] semantic Error: tasks/coverage/typescript/tests/cases/conformance/functions/functionOverloadCompatibilityWithVoid02.ts