mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 12:19:15 +00:00
feat(transformer/typescript): support transform export = and import = require(...) when module is commonjs (#7206)
close: #7141
This commit is contained in:
parent
fc86703933
commit
5cfdc05d06
7 changed files with 390 additions and 1098 deletions
|
|
@ -29,7 +29,6 @@ pub struct TransformCtx<'a> {
|
|||
|
||||
pub source_text: &'a str,
|
||||
|
||||
#[expect(unused)]
|
||||
pub module: Module,
|
||||
|
||||
// Helpers
|
||||
|
|
|
|||
|
|
@ -1,14 +1,21 @@
|
|||
use oxc_diagnostics::OxcDiagnostic;
|
||||
use oxc_span::Span;
|
||||
|
||||
pub fn import_equals_require_unsupported(span: Span) -> OxcDiagnostic {
|
||||
OxcDiagnostic::warn("`import lib = require(...);` is only supported when compiling modules to CommonJS.\nPlease consider using `import lib from '...';` alongside Typescript's --allowSyntheticDefaultImports option, or add @babel/plugin-transform-modules-commonjs to your Babel config.")
|
||||
pub fn import_equals_cannot_be_used_in_esm(span: Span) -> OxcDiagnostic {
|
||||
OxcDiagnostic::warn("Import assignment cannot be used when targeting ECMAScript modules.")
|
||||
.with_help(
|
||||
"Consider using 'import * as ns from \"mod\"',
|
||||
'import {a} from \"mod\"', 'import d from \"mod\"', or another module format instead.",
|
||||
)
|
||||
.with_label(span)
|
||||
.with_error_code("TS", "1202")
|
||||
}
|
||||
|
||||
pub fn export_assignment_unsupported(span: Span) -> OxcDiagnostic {
|
||||
OxcDiagnostic::warn("`export = <value>;` is only supported when compiling modules to CommonJS.\nPlease consider using `export default <value>;`, or add @babel/plugin-transform-modules-commonjs to your Babel config.")
|
||||
pub fn export_assignment_cannot_bed_used_in_esm(span: Span) -> OxcDiagnostic {
|
||||
OxcDiagnostic::warn("Export assignment cannot be used when targeting ECMAScript modules.")
|
||||
.with_help("Consider using 'export default' or another module format instead.")
|
||||
.with_label(span)
|
||||
.with_error_code("TS", "1203")
|
||||
}
|
||||
|
||||
pub fn ambient_module_nested(span: Span) -> OxcDiagnostic {
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ impl<'a, 'ctx> Traverse<'a> for TypeScript<'a, 'ctx> {
|
|||
|
||||
fn exit_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
self.annotations.exit_program(program, ctx);
|
||||
self.module.exit_program(program, ctx);
|
||||
}
|
||||
|
||||
fn enter_arrow_function_expression(
|
||||
|
|
@ -215,6 +216,7 @@ impl<'a, 'ctx> Traverse<'a> for TypeScript<'a, '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);
|
||||
}
|
||||
|
||||
fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
|
|
@ -298,12 +300,4 @@ impl<'a, 'ctx> Traverse<'a> for TypeScript<'a, 'ctx> {
|
|||
rewrite_extensions.enter_export_named_declaration(node, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
fn enter_ts_export_assignment(
|
||||
&mut self,
|
||||
node: &mut TSExportAssignment<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
self.module.enter_ts_export_assignment(node, ctx);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
use oxc_ast::{ast::*, NONE};
|
||||
use oxc_span::SPAN;
|
||||
use oxc_span::{CompactStr, SPAN};
|
||||
use oxc_syntax::reference::ReferenceFlags;
|
||||
use oxc_traverse::{Traverse, TraverseCtx};
|
||||
|
||||
use super::diagnostics;
|
||||
use crate::TransformCtx;
|
||||
|
||||
pub struct TypeScriptModule<'a, 'ctx> {
|
||||
|
|
@ -16,70 +17,105 @@ impl<'a, 'ctx> TypeScriptModule<'a, 'ctx> {
|
|||
}
|
||||
|
||||
impl<'a, 'ctx> Traverse<'a> for TypeScriptModule<'a, 'ctx> {
|
||||
/// ```TypeScript
|
||||
/// import b = babel;
|
||||
/// import AliasModule = LongNameModule;
|
||||
///
|
||||
/// ```JavaScript
|
||||
/// var b = babel;
|
||||
/// var AliasModule = LongNameModule;
|
||||
/// ```
|
||||
fn enter_declaration(&mut self, decl: &mut Declaration<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
match decl {
|
||||
Declaration::TSImportEqualsDeclaration(ts_import_equals)
|
||||
if ts_import_equals.import_kind.is_value() =>
|
||||
{
|
||||
*decl = self.transform_ts_import_equals(ts_import_equals, ctx);
|
||||
fn exit_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
// In Babel, it will insert `use strict` in `@babel/transform-modules-commonjs` plugin.
|
||||
// Once we have a commonjs plugin, we can consider moving this logic there.
|
||||
if self.ctx.module.is_commonjs() {
|
||||
let has_use_strict = program.directives.iter().any(Directive::is_use_strict);
|
||||
if !has_use_strict {
|
||||
let use_strict = ctx.ast.string_literal(SPAN, "use strict");
|
||||
program.directives.insert(0, ctx.ast.directive(SPAN, use_strict, "use strict"));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn enter_ts_export_assignment(
|
||||
&mut self,
|
||||
export_assignment: &mut TSExportAssignment<'a>,
|
||||
_ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
if self.ctx.source_type.is_module() {
|
||||
self.ctx
|
||||
.error(super::diagnostics::export_assignment_unsupported(export_assignment.span));
|
||||
fn enter_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
if let Statement::TSExportAssignment(export_assignment) = stmt {
|
||||
*stmt = self.transform_ts_export_assignment(export_assignment, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'ctx> TypeScriptModule<'a, 'ctx> {
|
||||
/// Transform `export = expression` to `module.exports = expression`.
|
||||
fn transform_ts_export_assignment(
|
||||
&mut self,
|
||||
export_assignment: &mut TSExportAssignment<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> Statement<'a> {
|
||||
if self.ctx.module.is_esm() {
|
||||
self.ctx.error(diagnostics::export_assignment_cannot_bed_used_in_esm(
|
||||
export_assignment.span,
|
||||
));
|
||||
}
|
||||
|
||||
// module.exports
|
||||
let module_exports = {
|
||||
let reference_id = ctx
|
||||
.create_reference_in_current_scope(CompactStr::new("module"), ReferenceFlags::Read);
|
||||
let reference =
|
||||
ctx.ast.alloc_identifier_reference_with_reference_id(SPAN, "module", reference_id);
|
||||
let object = Expression::Identifier(reference);
|
||||
let property = ctx.ast.identifier_name(SPAN, "exports");
|
||||
ctx.ast.member_expression_static(SPAN, object, property, false)
|
||||
};
|
||||
|
||||
let left = AssignmentTarget::from(SimpleAssignmentTarget::from(module_exports));
|
||||
let right = ctx.ast.move_expression(&mut export_assignment.expression);
|
||||
let assignment_expr =
|
||||
ctx.ast.expression_assignment(SPAN, AssignmentOperator::Assign, left, right);
|
||||
ctx.ast.statement_expression(SPAN, assignment_expr)
|
||||
}
|
||||
|
||||
/// Transform TSImportEqualsDeclaration to a VariableDeclaration.
|
||||
///
|
||||
/// ```TypeScript
|
||||
/// import module = require('module');
|
||||
/// import AliasModule = LongNameModule;
|
||||
///
|
||||
/// ```JavaScript
|
||||
/// const module = require('module');
|
||||
/// const AliasModule = LongNameModule;
|
||||
/// ```
|
||||
fn transform_ts_import_equals(
|
||||
&self,
|
||||
decl: &mut TSImportEqualsDeclaration<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> Declaration<'a> {
|
||||
let kind = VariableDeclarationKind::Var;
|
||||
let decls = {
|
||||
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);
|
||||
let decl_span = decl.span;
|
||||
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);
|
||||
let decl_span = decl.span;
|
||||
|
||||
let init = match &mut decl.module_reference {
|
||||
type_name @ match_ts_type_name!(TSModuleReference) => {
|
||||
self.transform_ts_type_name(&mut *type_name.to_ts_type_name_mut(), ctx)
|
||||
let (kind, init) = match &mut decl.module_reference {
|
||||
type_name @ match_ts_type_name!(TSModuleReference) => (
|
||||
VariableDeclarationKind::Var,
|
||||
self.transform_ts_type_name(&mut *type_name.to_ts_type_name_mut(), ctx),
|
||||
),
|
||||
TSModuleReference::ExternalModuleReference(reference) => {
|
||||
if self.ctx.module.is_esm() {
|
||||
self.ctx.error(diagnostics::import_equals_cannot_be_used_in_esm(decl_span));
|
||||
}
|
||||
TSModuleReference::ExternalModuleReference(reference) => {
|
||||
if self.ctx.source_type.is_module() {
|
||||
self.ctx.error(super::diagnostics::import_equals_require_unsupported(
|
||||
decl_span,
|
||||
));
|
||||
}
|
||||
|
||||
let callee = ctx.ast.expression_identifier_reference(SPAN, "require");
|
||||
let arguments = ctx
|
||||
.ast
|
||||
.vec1(Argument::StringLiteral(ctx.alloc(reference.expression.clone())));
|
||||
ctx.ast.expression_call(SPAN, callee, NONE, arguments, false)
|
||||
}
|
||||
};
|
||||
ctx.ast.vec1(ctx.ast.variable_declarator(SPAN, kind, binding, Some(init), false))
|
||||
let callee = ctx.ast.expression_identifier_reference(SPAN, "require");
|
||||
let arguments =
|
||||
ctx.ast.vec1(Argument::StringLiteral(ctx.alloc(reference.expression.clone())));
|
||||
(
|
||||
VariableDeclarationKind::Const,
|
||||
ctx.ast.expression_call(SPAN, callee, NONE, arguments, false),
|
||||
)
|
||||
}
|
||||
};
|
||||
let decls =
|
||||
ctx.ast.vec1(ctx.ast.variable_declarator(SPAN, kind, binding, Some(init), false));
|
||||
|
||||
ctx.ast.declaration_variable(SPAN, kind, decls, false)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -423,8 +423,7 @@ after transform: ["Y", "foo"]
|
|||
rebuilt : []
|
||||
|
||||
tasks/coverage/babel/packages/babel-parser/test/fixtures/estree/typescript/import-require/input.js
|
||||
semantic error: `import lib = require(...);` is only supported when compiling modules to CommonJS.
|
||||
Please consider using `import lib from '...';` alongside Typescript's --allowSyntheticDefaultImports option, or add @babel/plugin-transform-modules-commonjs to your Babel config.
|
||||
semantic error: Import assignment cannot be used when targeting ECMAScript modules.
|
||||
|
||||
tasks/coverage/babel/packages/babel-parser/test/fixtures/estree/typescript/literals/input.js
|
||||
semantic error: Bindings mismatch:
|
||||
|
|
@ -1101,12 +1100,10 @@ after transform: ScopeId(0): [ScopeId(1), ScopeId(2), ScopeId(3), ScopeId(4), Sc
|
|||
rebuilt : ScopeId(0): []
|
||||
|
||||
tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/export/equals/input.ts
|
||||
semantic error: `export = <value>;` is only supported when compiling modules to CommonJS.
|
||||
Please consider using `export default <value>;`, or add @babel/plugin-transform-modules-commonjs to your Babel config.
|
||||
semantic error: Export assignment cannot be used when targeting ECMAScript modules.
|
||||
|
||||
tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/export/equals-in-unambiguous/input.ts
|
||||
semantic error: `export = <value>;` is only supported when compiling modules to CommonJS.
|
||||
Please consider using `export default <value>;`, or add @babel/plugin-transform-modules-commonjs to your Babel config.
|
||||
semantic error: Export assignment cannot be used when targeting ECMAScript modules.
|
||||
|
||||
tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/export/export-type/input.ts
|
||||
semantic error: Bindings mismatch:
|
||||
|
|
@ -1226,18 +1223,10 @@ after transform: ScopeId(0): [SymbolId(0)]
|
|||
rebuilt : ScopeId(0): [SymbolId(0)]
|
||||
|
||||
tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/import/equals-require/input.ts
|
||||
semantic error: `import lib = require(...);` is only supported when compiling modules to CommonJS.
|
||||
Please consider using `import lib from '...';` alongside Typescript's --allowSyntheticDefaultImports option, or add @babel/plugin-transform-modules-commonjs to your Babel config.
|
||||
semantic error: Import assignment cannot be used when targeting ECMAScript modules.
|
||||
|
||||
tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/import/equals-require-in-unambiguous/input.ts
|
||||
semantic error: Missing SymbolId: "a"
|
||||
Missing ReferenceId: "require"
|
||||
Binding symbols mismatch:
|
||||
after transform: ScopeId(0): [SymbolId(0)]
|
||||
rebuilt : ScopeId(0): [SymbolId(0)]
|
||||
Unresolved references mismatch:
|
||||
after transform: []
|
||||
rebuilt : ["require"]
|
||||
semantic error: Import assignment cannot be used when targeting ECMAScript modules.
|
||||
|
||||
tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/import/export-import/input.ts
|
||||
semantic error: Missing SymbolId: "A"
|
||||
|
|
@ -1246,12 +1235,10 @@ after transform: ScopeId(0): [SymbolId(0)]
|
|||
rebuilt : ScopeId(0): [SymbolId(0)]
|
||||
|
||||
tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/import/export-import-require/input.ts
|
||||
semantic error: `import lib = require(...);` is only supported when compiling modules to CommonJS.
|
||||
Please consider using `import lib from '...';` alongside Typescript's --allowSyntheticDefaultImports option, or add @babel/plugin-transform-modules-commonjs to your Babel config.
|
||||
semantic error: Import assignment cannot be used when targeting ECMAScript modules.
|
||||
|
||||
tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/import/export-import-type-as-identifier/input.ts
|
||||
semantic error: `import lib = require(...);` is only supported when compiling modules to CommonJS.
|
||||
Please consider using `import lib from '...';` alongside Typescript's --allowSyntheticDefaultImports option, or add @babel/plugin-transform-modules-commonjs to your Babel config.
|
||||
semantic error: Import assignment cannot be used when targeting ECMAScript modules.
|
||||
|
||||
tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/import/export-import-type-require/input.ts
|
||||
semantic error: Bindings mismatch:
|
||||
|
|
@ -1259,8 +1246,7 @@ after transform: ScopeId(0): ["a"]
|
|||
rebuilt : ScopeId(0): []
|
||||
|
||||
tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/import/export-named-import-require/input.ts
|
||||
semantic error: `import lib = require(...);` is only supported when compiling modules to CommonJS.
|
||||
Please consider using `import lib from '...';` alongside Typescript's --allowSyntheticDefaultImports option, or add @babel/plugin-transform-modules-commonjs to your Babel config.
|
||||
semantic error: Import assignment cannot be used when targeting ECMAScript modules.
|
||||
|
||||
tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/import/import-default-and-named-id-type/input.ts
|
||||
semantic error: Bindings mismatch:
|
||||
|
|
@ -1283,8 +1269,7 @@ 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: `import lib = require(...);` is only supported when compiling modules to CommonJS.
|
||||
Please consider using `import lib from '...';` alongside Typescript's --allowSyntheticDefaultImports option, or add @babel/plugin-transform-modules-commonjs to your Babel config.
|
||||
semantic error: Import assignment cannot be used when targeting ECMAScript modules.
|
||||
|
||||
tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/import/internal-comments/input.ts
|
||||
semantic error: Bindings mismatch:
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,6 @@
|
|||
commit: d20b314c
|
||||
|
||||
Passed: 316/633
|
||||
Passed: 317/633
|
||||
|
||||
# All Passed:
|
||||
* babel-plugin-transform-class-static-block
|
||||
|
|
@ -572,7 +572,7 @@ rebuilt : ScopeId(1): []
|
|||
x Output mismatch
|
||||
|
||||
|
||||
# babel-plugin-transform-typescript (40/155)
|
||||
# babel-plugin-transform-typescript (41/155)
|
||||
* cast/as-expression/input.ts
|
||||
Unresolved references mismatch:
|
||||
after transform: ["T", "x"]
|
||||
|
|
@ -945,24 +945,13 @@ rebuilt : ScopeId(0): []
|
|||
|
||||
* exports/export=/input.ts
|
||||
|
||||
! `export = <value>;` is only supported when compiling modules to CommonJS.
|
||||
| Please consider using `export default <value>;`, or add @babel/plugin-
|
||||
| transform-modules-commonjs to your Babel config.
|
||||
! TS(1203): Export assignment cannot be used when targeting ECMAScript
|
||||
| modules.
|
||||
,-[tasks/coverage/babel/packages/babel-plugin-transform-typescript/test/fixtures/exports/export=/input.ts:1:1]
|
||||
1 | export = 0;
|
||||
: ^^^^^^^^^^^
|
||||
`----
|
||||
|
||||
|
||||
* exports/export=-to-cjs/input.ts
|
||||
|
||||
! `export = <value>;` is only supported when compiling modules to CommonJS.
|
||||
| Please consider using `export default <value>;`, or add @babel/plugin-
|
||||
| transform-modules-commonjs to your Babel config.
|
||||
,-[tasks/coverage/babel/packages/babel-plugin-transform-typescript/test/fixtures/exports/export=-to-cjs/input.ts:1:1]
|
||||
1 | export = 0;
|
||||
: ^^^^^^^^^^^
|
||||
`----
|
||||
help: Consider using 'export default' or another module format instead.
|
||||
|
||||
|
||||
* exports/imported-types/input.ts
|
||||
|
|
@ -1153,31 +1142,30 @@ rebuilt : ScopeId(0): []
|
|||
|
||||
* imports/import=-module/input.ts
|
||||
|
||||
! `import lib = require(...);` is only supported when compiling modules
|
||||
| to CommonJS.
|
||||
| Please consider using `import lib from '...';` alongside Typescript's
|
||||
| --allowSyntheticDefaultImports option, or add @babel/plugin-transform-
|
||||
| modules-commonjs to your Babel config.
|
||||
! TS(1202): Import assignment cannot be used when targeting ECMAScript
|
||||
| modules.
|
||||
,-[tasks/coverage/babel/packages/babel-plugin-transform-typescript/test/fixtures/imports/import=-module/input.ts:1:1]
|
||||
1 | import lib = require("lib");
|
||||
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
2 | lib();
|
||||
`----
|
||||
help: Consider using 'import * as ns from "mod"',
|
||||
'import {a} from "mod"', 'import d from "mod"', or another
|
||||
module format instead.
|
||||
|
||||
|
||||
* imports/import=-module-to-cjs/input.ts
|
||||
|
||||
! `import lib = require(...);` is only supported when compiling modules
|
||||
| to CommonJS.
|
||||
| Please consider using `import lib from '...';` alongside Typescript's
|
||||
| --allowSyntheticDefaultImports option, or add @babel/plugin-transform-
|
||||
| modules-commonjs to your Babel config.
|
||||
,-[tasks/coverage/babel/packages/babel-plugin-transform-typescript/test/fixtures/imports/import=-module-to-cjs/input.ts:1:1]
|
||||
1 | import lib = require("lib");
|
||||
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
2 | lib();
|
||||
`----
|
||||
|
||||
Missing SymbolId: "lib"
|
||||
Missing ReferenceId: "require"
|
||||
Binding symbols mismatch:
|
||||
after transform: ScopeId(0): [SymbolId(0)]
|
||||
rebuilt : ScopeId(0): [SymbolId(0)]
|
||||
Reference symbol mismatch for "lib":
|
||||
after transform: SymbolId(0) "lib"
|
||||
rebuilt : SymbolId(0) "lib"
|
||||
Unresolved references mismatch:
|
||||
after transform: []
|
||||
rebuilt : ["require"]
|
||||
|
||||
* imports/only-remove-type-imports/input.ts
|
||||
x Output mismatch
|
||||
|
|
|
|||
Loading…
Reference in a new issue