diff --git a/crates/oxc_compat/src/es_features.rs b/crates/oxc_compat/src/es_features.rs index d17f2f0a534f5..8502fde8a858e 100644 --- a/crates/oxc_compat/src/es_features.rs +++ b/crates/oxc_compat/src/es_features.rs @@ -55,6 +55,7 @@ pub enum ESFeature { ES2025RegexpModifiers, ES2026ExplicitResourceManagement, ES2020ExportNamespaceFrom, + ES2020ArbitraryModuleNamespaceNames, } pub fn features() -> &'static FxHashMap { use ESFeature::*; @@ -904,6 +905,23 @@ pub fn features() -> &'static FxHashMap { (Es, Version(2020u16, 0, 0)), ])), ), + ( + ES2020ArbitraryModuleNamespaceNames, + EngineTargets::new(FxHashMap::from_iter([ + (Samsung, Version(15u16, 0u16, 0u16)), + (Node, Version(16u16, 0u16, 0u16)), + (Firefox, Version(87u16, 0u16, 0u16)), + (Chrome, Version(88u16, 0u16, 0u16)), + (Safari, Version(14u16, 1u16, 0u16)), + (Ios, Version(14u16, 5u16, 0u16)), + (Edge, Version(88u16, 0u16, 0u16)), + (OperaMobile, Version(63u16, 0u16, 0u16)), + (Deno, Version(1u16, 6u16, 0u16)), + (Electron, Version(12u16, 0u16, 0u16)), + (Opera, Version(74u16, 0u16, 0u16)), + (Es, Version(2020u16, 0, 0)), + ])), + ), ]) }) } diff --git a/crates/oxc_transformer/src/es2020/mod.rs b/crates/oxc_transformer/src/es2020/mod.rs index 98f5c6f223c80..fb4428e9fde8b 100644 --- a/crates/oxc_transformer/src/es2020/mod.rs +++ b/crates/oxc_transformer/src/es2020/mod.rs @@ -84,4 +84,59 @@ impl<'a> Traverse<'a, TransformState<'a>> for ES2020<'a, '_> { self.ctx.error(warning); } } + + fn enter_import_specifier( + &mut self, + node: &mut ImportSpecifier<'a>, + _ctx: &mut TraverseCtx<'a>, + ) { + if self.options.arbitrary_module_namespace_names + && let ModuleExportName::StringLiteral(literal) = &node.imported + { + let warning = OxcDiagnostic::warn( + "Arbitrary module namespace identifier names are not available in the configured target environment.", + ) + .with_label(literal.span); + self.ctx.error(warning); + } + } + + fn enter_export_specifier( + &mut self, + node: &mut ExportSpecifier<'a>, + _ctx: &mut TraverseCtx<'a>, + ) { + if self.options.arbitrary_module_namespace_names { + if let ModuleExportName::StringLiteral(literal) = &node.exported { + let warning = OxcDiagnostic::warn( + "Arbitrary module namespace identifier names are not available in the configured target environment.", + ) + .with_label(literal.span); + self.ctx.error(warning); + } + if let ModuleExportName::StringLiteral(literal) = &node.local { + let warning = OxcDiagnostic::warn( + "Arbitrary module namespace identifier names are not available in the configured target environment.", + ) + .with_label(literal.span); + self.ctx.error(warning); + } + } + } + + fn enter_export_all_declaration( + &mut self, + node: &mut ExportAllDeclaration<'a>, + _ctx: &mut TraverseCtx<'a>, + ) { + if self.options.arbitrary_module_namespace_names + && let Some(ModuleExportName::StringLiteral(literal)) = &node.exported + { + let warning = OxcDiagnostic::warn( + "Arbitrary module namespace identifier names are not available in the configured target environment.", + ) + .with_label(literal.span); + self.ctx.error(warning); + } + } } diff --git a/crates/oxc_transformer/src/es2020/options.rs b/crates/oxc_transformer/src/es2020/options.rs index 607971cfae425..961f76dc7db29 100644 --- a/crates/oxc_transformer/src/es2020/options.rs +++ b/crates/oxc_transformer/src/es2020/options.rs @@ -14,4 +14,7 @@ pub struct ES2020Options { #[serde(skip)] pub optional_chaining: bool, + + #[serde(skip)] + pub arbitrary_module_namespace_names: bool, } diff --git a/crates/oxc_transformer/src/lib.rs b/crates/oxc_transformer/src/lib.rs index b77fbc4a6b88b..1bbf13405d2d1 100644 --- a/crates/oxc_transformer/src/lib.rs +++ b/crates/oxc_transformer/src/lib.rs @@ -251,6 +251,22 @@ impl<'a> Traverse<'a, TransformState<'a>> for TransformerImpl<'a, '_> { self.x2_es2020.enter_big_int_literal(node, ctx); } + fn enter_import_specifier( + &mut self, + node: &mut ImportSpecifier<'a>, + ctx: &mut TraverseCtx<'a>, + ) { + self.x2_es2020.enter_import_specifier(node, ctx); + } + + fn enter_export_specifier( + &mut self, + node: &mut ExportSpecifier<'a>, + ctx: &mut TraverseCtx<'a>, + ) { + self.x2_es2020.enter_export_specifier(node, ctx); + } + fn enter_binding_identifier( &mut self, node: &mut BindingIdentifier<'a>, @@ -677,6 +693,7 @@ impl<'a> Traverse<'a, TransformState<'a>> for TransformerImpl<'a, '_> { if let Some(typescript) = self.x0_typescript.as_mut() { typescript.enter_export_all_declaration(node, ctx); } + self.x2_es2020.enter_export_all_declaration(node, ctx); } fn enter_export_named_declaration( diff --git a/crates/oxc_transformer/src/options/env.rs b/crates/oxc_transformer/src/options/env.rs index 3d4de7c31564d..55c6b94884993 100644 --- a/crates/oxc_transformer/src/options/env.rs +++ b/crates/oxc_transformer/src/options/env.rs @@ -82,6 +82,7 @@ impl EnvOptions { // Turn this on would throw error for all bigints. big_int: false, optional_chaining: true, + arbitrary_module_namespace_names: false, }, es2021: ES2021Options { logical_assignment_operators: true }, es2022: ES2022Options { @@ -159,6 +160,8 @@ impl From for EnvOptions { nullish_coalescing_operator: o.has_feature(ES2020NullishCoalescingOperator), big_int: o.has_feature(ES2020BigInt), optional_chaining: o.has_feature(ES2020OptionalChaining), + arbitrary_module_namespace_names: o + .has_feature(ES2020ArbitraryModuleNamespaceNames), }, es2021: ES2021Options { logical_assignment_operators: o.has_feature(ES2021LogicalAssignmentOperators), diff --git a/crates/oxc_transformer/src/options/mod.rs b/crates/oxc_transformer/src/options/mod.rs index e475264d9b583..c13e97f70c436 100644 --- a/crates/oxc_transformer/src/options/mod.rs +++ b/crates/oxc_transformer/src/options/mod.rs @@ -233,6 +233,7 @@ impl TryFrom<&BabelOptions> for TransformOptions { nullish_coalescing_operator: options.plugins.nullish_coalescing_operator || env.es2020.nullish_coalescing_operator, big_int: env.es2020.big_int, + arbitrary_module_namespace_names: env.es2020.arbitrary_module_namespace_names, }; let es2021 = ES2021Options { diff --git a/crates/oxc_transformer/tests/integrations/es_target.rs b/crates/oxc_transformer/tests/integrations/es_target.rs index 966d47b02d1c8..96216312eedb5 100644 --- a/crates/oxc_transformer/tests/integrations/es_target.rs +++ b/crates/oxc_transformer/tests/integrations/es_target.rs @@ -21,6 +21,7 @@ fn es_target() { ("es2019", "1n ** 2n"), // test target error ("es2021", "class foo { static {} }"), ("es2021", "class Foo { #a; }"), + ("es2019", r#"export { foo as "string-name" };"#), // test arbitrary module namespace names warning ]; // Test no transformation for esnext. diff --git a/crates/oxc_transformer/tests/integrations/snapshots/es_target.snap b/crates/oxc_transformer/tests/integrations/snapshots/es_target.snap index ce921231728e3..7e69447e2bf0f 100644 --- a/crates/oxc_transformer/tests/integrations/snapshots/es_target.snap +++ b/crates/oxc_transformer/tests/integrations/snapshots/es_target.snap @@ -86,3 +86,14 @@ class Foo { _classPrivateFieldInitSpec(this, _a, void 0); } } + +########## 11 es2019 +export { foo as "string-name" }; +---------- + + ! Arbitrary module namespace identifier names are not available in the + | configured target environment. + ,---- + 1 | export { foo as "string-name" }; + : ^^^^^^^^^^^^^ + `---- diff --git a/tasks/compat_data/custom-compat-data.js b/tasks/compat_data/custom-compat-data.js index 5d291efa5f1bb..5b239d4c08799 100644 --- a/tasks/compat_data/custom-compat-data.js +++ b/tasks/compat_data/custom-compat-data.js @@ -25,6 +25,24 @@ const customEs2020 = [ electron: '5.0', }, }, + { + name: 'ArbitraryModuleNamespaceNames', + // https://github.com/tc39/ecma262/pull/2154 + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#browser_compatibility + targets: { + chrome: '88', + opera: '74', + edge: '88', + firefox: '87', + safari: '14.1', + node: '16.0', + deno: '1.6', + ios: '14.5', + samsung: '15.0', + opera_mobile: '63', + electron: '12.0', + }, + }, ].map(f('ES2020')); module.exports = [...customEs2020]; diff --git a/tasks/compat_data/data.json b/tasks/compat_data/data.json index 2fb5783f2ad72..786e1c983fc21 100644 --- a/tasks/compat_data/data.json +++ b/tasks/compat_data/data.json @@ -1066,5 +1066,22 @@ "electron": "5.0" }, "es": "ES2020" + }, + { + "name": "ArbitraryModuleNamespaceNames", + "targets": { + "chrome": "88", + "opera": "74", + "edge": "88", + "firefox": "87", + "safari": "14.1", + "node": "16.0", + "deno": "1.6", + "ios": "14.5", + "samsung": "15.0", + "opera_mobile": "63", + "electron": "12.0" + }, + "es": "ES2020" } ]