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
3 changes: 1 addition & 2 deletions crates/swc_ecma_compat_es2020/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,9 @@ pub fn es2020(config: Config, unresolved_mark: Mark) -> impl Pass {
assumptions.no_document_all = config.nullish_coalescing.no_document_all;

(
nullish_coalescing(config.nullish_coalescing),
optional_chaining(config.optional_chaining, unresolved_mark),
Compiler::new(swc_ecma_compiler::Config {
includes: Features::EXPORT_NAMESPACE_FROM,
includes: Features::EXPORT_NAMESPACE_FROM | Features::NULLISH_COALESCING,
assumptions,
..Default::default()
}),
Expand Down
268 changes: 15 additions & 253 deletions crates/swc_ecma_compat_es2020/src/nullish_coalescing.rs
Original file line number Diff line number Diff line change
@@ -1,264 +1,26 @@
use std::mem::take;

use serde::Deserialize;
use swc_common::{util::take::Take, Span, DUMMY_SP};
use swc_ecma_ast::*;
use swc_ecma_utils::{alias_ident_for_simple_assign_tatget, alias_if_required, StmtLike};
use swc_ecma_visit::{noop_visit_mut_type, visit_mut_pass, VisitMut, VisitMutWith};

pub fn nullish_coalescing(c: Config) -> impl Pass + 'static {
visit_mut_pass(NullishCoalescing {
c,
..Default::default()
})
}

#[derive(Debug, Default)]
struct NullishCoalescing {
vars: Vec<VarDeclarator>,
c: Config,
}
use swc_ecma_ast::Pass;
use swc_ecma_compiler::{Compiler, Features};
use swc_ecma_transforms_base::assumptions::Assumptions;

/// Configuration for nullish coalescing transformation
#[derive(Debug, Clone, Copy, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Config {
#[serde(default)]
pub no_document_all: bool,
}

impl NullishCoalescing {
fn visit_mut_stmt_like<T>(&mut self, stmts: &mut Vec<T>)
where
T: VisitMutWith<Self> + StmtLike,
{
let mut buf = Vec::with_capacity(stmts.len() + 2);

for mut stmt in stmts.take() {
stmt.visit_mut_with(self);

if !self.vars.is_empty() {
buf.push(T::from(
VarDecl {
span: DUMMY_SP,
kind: VarDeclKind::Var,
decls: take(&mut self.vars),
declare: false,
..Default::default()
}
.into(),
));
}

buf.push(stmt);
}

*stmts = buf
}
}

