fix(transformer/typescript): remove type-only import = when only_remove_type_imports is true (#8275)

close: https://github.com/oxc-project/oxc/issues/8230
close: https://github.com/rolldown/rolldown/issues/3287

Related PR in Babel: https://github.com/oxc-project/oxc/issues/8230

I have compared our output with TypeScript, and it is the same as `TypeScript`, Babel's implementation currently hasn't removed imports referenced by type-only `TSImportEqualsDeclaration`
This commit is contained in:
Dunqing 2025-01-11 08:42:51 +00:00
parent d56020b84c
commit 9a03bd23b9
13 changed files with 367 additions and 340 deletions

View file

@ -143,9 +143,12 @@ impl<'a> Traverse<'a> for TypeScriptAnnotations<'a, '_> {
true
}
}
Statement::TSExportAssignment(_) | Statement::TSNamespaceExportDeclaration(_) => {
false
}
// `import Binding = X.Y.Z`
// `Binding` can be referenced as a value or a type, but here we already know it only as a type
// See `TypeScriptModule::transform_ts_import_equals`
Statement::TSTypeAliasDeclaration(_)
| Statement::TSExportAssignment(_)
| Statement::TSNamespaceExportDeclaration(_) => false,
_ => return true,
};

View file

@ -57,7 +57,7 @@ impl<'a, 'ctx> TypeScript<'a, 'ctx> {
annotations: TypeScriptAnnotations::new(options, ctx),
r#enum: TypeScriptEnum::new(),
namespace: TypeScriptNamespace::new(options, ctx),
module: TypeScriptModule::new(ctx),
module: TypeScriptModule::new(options.only_remove_type_imports, ctx),
rewrite_extensions: TypeScriptRewriteExtensions::new(options),
}
}

View file

@ -1,4 +1,5 @@
use oxc_ast::{ast::*, NONE};
use oxc_semantic::Reference;
use oxc_span::SPAN;
use oxc_syntax::reference::ReferenceFlags;
use oxc_traverse::{Traverse, TraverseCtx};
@ -7,12 +8,14 @@ use super::diagnostics;
use crate::TransformCtx;
pub struct TypeScriptModule<'a, 'ctx> {
/// <https://babeljs.io/docs/babel-plugin-transform-typescript#onlyremovetypeimports>
only_remove_type_imports: bool,
ctx: &'ctx TransformCtx<'a>,
}
impl<'a, 'ctx> TypeScriptModule<'a, 'ctx> {
pub fn new(ctx: &'ctx TransformCtx<'a>) -> Self {
Self { ctx }
pub fn new(only_remove_type_imports: bool, ctx: &'ctx TransformCtx<'a>) -> Self {
Self { only_remove_type_imports, ctx }
}
}
@ -37,7 +40,9 @@ impl<'a> Traverse<'a> for TypeScriptModule<'a, '_> {
fn enter_declaration(&mut self, decl: &mut Declaration<'a>, ctx: &mut TraverseCtx<'a>) {
if let Declaration::TSImportEqualsDeclaration(import_equals) = decl {
if import_equals.import_kind.is_value() {
*decl = self.transform_ts_import_equals(import_equals, ctx);
if let Some(new_decl) = self.transform_ts_import_equals(import_equals, ctx) {
*decl = new_decl;
}
}
}
}
@ -88,7 +93,29 @@ impl<'a> TypeScriptModule<'a, '_> {
&self,
decl: &mut TSImportEqualsDeclaration<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Declaration<'a> {
) -> Option<Declaration<'a>> {
if !self.only_remove_type_imports
&& !ctx.parent().is_export_named_declaration()
&& ctx.symbols().get_resolved_references(decl.id.symbol_id()).all(Reference::is_type)
{
// No value reference, we will remove this declaration in `TypeScriptAnnotations`
match &mut decl.module_reference {
module_reference @ match_ts_type_name!(TSModuleReference) => {
let ident = module_reference.to_ts_type_name().get_identifier_reference();
let reference = ctx.symbols_mut().get_reference_mut(ident.reference_id());
// The binding of TSImportEqualsDeclaration has treated as a type reference,
// so an identifier reference that it referenced also should be treated as a type reference.
// `import TypeBinding = X.Y.Z`
// ^ `X` should be treated as a type reference.
let flags = reference.flags_mut();
debug_assert_eq!(*flags, ReferenceFlags::Read);
*flags = ReferenceFlags::Type;
}
TSModuleReference::ExternalModuleReference(_) => {}
}
return None;
}
let binding_pattern_kind =
ctx.ast.binding_pattern_kind_binding_identifier(SPAN, &decl.id.name);
let binding = ctx.ast.binding_pattern(binding_pattern_kind, NONE, false);
@ -123,7 +150,7 @@ impl<'a> TypeScriptModule<'a, '_> {
let decls =
ctx.ast.vec1(ctx.ast.variable_declarator(SPAN, kind, binding, Some(init), false));
ctx.ast.declaration_variable(SPAN, kind, decls, false)
Some(ctx.ast.declaration_variable(SPAN, kind, decls, false))
}
#[allow(clippy::only_used_in_recursion)]

