Skip to content

Commit da4b742

Browse files
committed
feat(transformer): add ES2020 export namespace from transformation
1 parent be53dad commit da4b742

File tree

10 files changed

+136
-13
lines changed

10 files changed

+136
-13
lines changed
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
//! ES2020: Export Namespace From
2+
//!
3+
//! This plugin transforms `export * as ns from "mod"` to `import * as _ns from "mod"; export { _ns as ns }`.
4+
//!
5+
//! > This plugin is included in `preset-env`, in ES2020
6+
//!
7+
//! ## Example
8+
//!
9+
//! Input:
10+
//! ```js
11+
//! export * as ns from "mod";
12+
//! ```
13+
//!
14+
//! Output:
15+
//! ```js
16+
//! import * as _ns from "mod";
17+
//! export { _ns as ns };
18+
//! ```
19+
//!
20+
//! ## Implementation
21+
//!
22+
//! Implementation based on [@babel/plugin-transform-export-namespace-from](https://babeljs.io/docs/babel-plugin-transform-export-namespace-from).
23+
//!
24+
//! ## References:
25+
//! * Babel plugin implementation: <https://github.com/babel/babel/tree/v7.28.4/packages/babel-plugin-transform-export-namespace-from>
26+
//! * "export ns from" TC39 proposal: <https://github.com/tc39/proposal-export-ns-from>
27+
28+
use oxc_allocator::TakeIn;
29+
use oxc_ast::{NONE, ast::*};
30+
use oxc_semantic::SymbolFlags;
31+
use oxc_span::SPAN;
32+
use oxc_traverse::{Traverse, ast_operations::to_identifier};
33+
34+
use crate::{context::TraverseCtx, state::TransformState};
35+
36+
pub struct ExportNamespaceFrom<'a, 'ctx> {
37+
_ctx: &'ctx crate::context::TransformCtx<'a>,
38+
}
39+
40+
impl<'a, 'ctx> ExportNamespaceFrom<'a, 'ctx> {
41+
pub fn new(ctx: &'ctx crate::context::TransformCtx<'a>) -> Self {
42+
Self { _ctx: ctx }
43+
}
44+
}
45+
46+
impl<'a> Traverse<'a, TransformState<'a>> for ExportNamespaceFrom<'a, '_> {
47+
fn exit_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) {
48+
let mut new_statements = ctx.ast.vec_with_capacity(program.body.len());
49+
50+
for stmt in program.body.take_in(ctx.ast) {
51+
match stmt {
52+
Statement::ExportAllDeclaration(export_all) if export_all.exported.is_some() => {
53+
// Transform `export * as ns from "mod"` to:
54+
// `import * as _ns from "mod"; export { _ns as ns };`
55+
56+
let ExportAllDeclaration { span, exported, source, export_kind, .. } =
57+
export_all.unbox();
58+
59+
// Create a unique binding for the import
60+
let binding = ctx.generate_uid(
61+
&to_identifier(exported.as_ref().unwrap().name().to_string()),
62+
program.scope_id(),
63+
SymbolFlags::Import,
64+
);
65+
66+
// Create `import * as _ns from "mod"`
67+
let import_specifier = ImportDeclarationSpecifier::ImportNamespaceSpecifier(
68+
ctx.ast.alloc_import_namespace_specifier(
69+
SPAN,
70+
binding.create_binding_identifier(ctx),
71+
),
72+
);
73+
74+
let import_decl = ctx.ast.alloc_import_declaration(
75+
SPAN,
76+
Some(ctx.ast.vec1(import_specifier)),
77+
source,
78+
None,
79+
NONE,
80+
export_kind,
81+
);
82+
new_statements.push(Statement::ImportDeclaration(import_decl));
83+
84+
// Create `export { _ns as ns }`
85+
let local =
86+
ModuleExportName::IdentifierReference(binding.create_read_reference(ctx));
87+
let export_specifier =
88+
ctx.ast.export_specifier(span, local, exported.unwrap(), export_kind);
89+
90+
let export_named_decl = ctx.ast.alloc_export_named_declaration(
91+
SPAN,
92+
None,
93+
ctx.ast.vec1(export_specifier),
94+
None,
95+
export_kind,
96+
NONE,
97+
);
98+
new_statements.push(Statement::ExportNamedDeclaration(export_named_decl));
99+
}
100+
_ => {
101+
new_statements.push(stmt);
102+
}
103+
}
104+
}
105+
106+
program.body = new_statements;
107+
}
108+
}

