Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions crates/oxc_transformer/src/es2020/export_namespace_from.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
//! ES2020: Export Namespace From
//!
//! This plugin transforms `export * as ns from "mod"` to `import * as _ns from "mod"; export { _ns as ns }`.
//!
//! > This plugin is included in `preset-env`, in ES2020
//!
//! ## Example
//!
//! Input:
//! ```js
//! export * as ns from "mod";
//! ```
//!
//! Output:
//! ```js
//! import * as _ns from "mod";
//! export { _ns as ns };
//! ```
//!
//! ## Implementation
//!
//! Implementation based on [@babel/plugin-transform-export-namespace-from](https://babeljs.io/docs/babel-plugin-transform-export-namespace-from).
//!
//! ## References:
//! * Babel plugin implementation: <https://github.com/babel/babel/tree/v7.28.4/packages/babel-plugin-transform-export-namespace-from>
//! * "export ns from" TC39 proposal: <https://github.com/tc39/proposal-export-ns-from>

use oxc_allocator::TakeIn;
use oxc_ast::{NONE, ast::*};
use oxc_semantic::SymbolFlags;
use oxc_span::SPAN;
use oxc_traverse::Traverse;

use crate::{
context::{TransformCtx, TraverseCtx},
state::TransformState,
};

pub struct ExportNamespaceFrom<'a, 'ctx> {
_ctx: &'ctx TransformCtx<'a>,
}

impl<'a, 'ctx> ExportNamespaceFrom<'a, 'ctx> {
pub fn new(ctx: &'ctx TransformCtx<'a>) -> Self {
Self { _ctx: ctx }
}
}

impl<'a> Traverse<'a, TransformState<'a>> for ExportNamespaceFrom<'a, '_> {
fn exit_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) {
// Early return if there's no `export * as ns from "mod"` to transform
let has_export_namespace = program.body.iter().any(
|stmt| matches!(stmt, Statement::ExportAllDeclaration(decl) if decl.exported.is_some()),
);
if !has_export_namespace {
return;
}

let mut new_statements = ctx.ast.vec_with_capacity(program.body.len());

for stmt in program.body.take_in(ctx.ast) {
match stmt {
Statement::ExportAllDeclaration(export_all) if export_all.exported.is_some() => {
// Transform `export * as ns from "mod"` to:
// `import * as _ns from "mod"; export { _ns as ns };`

let ExportAllDeclaration { span, exported, source, export_kind, .. } =
export_all.unbox();
let exported_name = exported.unwrap();

// Create a unique binding for the import based on the exported name
let binding = ctx.generate_uid_based_on_node(
&exported_name,
program.scope_id(),
SymbolFlags::Import,
);

// Create `import * as _ns from "mod"`
let import_specifier = ImportDeclarationSpecifier::ImportNamespaceSpecifier(
ctx.ast.alloc_import_namespace_specifier(
SPAN,
binding.create_binding_identifier(ctx),
),
);

let import_decl = ctx.ast.alloc_import_declaration(
SPAN,
Some(ctx.ast.vec1(import_specifier)),
source,
None,
NONE,
export_kind,
);
new_statements.push(Statement::ImportDeclaration(import_decl));

// Create `export { _ns as ns }`
let local =
ModuleExportName::IdentifierReference(binding.create_read_reference(ctx));
let export_specifier =
ctx.ast.export_specifier(span, local, exported_name, export_kind);

let export_named_decl = ctx.ast.alloc_export_named_declaration(
span,
None,
ctx.ast.vec1(export_specifier),
None,
export_kind,
NONE,
);
new_statements.push(Statement::ExportNamedDeclaration(export_named_decl));
}
_ => {
new_statements.push(stmt);
}
}
}

program.body = new_statements;
}
}
10 changes: 10 additions & 0 deletions crates/oxc_transformer/src/es2020/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ use crate::{
state::TransformState,
};