View file

@ -138,13 +138,19 @@ describe('modules', () => {
const code = `
export = function foo (): void {}
import bar = require('bar')
console.log(bar)
`;
const ret = transform('test.ts', code, {
typescript: {
declaration: {},
},
});
expect(ret.code).toEqual('module.exports = function foo() {};\nconst bar = require("bar");\n');
expect(ret.code).toMatchInlineSnapshot(`
"module.exports = function foo() {};
const bar = require("bar");
console.log(bar);
"
`);
expect(ret.declaration).toEqual('declare const _default: () => void;\nexport = _default;\n');
});
});

View file

@ -130,10 +130,9 @@ after transform: ["Y", "foo"]
rebuilt : []
tasks/coverage/babel/packages/babel-parser/test/fixtures/estree/typescript/import-require/input.js
semantic error: Missing SymbolId: "x"
Binding symbols mismatch:
after transform: ScopeId(0): [SymbolId(0)]
rebuilt : ScopeId(0): [SymbolId(0)]
semantic error: Bindings mismatch:
after transform: ScopeId(0): ["x"]
rebuilt : ScopeId(0): []
tasks/coverage/babel/packages/babel-parser/test/fixtures/estree/typescript/literals/input.js
semantic error: Scope children mismatch:
@ -723,28 +722,30 @@ after transform: ScopeId(0): [ScopeId(1), ScopeId(2)]
rebuilt : ScopeId(0): []
tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/import/equals/input.ts
semantic error: Missing SymbolId: "A"
Binding symbols mismatch:
after transform: ScopeId(0): [SymbolId(0)]
rebuilt : ScopeId(0): [SymbolId(0)]
semantic error: Bindings mismatch:
after transform: ScopeId(0): ["A"]
rebuilt : ScopeId(0): []
Unresolved references mismatch:
after transform: ["B"]
rebuilt : []
tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/import/equals-in-unambiguous/input.ts
semantic error: Missing SymbolId: "A"
Binding symbols mismatch:
after transform: ScopeId(0): [SymbolId(0)]
rebuilt : ScopeId(0): [SymbolId(0)]
semantic error: Bindings mismatch:
after transform: ScopeId(0): ["A"]
rebuilt : ScopeId(0): []
Unresolved references mismatch:
after transform: ["B"]
rebuilt : []
tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/import/equals-require/input.ts
semantic error: Missing SymbolId: "a"
Binding symbols mismatch:
after transform: ScopeId(0): [SymbolId(0)]
rebuilt : ScopeId(0): [SymbolId(0)]
semantic error: Bindings mismatch:
after transform: ScopeId(0): ["a"]
rebuilt : ScopeId(0): []
tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/import/equals-require-in-unambiguous/input.ts
semantic error: Missing SymbolId: "a"
Binding symbols mismatch:
after transform: ScopeId(0): [SymbolId(0)]
rebuilt : ScopeId(0): [SymbolId(0)]
semantic error: Bindings mismatch:
after transform: ScopeId(0): ["a"]
rebuilt : ScopeId(0): []
tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/import/export-import/input.ts
semantic error: Missing SymbolId: "A"
@ -799,10 +800,9 @@ after transform: ScopeId(0): ["a"]
rebuilt : ScopeId(0): []
tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/import/import-type-as-identifier/input.ts
semantic error: Missing SymbolId: "type"
Binding symbols mismatch:
after transform: ScopeId(0): [SymbolId(0)]
rebuilt : ScopeId(0): [SymbolId(0)]
semantic error: Bindings mismatch:
after transform: ScopeId(0): ["type"]
rebuilt : ScopeId(0): []
tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/import/internal-comments/input.ts
semantic error: Bindings mismatch:
@ -1431,8 +1431,7 @@ after transform: ScopeId(0): [ScopeId(1)]
rebuilt : ScopeId(0): []
tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/scope/redeclaration-import-equals-var/input.ts
semantic error: Missing SymbolId: "a"
Bindings mismatch:
semantic error: Bindings mismatch:
after transform: ScopeId(0): ["M", "a"]
rebuilt : ScopeId(0): ["a"]
Scope children mismatch:
@ -1443,7 +1442,13 @@ after transform: SymbolId(1): SymbolFlags(FunctionScopedVariable | Import)
rebuilt : SymbolId(0): SymbolFlags(FunctionScopedVariable)
Symbol span mismatch for "a":
after transform: SymbolId(1): Span { start: 20, end: 21 }
rebuilt : SymbolId(0): Span { start: 0, end: 0 }
rebuilt : SymbolId(0): Span { start: 31, end: 32 }
Symbol redeclarations mismatch for "a":
after transform: SymbolId(1): [Span { start: 31, end: 32 }]
rebuilt : SymbolId(0): []
Unresolved references mismatch:
after transform: ["M"]
rebuilt : []
tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/scope/redeclaration-import-let/input.ts
semantic error: Symbol flags mismatch for "Context":

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,8 @@
var AliasModule = LongNameModule;
const some = 3;
const bar = AliasModule.foo;
const baz = AliasModule.Inner.bar;
let str;
let node;
console.log(some);
export {};