crates/oxc_transformer/src/es2020/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ use crate::{
77
state::TransformState,
88
};
99

10+
mod export_namespace_from;
1011
mod nullish_coalescing_operator;
1112
mod optional_chaining;
1213
mod options;
14+
use export_namespace_from::ExportNamespaceFrom;
1315
use nullish_coalescing_operator::NullishCoalescingOperator;
1416
pub use optional_chaining::OptionalChaining;
1517
pub use options::ES2020Options;
@@ -19,6 +21,7 @@ pub struct ES2020<'a, 'ctx> {
1921
options: ES2020Options,
2022

2123
// Plugins
24+
export_namespace_from: ExportNamespaceFrom<'a, 'ctx>,
2225
nullish_coalescing_operator: NullishCoalescingOperator<'a, 'ctx>,
2326
optional_chaining: OptionalChaining<'a, 'ctx>,
2427
}
@@ -28,13 +31,20 @@ impl<'a, 'ctx> ES2020<'a, 'ctx> {
2831
Self {
2932
ctx,
3033
options,
34+
export_namespace_from: ExportNamespaceFrom::new(ctx),
3135
nullish_coalescing_operator: NullishCoalescingOperator::new(ctx),
3236
optional_chaining: OptionalChaining::new(ctx),
3337
}
3438
}
3539
}
3640

3741
impl<'a> Traverse<'a, TransformState<'a>> for ES2020<'a, '_> {
42+
fn exit_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) {
43+
if self.options.export_namespace_from {
44+
self.export_namespace_from.exit_program(program, ctx);
45+
}
46+
}
47+
3848
fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
3949
if self.options.nullish_coalescing_operator {
4050
self.nullish_coalescing_operator.enter_expression(expr, ctx);

crates/oxc_transformer/src/es2020/options.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ use serde::Deserialize;
33
#[derive(Debug, Default, Clone, Copy, Deserialize)]
44
#[serde(default, rename_all = "camelCase", deny_unknown_fields)]
55
pub struct ES2020Options {
6+
#[serde(skip)]
7+
pub export_namespace_from: bool,
8+
69
#[serde(skip)]
710
pub nullish_coalescing_operator: bool,
811

crates/oxc_transformer/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ impl<'a> Traverse<'a, TransformState<'a>> for TransformerImpl<'a, '_> {
212212
typescript.exit_program(program, ctx);
213213
}
214214
self.x2_es2022.exit_program(program, ctx);
215+
self.x2_es2020.exit_program(program, ctx);
215216
self.x2_es2018.exit_program(program, ctx);
216217
self.common.exit_program(program, ctx);
217218
}