impl VisitMut for NullishCoalescing {
noop_visit_mut_type!(fail);

/// Prevents #1123
fn visit_mut_block_stmt(&mut self, s: &mut BlockStmt) {
let old_vars = self.vars.take();
s.visit_mut_children_with(self);
self.vars = old_vars;
}

/// Prevents #1123
fn visit_mut_switch_case(&mut self, s: &mut SwitchCase) {
// Prevents #6328
s.test.visit_mut_with(self);
let old_vars = self.vars.take();
s.cons.visit_mut_with(self);
self.vars = old_vars;
}

fn visit_mut_module_items(&mut self, n: &mut Vec<ModuleItem>) {
self.visit_mut_stmt_like(n)
}

fn visit_mut_stmts(&mut self, n: &mut Vec<Stmt>) {
self.visit_mut_stmt_like(n)
}

fn visit_mut_expr(&mut self, e: &mut Expr) {
e.visit_mut_children_with(self);

match e {
Expr::Bin(BinExpr {
span,
left,
op: op!("??"),
right,
}) => {
//
let (l, aliased) = alias_if_required(left, "ref");

if aliased {
self.vars.push(VarDeclarator {
span: DUMMY_SP,
name: l.clone().into(),
init: None,
definite: false,
});
}

let var_expr = if aliased {
AssignExpr {
span: DUMMY_SP,
op: op!("="),
left: l.clone().into(),
right: left.take(),
}
.into()
} else {
l.clone().into()
};

*e = make_cond(self.c, *span, &l, var_expr, right.take());
}

Expr::Assign(ref mut assign @ AssignExpr { op: op!("??="), .. }) => {
match &mut assign.left {
AssignTarget::Simple(SimpleAssignTarget::Ident(i)) => {
*e = AssignExpr {
span: assign.span,
op: op!("="),
left: i.clone().into(),
right: Box::new(make_cond(
self.c,
assign.span,
&Ident::from(&*i),
Expr::Ident(Ident::from(&*i)),
assign.right.take(),
)),
}
.into();
}

AssignTarget::Simple(left) => {
let alias = alias_ident_for_simple_assign_tatget(left, "refs");
self.vars.push(VarDeclarator {
span: DUMMY_SP,
name: alias.clone().into(),
init: None,
definite: false,
});

// TODO: Check for computed.
let right_expr = AssignExpr {
span: assign.span,
left: left.clone().into(),
op: op!("="),
right: assign.right.take(),
}
.into();

let var_expr = AssignExpr {
span: DUMMY_SP,
op: op!("="),
left: alias.clone().into(),
right: left.take().into(),
}
.into();

*e = AssignExpr {
span: assign.span,
op: op!("="),
left: alias.clone().into(),
right: Box::new(make_cond(
self.c,
assign.span,
&alias,
var_expr,
right_expr,
)),
}
.into();
}

_ => {}
}
}

_ => {}
}
}

fn visit_mut_block_stmt_or_expr(&mut self, n: &mut BlockStmtOrExpr) {
let vars = self.vars.take();
n.visit_mut_children_with(self);

if !self.vars.is_empty() {
if let BlockStmtOrExpr::Expr(expr) = n {
// expr
// { var decl = init; return expr; }
let stmts = vec![
VarDecl {
span: DUMMY_SP,
kind: VarDeclKind::Var,
decls: self.vars.take(),
declare: false,
..Default::default()
}
.into(),
Stmt::Return(ReturnStmt {
span: DUMMY_SP,
arg: Some(expr.take()),
}),
];
*n = BlockStmtOrExpr::BlockStmt(BlockStmt {
span: DUMMY_SP,
stmts,
..Default::default()
});
}
}

self.vars = vars;
}
}
/// Creates a nullish coalescing transformation pass
///
/// This is now a thin wrapper around the Compiler implementation.
pub fn nullish_coalescing(c: Config) -> impl Pass + 'static {
let mut assumptions = Assumptions::default();
assumptions.no_document_all = c.no_document_all;

#[tracing::instrument(level = "debug", skip_all)]
fn make_cond(c: Config, span: Span, alias: &Ident, var_expr: Expr, init: Box<Expr>) -> Expr {
if c.no_document_all {
CondExpr {
span,
test: BinExpr {
span: DUMMY_SP,
left: Box::new(var_expr),
op: op!("!="),
right: Box::new(Expr::Lit(Lit::Null(Null { span: DUMMY_SP }))),
}
.into(),
cons: alias.clone().into(),
alt: init,
}
} else {
CondExpr {
span,
test: BinExpr {
span: DUMMY_SP,
left: Box::new(Expr::Bin(BinExpr {
span: DUMMY_SP,
left: Box::new(var_expr),
op: op!("!=="),
right: Box::new(Expr::Lit(Lit::Null(Null { span: DUMMY_SP }))),
})),
op: op!("&&"),
right: Box::new(Expr::Bin(BinExpr {
span: DUMMY_SP,
left: Box::new(Expr::Ident(alias.clone())),
op: op!("!=="),
right: Expr::undefined(DUMMY_SP),
})),
}
.into(),
cons: alias.clone().into(),
alt: init,
}
}
.into()
Compiler::new(swc_ecma_compiler::Config {
includes: Features::NULLISH_COALESCING,
assumptions,
..Default::default()
})
}
1 change: 1 addition & 0 deletions crates/swc_ecma_compiler/src/es2020/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub(crate) mod export_namespace_from;
pub(crate) mod nullish_coalescing;
Loading
Loading