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
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/swc_ecma_compat_es2020/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ swc_common = { version = "17.0.1", path = "../swc_common" }
swc_ecma_ast = { version = "18.0.0", path = "../swc_ecma_ast" }
swc_ecma_compat_es2022 = { version = "31.0.0", path = "../swc_ecma_compat_es2022" }
swc_ecma_compiler = { version = "8.0.0", path = "../swc_ecma_compiler" }
swc_ecma_transformer = { version = "0.1.0", path = "../swc_ecma_transformer" }
swc_ecma_transforms_base = { version = "30.0.0", path = "../swc_ecma_transforms_base" }
swc_ecma_utils = { version = "24.0.0", path = "../swc_ecma_utils" }
swc_ecma_visit = { version = "18.0.1", path = "../swc_ecma_visit" }
Expand Down
8 changes: 3 additions & 5 deletions crates/swc_ecma_compat_es2020/src/export_namespace_from.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
use swc_ecma_ast::Pass;
use swc_ecma_compiler::{Compiler, Features};

pub fn export_namespace_from() -> impl Pass {
Compiler::new(swc_ecma_compiler::Config {
includes: Features::EXPORT_NAMESPACE_FROM,
..Default::default()
})
let mut options = swc_ecma_transformer::Options::default();
options.env.es2020.export_namespace_from = true;
options.into_pass()
}
13 changes: 8 additions & 5 deletions crates/swc_ecma_preset_env/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,10 @@ where
pass,
/* ES2022 */ | PrivatePropertyInObject
/* ES2021 */ | LogicalAssignmentOperators
/* ES2020 */ | ExportNamespaceFrom
);
if !caniuse(Feature::ExportNamespaceFrom) {
options.env.es2020.export_namespace_from = true;
}

// ES2020
let pass = add!(
Expand Down Expand Up @@ -209,6 +211,9 @@ where
// ES2016
let pass = add!(pass, ExponentiationOperator, es2016::exponentiation());

// Single-pass compiler
let pass = (pass, options.into_pass());

// ES2015
let pass = add!(pass, BlockScopedFunctions, es2015::block_scoped_functions());
let pass = add!(
Expand Down Expand Up @@ -325,13 +330,11 @@ where
bugfixes::template_literal_caching()
);

let pass = add!(
add!(
pass,
BugfixSafariIdDestructuringCollisionInFunctionExpression,
bugfixes::safari_id_destructuring_collision_in_function_expression()
);

(pass, options.into_pass())
)
}

pub fn transform_from_env<C>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import "core-js/modules/es.regexp.exec.js";
import "core-js/modules/es.regexp.to-string.js";
var a = RegExp("(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})", "u");
var b = RegExp(".", "s");
var c = new RegExp(".", "imsuy");
var c = RegExp(".", "imsuy");
console.log(a.unicode);
console.log(b.dotAll);
console.log(c.sticky);
Expand Down
1 change: 1 addition & 0 deletions crates/swc_ecma_transformer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ version = "0.1.0"
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(swc_ast_unknown)'] }

[dependencies]
swc_atoms = { version = "9.0.0", path = "../swc_atoms" }
swc_common = { version = "17.0.1", path = "../swc_common" }
swc_ecma_ast = { version = "18.0.0", path = "../swc_ecma_ast" }
swc_ecma_hooks = { version = "0.2.0", path = "../swc_ecma_hooks" }
Expand Down
132 changes: 132 additions & 0 deletions crates/swc_ecma_transformer/src/es2020/export_namespace_from.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
use swc_ecma_ast::*;
use swc_ecma_hooks::VisitMutHook;
use swc_ecma_utils::private_ident;

use crate::{utils::normalize_module_export_name, TraverseCtx};

pub fn hook() -> impl VisitMutHook<TraverseCtx> {
ExportNamespaceFromPass
}

struct ExportNamespaceFromPass;