crates/oxc_transformer/src/options/babel/plugins.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ pub struct BabelPlugins {
6363
// ES2019
6464
pub optional_catch_binding: bool,
6565
// ES2020
66+
pub export_namespace_from: bool,
6667
pub optional_chaining: bool,
6768
pub nullish_coalescing_operator: bool,
6869
// ES2021
@@ -148,6 +149,7 @@ impl TryFrom<PluginPresetEntries> for BabelPlugins {
148149
}
149150
"transform-async-generator-functions" => p.async_generator_functions = true,
150151
"transform-optional-catch-binding" => p.optional_catch_binding = true,
152+
"transform-export-namespace-from" => p.export_namespace_from = true,
151153
"transform-optional-chaining" => p.optional_chaining = true,
152154
"transform-nullish-coalescing-operator" => p.nullish_coalescing_operator = true,
153155
"transform-logical-assignment-operators" => p.logical_assignment_operators = true,

crates/oxc_transformer/src/options/env.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ impl EnvOptions {
7474
},
7575
es2019: ES2019Options { optional_catch_binding: true },
7676
es2020: ES2020Options {
77+
export_namespace_from: true,
7778
nullish_coalescing_operator: true,
7879
// Turn this on would throw error for all bigints.
7980
big_int: false,
@@ -150,6 +151,7 @@ impl From<EngineTargets> for EnvOptions {
150151
optional_catch_binding: o.has_feature(ES2019OptionalCatchBinding),
151152
},
152153
es2020: ES2020Options {
154+
export_namespace_from: o.has_feature(ES2020ExportNamespaceFrom),
153155
nullish_coalescing_operator: o.has_feature(ES2020NullishCoalescingOperator),
154156
big_int: o.has_feature(ES2020BigInt),
155157
optional_chaining: o.has_feature(ES2020OptionalChaining),

crates/oxc_transformer/src/options/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,8 @@ impl TryFrom<&BabelOptions> for TransformOptions {
226226
};
227227

228228
let es2020 = ES2020Options {
229+
export_namespace_from: options.plugins.export_namespace_from
230+
|| env.es2020.export_namespace_from,
229231
optional_chaining: options.plugins.optional_chaining || env.es2020.optional_chaining,
230232
nullish_coalescing_operator: options.plugins.nullish_coalescing_operator
231233
|| env.es2020.nullish_coalescing_operator,

tasks/coverage/snapshots/semantic_typescript.snap

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7435,8 +7435,8 @@ rebuilt : []
74357435

74367436
semantic Error: tasks/coverage/typescript/tests/cases/compiler/declarationEmitDoesNotUseReexportedNamespaceAsLocal.ts
74377437
Bindings mismatch:
7438-
after transform: ScopeId(0): ["add", "x"]
7439-
rebuilt : ScopeId(0): ["x"]
7438+
after transform: ScopeId(0): ["_Q", "add", "x"]
7439+
rebuilt : ScopeId(0): ["_Q", "x"]
74407440
Scope children mismatch:
74417441
after transform: ScopeId(0): [ScopeId(1)]
74427442
rebuilt : ScopeId(0): []
@@ -37758,8 +37758,8 @@ rebuilt : ["x"]
3775837758

3775937759
semantic Error: tasks/coverage/typescript/tests/cases/conformance/es2022/arbitraryModuleNamespaceIdentifiers/arbitraryModuleNamespaceIdentifiers_module.ts
3776037760
Bindings mismatch:
37761-
after transform: ScopeId(0): ["importStarTestA", "importTest", "reimportTest", "someValue", "typeA", "typeB", "typeC", "valueX", "valueY", "valueZ"]
37762-
rebuilt : ScopeId(0): ["importStarTestA", "importTest", "reimportTest", "someValue", "valueX", "valueY", "valueZ"]
37761+
after transform: ScopeId(0): ["_Z", "importStarTestA", "importTest", "reimportTest", "someValue", "typeA", "typeB", "typeC", "valueX", "valueY", "valueZ"]
37762+
rebuilt : ScopeId(0): ["_Z", "importStarTestA", "importTest", "reimportTest", "someValue", "valueX", "valueY", "valueZ"]
3776337763
Scope children mismatch:
3776437764
after transform: ScopeId(0): [ScopeId(1), ScopeId(2)]
3776537765
rebuilt : ScopeId(0): []

tasks/transform_conformance/snapshots/babel.snap.md

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
commit: 41d96516
22

3-
Passed: 706/1213
3+
Passed: 712/1217
44

55
# All Passed:
66
* babel-plugin-transform-logical-assignment-operators
7+
* babel-plugin-transform-export-namespace-from
78
* babel-plugin-transform-optional-catch-binding
89
* babel-plugin-transform-react-display-name
910
* babel-plugin-transform-react-jsx-self
1011
* babel-plugin-transform-react-jsx-source
1112

1213

13-
# babel-preset-env (43/130)
14+
# babel-preset-env (45/130)
1415
* dynamic-import/auto-esm-unsupported-import-unsupported/input.mjs
1516
x Output mismatch
1617

@@ -47,12 +48,6 @@ x Output mismatch
4748
* export-namespace-from/auto-export-namespace-not-supported/input.mjs
4849
x Output mismatch
4950

50-
* export-namespace-from/false-export-namespace-not-supported/input.mjs
51-
x Output mismatch
52-
53-
* export-namespace-from/false-export-namespace-not-supported-caller-supported/input.mjs
54-
x Output mismatch
55-
5651
* modules/auto-cjs/input.mjs
5752
x Output mismatch
5853

tasks/transform_conformance/src/constants.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ pub const PLUGINS: &[&str] = &[
1414
"babel-plugin-transform-logical-assignment-operators",
1515
// "babel-plugin-transform-numeric-separator",
1616
// ES2020
17-
// "babel-plugin-transform-export-namespace-from",
17+
"babel-plugin-transform-export-namespace-from",
1818
// "babel-plugin-transform-dynamic-import",
1919
"babel-plugin-transform-nullish-coalescing-operator",
2020
"babel-plugin-transform-optional-chaining",

0 commit comments

Comments
 (0)