View file

@ -2060,11 +2060,9 @@ after transform: ScopeId(0): ["React", "x"]
rebuilt : ScopeId(0): ["x"]
* imports/elide-type-referenced-in-imports-equal-no/input.ts
Missing SymbolId: "foo"
Missing SymbolId: "bar"
Binding symbols mismatch:
after transform: ScopeId(0): [SymbolId(0), SymbolId(1), SymbolId(2), SymbolId(3)]
rebuilt : ScopeId(0): [SymbolId(0), SymbolId(1), SymbolId(2), SymbolId(3)]
Bindings mismatch:
after transform: ScopeId(0): ["bar", "foo", "nsa", "nsb"]
rebuilt : ScopeId(0): []
* imports/elide-typeof/input.ts
Bindings mismatch:
@ -2215,23 +2213,22 @@ after transform: SymbolId(2): [ReferenceId(4), ReferenceId(6)]
rebuilt : SymbolId(1): [ReferenceId(1)]
* namespace/alias/input.ts
Missing SymbolId: "b"
Missing SymbolId: "AliasModule"
Bindings mismatch:
after transform: ScopeId(0): ["AliasModule", "LongNameModule", "b", "babel", "bar", "baz", "node", "some", "str"]
rebuilt : ScopeId(0): ["AliasModule", "b", "babel", "bar", "baz", "node", "some", "str"]
rebuilt : ScopeId(0): ["AliasModule", "bar", "baz", "node", "some", "str"]
Scope children mismatch:
after transform: ScopeId(0): [ScopeId(1)]
rebuilt : ScopeId(0): []
Reference symbol mismatch for "AliasModule":
after transform: SymbolId(8) "AliasModule"
rebuilt : SymbolId(2) "AliasModule"
rebuilt : SymbolId(0) "AliasModule"
Reference symbol mismatch for "AliasModule":
after transform: SymbolId(8) "AliasModule"
rebuilt : SymbolId(2) "AliasModule"
rebuilt : SymbolId(0) "AliasModule"
Unresolved reference IDs mismatch for "LongNameModule":
after transform: [ReferenceId(1), ReferenceId(5)]
rebuilt : [ReferenceId(1)]
rebuilt : [ReferenceId(0)]
* namespace/clobber-class/input.ts
Missing SymbolId: "_A"

View file

@ -1,6 +1,6 @@
commit: 54a8389f
Passed: 126/144
Passed: 126/145
# All Passed:
* babel-plugin-transform-class-static-block
@ -47,7 +47,7 @@ after transform: SymbolId(0): [ReferenceId(0), ReferenceId(2), ReferenceId(6), R
rebuilt : SymbolId(0): [ReferenceId(0), ReferenceId(2), ReferenceId(6), ReferenceId(10)]
# babel-plugin-transform-typescript (2/10)
# babel-plugin-transform-typescript (2/11)
* class-property-definition/input.ts
Unresolved references mismatch:
after transform: ["const"]
@ -159,6 +159,12 @@ Scope children mismatch:
after transform: ScopeId(0): [ScopeId(1)]
rebuilt : ScopeId(0): []
* preserve-import-=/input.js
Missing SymbolId: "Foo"
Binding symbols mismatch:
after transform: ScopeId(0): [SymbolId(0), SymbolId(1), SymbolId(2)]
rebuilt : ScopeId(0): [SymbolId(0), SymbolId(1), SymbolId(2)]
* redeclarations/input.ts
Bindings mismatch:
after transform: ScopeId(0): ["A"]

View file

@ -0,0 +1,4 @@
import nsa from "./module-a";
import Foo = nsa.bar;
const foo: Foo = 0;

View file

@ -0,0 +1,10 @@
{
"plugins": [
[
"transform-typescript",
{
"onlyRemoveTypeImports": true
}
]
]
}

View file

@ -0,0 +1,3 @@
import nsa from "./module-a";
var Foo = nsa.bar;
const foo = 0;