Skip to content

Commit

Permalink
feat(transformer/typescript): support transform export = and import =…
Browse files Browse the repository at this point in the history
… require(...) when module is commonjs
  • Loading branch information
Dunqing committed Nov 9, 2024
1 parent 4c48826 commit c04fcf3
Show file tree
Hide file tree
Showing 6 changed files with 193 additions and 204 deletions.
21 changes: 16 additions & 5 deletions crates/oxc_transformer/src/typescript/diagnostics.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
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.")
.with_label(span)
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 {
Expand Down
9 changes: 1 addition & 8 deletions crates/oxc_transformer/src/typescript/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,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>) {
Expand Down Expand Up @@ -298,12 +299,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);
}
}
77 changes: 50 additions & 27 deletions crates/oxc_transformer/src/typescript/module.rs
Original file line number Diff line number Diff line change
@@ -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> {
Expand All @@ -16,38 +17,62 @@ 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_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>) {
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);
if let Declaration::TSImportEqualsDeclaration(import_equals) = decl {
if import_equals.import_kind.is_value() {
*decl = self.transform_ts_import_equals(import_equals, ctx);
}
_ => {}
}
}
}

fn enter_ts_export_assignment(
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>,
) {
if self.ctx.source_type.is_module() {
self.ctx
.error(super::diagnostics::export_assignment_unsupported(export_assignment.span));
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)
}
}

impl<'a, 'ctx> TypeScriptModule<'a, 'ctx> {
/// Transform TSImportEqualsDeclaration to a VariableDeclaration.
///
/// ```TypeScript
/// import module = require('module');
/// import AliasModule = LongNameModule;
///
/// ```JavaScript
/// var module = require('module');
/// var AliasModule = LongNameModule;
/// ```
fn transform_ts_import_equals(
&self,
decl: &mut TSImportEqualsDeclaration<'a>,
Expand All @@ -65,10 +90,8 @@ impl<'a, 'ctx> TypeScriptModule<'a, 'ctx> {
self.transform_ts_type_name(&mut *type_name.to_ts_type_name_mut(), ctx)
}
TSModuleReference::ExternalModuleReference(reference) => {
if self.ctx.source_type.is_module() {
self.ctx.error(super::diagnostics::import_equals_require_unsupported(
decl_span,
));
if self.ctx.module.is_esm() {
self.ctx.error(diagnostics::import_equals_cannot_be_used_in_esm(decl_span));
}

let callee = ctx.ast.expression_identifier_reference(SPAN, "require");
Expand Down
4 changes: 2 additions & 2 deletions tasks/coverage/snapshots/semantic_babel.snap
Original file line number Diff line number Diff line change
Expand Up @@ -1231,13 +1231,13 @@ Please consider using `import lib from '...';` alongside Typescript's --allowSyn

tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/import/equals-require-in-unambiguous/input.ts
semantic error: Missing SymbolId: "a"
Missing ReferenceId: "require"
Missing ReferenceId: "requires"
Binding symbols mismatch:
after transform: ScopeId(0): [SymbolId(0)]
rebuilt : ScopeId(0): [SymbolId(0)]
Unresolved references mismatch:
after transform: []
rebuilt : ["require"]
rebuilt : ["requires"]

tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/import/export-import/input.ts
semantic error: Missing SymbolId: "A"
Expand Down
Loading

0 comments on commit c04fcf3

Please sign in to comment.