feat(transformer/typescript): support transform export = and import = require(...) when module is commonjs (#7206)

close: #7141
This commit is contained in:
Dunqing 2024-11-09 09:32:28 +00:00
parent fc86703933
commit 5cfdc05d06
7 changed files with 390 additions and 1098 deletions

View file

@ -29,7 +29,6 @@ pub struct TransformCtx<'a> {
pub source_text: &'a str,
#[expect(unused)]
pub module: Module,
// Helpers

View file

@ -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 {

View file

@ -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);
}
}

View file

@ -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)
}

View file

@ -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

View file

@ -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