impl ExportNamespaceFromPass {
fn transform_export_namespace_from(&mut self, items: &mut Vec<ModuleItem>) {
let count = items
.iter()
.filter(|m| {
matches!(m, ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(NamedExport {
specifiers,
src: Some(..),
type_only: false,
..
})) if specifiers.iter().any(|s| s.is_namespace()))
})
.count();

if count == 0 {
return;
}

let mut stmts = Vec::<ModuleItem>::with_capacity(items.len() + count);

for item in items.drain(..) {
match item {
ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(NamedExport {
span,
specifiers,
src: Some(src),
type_only: false,
with,
})) if specifiers.iter().any(|s| s.is_namespace()) => {
let mut origin_specifiers = Vec::new();

let mut import_specifiers = Vec::new();
let mut export_specifiers = Vec::new();

for s in specifiers.into_iter() {
match s {
ExportSpecifier::Namespace(ExportNamespaceSpecifier { span, name }) => {
let local_bridge = private_ident!(format!(
"_{}",
normalize_module_export_name(&name)
));

import_specifiers.push(ImportSpecifier::Namespace(
ImportStarAsSpecifier {
span,
local: local_bridge.clone(),
},
));
export_specifiers.push(ExportSpecifier::Named(
ExportNamedSpecifier {
span,
orig: local_bridge.into(),
exported: Some(name),
is_type_only: false,
},
))
}
ExportSpecifier::Default(..) | ExportSpecifier::Named(..) => {
origin_specifiers.push(s);
}
#[cfg(swc_ast_unknown)]
_ => panic!("unable to access unknown nodes"),
}
}

stmts.push(
ImportDecl {
span,
specifiers: import_specifiers,
src: src.clone(),
type_only: false,
with: with.clone(),
phase: Default::default(),
}
.into(),
);

stmts.push(
NamedExport {
span,
specifiers: export_specifiers,
src: None,
type_only: false,
with: None,
}
.into(),
);

if !origin_specifiers.is_empty() {
stmts.push(
NamedExport {
span,
specifiers: origin_specifiers,
src: Some(src),
type_only: false,
with,
}
.into(),
);
}
}
_ => {
stmts.push(item);
}
}
}

*items = stmts;
}
}

impl VisitMutHook<TraverseCtx> for ExportNamespaceFromPass {
fn exit_program(&mut self, node: &mut Program, _: &mut TraverseCtx) {
let Program::Module(module) = node else {
return;
};

self.transform_export_namespace_from(&mut module.body);
}
}
20 changes: 11 additions & 9 deletions crates/swc_ecma_transformer/src/es2020/mod.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
use swc_ecma_hooks::VisitMutHook;

use crate::TraverseCtx;
use crate::{hook_utils::OptionalHook, TraverseCtx};

mod export_namespace_from;

#[derive(Debug, Default)]
#[non_exhaustive]
pub struct Es2020Options {}

pub fn hook(options: Es2020Options) -> impl VisitMutHook<TraverseCtx> {
Es2020Pass { options }
pub struct Es2020Options {
pub export_namespace_from: bool,
}

struct Es2020Pass {
options: Es2020Options,
pub fn hook(options: Es2020Options) -> impl VisitMutHook<TraverseCtx> {
OptionalHook(if options.export_namespace_from {
Some(self::export_namespace_from::hook())
} else {
None
})
}

impl VisitMutHook<TraverseCtx> for Es2020Pass {}
1 change: 1 addition & 0 deletions crates/swc_ecma_transformer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ mod jsx;
mod options;
mod regexp;
mod typescript;
mod utils;

pub struct TraverseCtx {}

Expand Down
20 changes: 20 additions & 0 deletions crates/swc_ecma_transformer/src/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use std::borrow::Cow;

use swc_atoms::Atom;
use swc_ecma_ast::*;

pub(crate) fn normalize_module_export_name(module_export_name: &ModuleExportName) -> Cow<Atom> {
match module_export_name {
ModuleExportName::Ident(Ident { sym: name, .. }) => Cow::Borrowed(name),
ModuleExportName::Str(Str { value: name, .. }) => {
// Normally, the export name should be valid UTF-8. But it might also contain
// unpaired surrogates. Node would give an error in this case:
// `SyntaxError: Invalid module export name: contains unpaired
// surrogate`. Here, we temporarily replace the unpaired surrogates
// with U+FFFD REPLACEMENT CHARACTER by using Wtf8::to_string_lossy.
Cow::Owned(Atom::from(name.to_string_lossy()))
}
#[cfg(swc_ast_unknown)]
_ => panic!("unable to access unknown nodes"),
}
}
Loading