mod export_namespace_from;
mod nullish_coalescing_operator;
mod optional_chaining;
mod options;
use export_namespace_from::ExportNamespaceFrom;
use nullish_coalescing_operator::NullishCoalescingOperator;
pub use optional_chaining::OptionalChaining;
pub use options::ES2020Options;
Expand All @@ -19,6 +21,7 @@ pub struct ES2020<'a, 'ctx> {
options: ES2020Options,

// Plugins
export_namespace_from: ExportNamespaceFrom<'a, 'ctx>,
nullish_coalescing_operator: NullishCoalescingOperator<'a, 'ctx>,
optional_chaining: OptionalChaining<'a, 'ctx>,
}
Expand All @@ -28,13 +31,20 @@ impl<'a, 'ctx> ES2020<'a, 'ctx> {
Self {
ctx,
options,
export_namespace_from: ExportNamespaceFrom::new(ctx),
nullish_coalescing_operator: NullishCoalescingOperator::new(ctx),
optional_chaining: OptionalChaining::new(ctx),
}
}
}

impl<'a> Traverse<'a, TransformState<'a>> for ES2020<'a, '_> {
fn exit_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) {
if self.options.export_namespace_from {
self.export_namespace_from.exit_program(program, ctx);
}
}

fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
if self.options.nullish_coalescing_operator {
self.nullish_coalescing_operator.enter_expression(expr, ctx);
Expand Down
3 changes: 3 additions & 0 deletions crates/oxc_transformer/src/es2020/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ use serde::Deserialize;
#[derive(Debug, Default, Clone, Copy, Deserialize)]
#[serde(default, rename_all = "camelCase", deny_unknown_fields)]
pub struct ES2020Options {
#[serde(skip)]
pub export_namespace_from: bool,

#[serde(skip)]
pub nullish_coalescing_operator: bool,

Expand Down
1 change: 1 addition & 0 deletions crates/oxc_transformer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ impl<'a> Traverse<'a, TransformState<'a>> for TransformerImpl<'a, '_> {
typescript.exit_program(program, ctx);
}
self.x2_es2022.exit_program(program, ctx);
self.x2_es2020.exit_program(program, ctx);
self.x2_es2018.exit_program(program, ctx);
self.common.exit_program(program, ctx);
}
Expand Down
2 changes: 2 additions & 0 deletions crates/oxc_transformer/src/options/babel/plugins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ pub struct BabelPlugins {
// ES2019
pub optional_catch_binding: bool,
// ES2020
pub export_namespace_from: bool,
pub optional_chaining: bool,
pub nullish_coalescing_operator: bool,
// ES2021
Expand Down Expand Up @@ -148,6 +149,7 @@ impl TryFrom<PluginPresetEntries> for BabelPlugins {
}
"transform-async-generator-functions" => p.async_generator_functions = true,
"transform-optional-catch-binding" => p.optional_catch_binding = true,
"transform-export-namespace-from" => p.export_namespace_from = true,
"transform-optional-chaining" => p.optional_chaining = true,
"transform-nullish-coalescing-operator" => p.nullish_coalescing_operator = true,
"transform-logical-assignment-operators" => p.logical_assignment_operators = true,
Expand Down
2 changes: 2 additions & 0 deletions crates/oxc_transformer/src/options/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ impl EnvOptions {
},
es2019: ES2019Options { optional_catch_binding: true },
es2020: ES2020Options {
export_namespace_from: true,
nullish_coalescing_operator: true,
// Turn this on would throw error for all bigints.
big_int: false,
Expand Down Expand Up @@ -154,6 +155,7 @@ impl From<EngineTargets> for EnvOptions {
optional_catch_binding: o.has_feature(ES2019OptionalCatchBinding),
},
es2020: ES2020Options {
export_namespace_from: o.has_feature(ES2020ExportNamespaceFrom),
nullish_coalescing_operator: o.has_feature(ES2020NullishCoalescingOperator),
big_int: o.has_feature(ES2020BigInt),
optional_chaining: o.has_feature(ES2020OptionalChaining),
Expand Down
2 changes: 2 additions & 0 deletions crates/oxc_transformer/src/options/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,8 @@ impl TryFrom<&BabelOptions> for TransformOptions {
};

let es2020 = ES2020Options {
export_namespace_from: options.plugins.export_namespace_from
|| env.es2020.export_namespace_from,
optional_chaining: options.plugins.optional_chaining || env.es2020.optional_chaining,
nullish_coalescing_operator: options.plugins.nullish_coalescing_operator
|| env.es2020.nullish_coalescing_operator,
Expand Down
10 changes: 10 additions & 0 deletions crates/oxc_traverse/src/ast_operations/gather_node_parts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,16 @@ impl<'a> GatherNodeParts<'a> for ExportSpecifier<'a> {
}
}

impl<'a> GatherNodeParts<'a> for ModuleExportName<'a> {
fn gather<F: FnMut(&str)>(&self, f: &mut F) {
match self {
ModuleExportName::IdentifierName(ident) => ident.gather(f),
ModuleExportName::IdentifierReference(ident) => ident.gather(f),
ModuleExportName::StringLiteral(lit) => lit.gather(f),
}
}
}

impl<'a> GatherNodeParts<'a> for ImportSpecifier<'a> {
fn gather<F: FnMut(&str)>(&self, f: &mut F) {
self.local.gather(f);
Expand Down
8 changes: 4 additions & 4 deletions tasks/coverage/snapshots/semantic_typescript.snap
Original file line number Diff line number Diff line change
Expand Up @@ -7435,8 +7435,8 @@ rebuilt : []

semantic Error: tasks/coverage/typescript/tests/cases/compiler/declarationEmitDoesNotUseReexportedNamespaceAsLocal.ts
Bindings mismatch:
after transform: ScopeId(0): ["add", "x"]
rebuilt : ScopeId(0): ["x"]
after transform: ScopeId(0): ["_Q", "add", "x"]
rebuilt : ScopeId(0): ["_Q", "x"]
Scope children mismatch:
after transform: ScopeId(0): [ScopeId(1)]
rebuilt : ScopeId(0): []
Expand Down Expand Up @@ -37758,8 +37758,8 @@ rebuilt : ["x"]

semantic Error: tasks/coverage/typescript/tests/cases/conformance/es2022/arbitraryModuleNamespaceIdentifiers/arbitraryModuleNamespaceIdentifiers_module.ts
Bindings mismatch:
after transform: ScopeId(0): ["importStarTestA", "importTest", "reimportTest", "someValue", "typeA", "typeB", "typeC", "valueX", "valueY", "valueZ"]
rebuilt : ScopeId(0): ["importStarTestA", "importTest", "reimportTest", "someValue", "valueX", "valueY", "valueZ"]
after transform: ScopeId(0): ["_Z", "importStarTestA", "importTest", "reimportTest", "someValue", "typeA", "typeB", "typeC", "valueX", "valueY", "valueZ"]
rebuilt : ScopeId(0): ["_Z", "importStarTestA", "importTest", "reimportTest", "someValue", "valueX", "valueY", "valueZ"]
Scope children mismatch:
after transform: ScopeId(0): [ScopeId(1), ScopeId(2)]
rebuilt : ScopeId(0): []
Expand Down
11 changes: 3 additions & 8 deletions tasks/transform_conformance/snapshots/babel.snap.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
commit: 41d96516

Passed: 706/1213
Passed: 712/1217

# All Passed:
* babel-plugin-transform-logical-assignment-operators
* babel-plugin-transform-export-namespace-from
* babel-plugin-transform-optional-catch-binding
* babel-plugin-transform-react-display-name
* babel-plugin-transform-react-jsx-self
* babel-plugin-transform-react-jsx-source


# babel-preset-env (43/130)
# babel-preset-env (45/130)
* dynamic-import/auto-esm-unsupported-import-unsupported/input.mjs
x Output mismatch

Expand Down Expand Up @@ -47,12 +48,6 @@ x Output mismatch
* export-namespace-from/auto-export-namespace-not-supported/input.mjs
x Output mismatch

* export-namespace-from/false-export-namespace-not-supported/input.mjs
x Output mismatch

* export-namespace-from/false-export-namespace-not-supported-caller-supported/input.mjs
x Output mismatch

* modules/auto-cjs/input.mjs
x Output mismatch

Expand Down
2 changes: 1 addition & 1 deletion tasks/transform_conformance/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub const PLUGINS: &[&str] = &[
"babel-plugin-transform-logical-assignment-operators",
// "babel-plugin-transform-numeric-separator",
// ES2020
// "babel-plugin-transform-export-namespace-from",
"babel-plugin-transform-export-namespace-from",
// "babel-plugin-transform-dynamic-import",
"babel-plugin-transform-nullish-coalescing-operator",
"babel-plugin-transform-optional-chaining",
Expand Down
Loading