Fix stack overflow (#1032)

swc_ecma_codegen:
 - Fix codegen of large binary expressions.

swc_ecma_parser:
 - Fix stack overflow while parsing large binary expressions.

swc_ecma_transforms:
 - typescrip::strip: Fix stack overflow while handling large binary operations.
 - hygiene: Fix stack overflow by migrating it to `VisitMut`.
 - hygiene: Improve performance.
 - fixer: Fix stack overflow by migrating it to `VisitMut`.
 - resolver: Migrate to `VisitMut`.
This commit is contained in:
강동윤 2020-09-06 15:09:02 +09:00 committed by GitHub
parent c0cb9e4401
commit eb2162cbd2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 4014 additions and 5522 deletions

View File

@ -79,6 +79,7 @@ jobs:
- name: Run cargo test
run: |
export PATH="$PATH:$HOME/npm/bin"
EXEC=0 cargo test --color always --all --exclude node --exclude wasm
cargo test --color always --all --exclude node --exclude wasm
#

View File

@ -1,6 +1,6 @@
[package]
name = "swc_ecma_codegen"
version = "0.35.0"
version = "0.35.1"
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
license = "Apache-2.0/MIT"
repository = "https://github.com/swc-project/swc.git"

View File

@ -16,6 +16,11 @@ mod tests {
assert_min(r#" 'foobar' "#, r#"'foobar';"#);
}
#[test]
fn bin_expr() {
assert_min("1+2+3+4+5", "1+2+3+4+5;");
}
#[test]
fn template_expression() {
assert_min("``", "``;");

View File

@ -690,10 +690,9 @@ impl<'a> Emitter<'a> {
emit!(node.right);
}
#[emitter]
fn emit_bin_expr(&mut self, node: &BinExpr) -> Result {
self.emit_leading_comments_of_pos(node.span().lo())?;
/// Prints operator and right node of a binary expression.
#[inline(never)]
fn emit_bin_expr_trailing(&mut self, node: &BinExpr) -> Result {
// let indent_before_op = needs_indention(node, &node.left, node.op);
// let indent_after_op = needs_indention(node, node.op, &node.right);
let need_space = match node.op {
@ -701,19 +700,17 @@ impl<'a> Emitter<'a> {
_ => false,
};
emit!(node.left);
let need_pre_space = need_space
|| match *node.left {
Expr::Update(UpdateExpr { prefix: false, .. }) => true,
_ => false,
};
if need_pre_space {
space!();
space!(self);
} else {
formatting_space!();
formatting_space!(self);
}
operator!(node.op.as_str());
operator!(self, node.op.as_str());
let need_post_space = need_space
|| match *node.right {
@ -721,11 +718,47 @@ impl<'a> Emitter<'a> {
_ => false,
};
if need_post_space {
space!();
space!(self);
} else {
formatting_space!();
formatting_space!(self);
}
emit!(node.right);
emit!(self, node.right);
Ok(())
}
#[emitter]
fn emit_bin_expr(&mut self, node: &BinExpr) -> Result {
self.emit_leading_comments_of_pos(node.span().lo())?;
{
let mut left = Some(node);
let mut lefts = vec![];
while let Some(l) = left {
lefts.push(l);
match &*l.left {
Expr::Bin(b) => {
left = Some(b);
}
_ => break,
}
}
let len = lefts.len();
for (i, left) in lefts.into_iter().rev().enumerate() {
if i == 0 {
emit!(left.left);
}
// Check if it's last
if i + 1 != len {
self.emit_bin_expr_trailing(left)?;
}
}
}
self.emit_bin_expr_trailing(node)?;
}
#[emitter]

View File

@ -1,6 +1,6 @@
[package]
name = "swc_ecma_parser"
version = "0.37.0"
version = "0.37.1"
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
license = "Apache-2.0/MIT"
repository = "https://github.com/swc-project/swc.git"

View File

@ -39,10 +39,50 @@ impl<'a, I: Tokens> Parser<I> {
///
/// `parseExprOp`
pub(in crate::parser) fn parse_bin_op_recursively(
&mut self,
mut left: Box<Expr>,
mut min_prec: u8,
) -> PResult<Box<Expr>> {
loop {
let (next_left, next_prec) = self.parse_bin_op_recursively_inner(left, min_prec)?;
match &*next_left {
Expr::Bin(BinExpr {
span,
left,
op: op!("&&"),
..
})
| Expr::Bin(BinExpr {
span,
left,
op: op!("||"),
..
}) => match &**left {
Expr::Bin(BinExpr { op: op!("??"), .. }) => {
self.emit_err(*span, SyntaxError::NullishCoalescingWithLogicalOp);
}
_ => {}
},
_ => {}
}
min_prec = match next_prec {
Some(v) => v,
None => return Ok(next_left),
};
left = next_left;
}
}
/// Returns `(left, Some(next_prec))` or `(expr, None)`.
fn parse_bin_op_recursively_inner(
&mut self,
left: Box<Expr>,
min_prec: u8,
) -> PResult<Box<Expr>> {
) -> PResult<(Box<Expr>, Option<u8>)> {
const PREC_OF_IN: u8 = 7;
if self.input.syntax().typescript()
@ -69,21 +109,21 @@ impl<'a, I: Tokens> Parser<I> {
}))
};
return self.parse_bin_op_recursively(node, min_prec);
return self.parse_bin_op_recursively_inner(node, min_prec);
}
let ctx = self.ctx();
// Return left on eof
let word = match cur!(false) {
Ok(cur) => cur,
Err(..) => return Ok(left),
Err(..) => return Ok((left, None)),
};
let op = match *word {
Word(Word::Keyword(Keyword::In)) if ctx.include_in_expr => op!("in"),
Word(Word::Keyword(Keyword::InstanceOf)) => op!("instanceof"),
Token::BinOp(op) => op.into(),
_ => {
return Ok(left);
return Ok((left, None));
}
};
@ -100,7 +140,7 @@ impl<'a, I: Tokens> Parser<I> {
op.precedence()
);
return Ok(left);
return Ok((left, None));
}
bump!();
trace!(
@ -173,18 +213,7 @@ impl<'a, I: Tokens> Parser<I> {
right,
}));
let expr = self.parse_bin_op_recursively(node, min_prec)?;
if op == op!("??") {
match *expr {
Expr::Bin(BinExpr { span, op, .. }) if op == op!("&&") || op == op!("||") => {
self.emit_err(span, SyntaxError::NullishCoalescingWithLogicalOp);
}
_ => {}
}
}
Ok(expr)
return Ok((node, Some(min_prec)));
}
/// Parse unary expression and update expression.

View File

@ -6,7 +6,7 @@ edition = "2018"
license = "Apache-2.0/MIT"
name = "swc_ecma_transforms"
repository = "https://github.com/swc-project/swc.git"
version = "0.23.1"
version = "0.23.2"
[features]
const-modules = ["dashmap"]
@ -25,6 +25,7 @@ jsdoc = {version = "0.5.0", path = "../jsdoc"}
log = "0.4.8"
once_cell = "1"
ordered-float = "1.0.1"
phf = {version = "0.8.0", features = ["macros"]}
regex = "1"
retain_mut = "=0.1.1"
scoped-tls = "1"

View File

@ -1,24 +1,35 @@
use phf::phf_set;
use swc_atoms::{js_word, JsWord};
macro_rules! native {
(
$sym:expr,
$(
$i:tt
),*
) => {
match *$sym{
pub(crate) fn is_native(sym: &str) -> bool {
static SET: phf::Set<&'static str> = phf_set! {
$(
$i,
)*
};
SET.contains(sym)
}
/// Faster
pub(crate) fn is_native_word(sym: &JsWord) -> bool {
match *sym{
$(
js_word!($i) => true,
)*
_ => false
}
}
};
}
pub(crate) fn is_native(sym: &JsWord) -> bool {
native!(
sym,
native!(
"Array",
"ArrayBuffer",
"Atomics",
@ -577,5 +588,4 @@ pub(crate) fn is_native(sym: &JsWord) -> bool {
"XPathExpression",
"XPathResult",
"XSLTProcessor"
)
}
);

View File

@ -1,6 +1,6 @@
use swc_common::Spanned;
// use swc_common::Spanned;
use swc_ecma_ast::*;
use swc_ecma_visit::{Fold, FoldWith};
use swc_ecma_visit::Fold;
/// This transform validates span on debug mode and does nothing on release
/// mode.
@ -8,148 +8,109 @@ pub struct Validator {
pub name: &'static str,
}
macro_rules! ne {
($v:expr, $T:ty, $l:expr, $r:expr) => {{
// debug_assert_ne!(
// $l,
// $r,
// "{}: {}: {} should not be same as {}",
// $v.name,
// stringify!($T),
// stringify!($l),
// stringify!($r),
// );
}};
}
macro_rules! lte {
($v:expr, $T:ty, $l:expr, $r:expr) => {{
// debug_assert!(
// $l <= $r,
// "{}: {}: {} should be <= {}; l={:?}; r={:?}",
// $v.name,
// stringify!($T),
// stringify!($l),
// stringify!($r),
// $l,
// $r
// );
}};
}
macro_rules! gte {
($v:expr, $T:ty, $l:expr, $r:expr) => {{
// debug_assert!(
// $l >= $r,
// "{}: {}: {} should be >= {}; l={:?}; r={:?}",
// $v.name,
// stringify!($T),
// stringify!($l),
// stringify!($r),
// $l,
// $r
// );
}};
}
impl Fold for Validator {
fn fold_assign_expr(&mut self, node: AssignExpr) -> AssignExpr {
if node.span.is_dummy() {
return node.fold_children_with(self);
}
if !node.left.span().is_dummy() {
gte!(self, AssignExpr, node.left.span().lo(), node.span().lo());
}
// fn fold_assign_expr(&mut self, node: AssignExpr) -> AssignExpr {
// if node.span.is_dummy() {
// return node.fold_children_with(self);
// }
//
// if !node.left.span().is_dummy() {
// gte!(self, AssignExpr, node.left.span().lo(), node.span().lo());
// }
//
// // if !node.right.span().is_dummy() {
// // eq!(self, AssignExpr, node.right.span().hi(),
// node.span().hi()); // }
//
// node.fold_children_with(self)
// }
//
// fn fold_bin_expr(&mut self, node: BinExpr) -> BinExpr {
// if node.span.is_dummy() {
// return node.fold_children_with(self);
// }
//
// if !node.left.span().is_dummy() {
// gte!(self, BinExpr, node.left.span().lo(), node.span().lo());
// }
//
// if !node.right.span().is_dummy() {
// eq!(self, AssignExpr, node.right.span().hi(), node.span().hi());
// gte!(self, BinExpr, node.span().hi(), node.right.span().hi());
// }
//
// node.fold_children_with(self)
// }
//
// fn fold_cond_expr(&mut self, node: CondExpr) -> CondExpr {
// if node.span.is_dummy() {
// return node.fold_children_with(self);
// }
//
// if !node.test.span().is_dummy() {
// gte!(self, CondExpr, node.test.span().lo(), node.span().lo());
// }
//
// if !node.alt.span().is_dummy() {
// lte!(self, CondExpr, node.alt.span().hi(), node.span().hi());
// }
//
// node.fold_children_with(self)
// }
//
// fn fold_member_expr(&mut self, node: MemberExpr) -> MemberExpr {
// if node.span.is_dummy() {
// return node.fold_children_with(self);
// }
//
// if !node.obj.span().is_dummy() {
// ne!(self, MemberExpr, node.span(), node.obj.span());
// }
//
// if !node.prop.span().is_dummy() {
// ne!(self, MemberExpr, node.span(), node.prop.span());
// }
//
// if !node.obj.span().is_dummy() {
// lte!(self, MemberExpr, node.span().lo(), node.obj.span().lo());
// }
//
// if !node.computed && !node.prop.span().is_dummy() {
// gte!(self, MemberExpr, node.span().hi(), node.prop.span().hi());
// }
//
// node.fold_children_with(self)
// }
//
// fn fold_unary_expr(&mut self, node: UnaryExpr) -> UnaryExpr {
// if node.span.is_dummy() {
// return node.fold_children_with(self);
// }
//
// if !node.arg.span().is_dummy() {
// lte!(self, UnaryExpr, node.arg.span().hi(), node.span().hi())
// }
//
// node.fold_children_with(self)
// }
//
// fn fold_update_expr(&mut self, node: UpdateExpr) -> UpdateExpr {
// if node.span.is_dummy() {
// return node.fold_children_with(self);
// }
//
// if node.prefix {
// if !node.arg.span().is_dummy() {
// lte!(self, UpdateExpr, node.arg.span().hi(), node.span().hi())
// }
// } else if !node.arg.span().is_dummy() {
// gte!(self, UpdateExpr, node.arg.span().lo(), node.span().lo())
// }
//
// node.fold_children_with(self)
// }
node.fold_children_with(self)
}
fn fold_bin_expr(&mut self, node: BinExpr) -> BinExpr {
if node.span.is_dummy() {
return node.fold_children_with(self);
}
if !node.left.span().is_dummy() {
gte!(self, BinExpr, node.left.span().lo(), node.span().lo());
}
if !node.right.span().is_dummy() {
gte!(self, BinExpr, node.span().hi(), node.right.span().hi());
}
node.fold_children_with(self)
}
fn fold_cond_expr(&mut self, node: CondExpr) -> CondExpr {
if node.span.is_dummy() {
return node.fold_children_with(self);
}
if !node.test.span().is_dummy() {
gte!(self, CondExpr, node.test.span().lo(), node.span().lo());
}
if !node.alt.span().is_dummy() {
lte!(self, CondExpr, node.alt.span().hi(), node.span().hi());
}
node.fold_children_with(self)
}
fn fold_member_expr(&mut self, node: MemberExpr) -> MemberExpr {
if node.span.is_dummy() {
return node.fold_children_with(self);
}
if !node.obj.span().is_dummy() {
ne!(self, MemberExpr, node.span(), node.obj.span());
}
if !node.prop.span().is_dummy() {
ne!(self, MemberExpr, node.span(), node.prop.span());
}
if !node.obj.span().is_dummy() {
lte!(self, MemberExpr, node.span().lo(), node.obj.span().lo());
}
if !node.computed && !node.prop.span().is_dummy() {
gte!(self, MemberExpr, node.span().hi(), node.prop.span().hi());
}
node.fold_children_with(self)
}
fn fold_unary_expr(&mut self, node: UnaryExpr) -> UnaryExpr {
if node.span.is_dummy() {
return node.fold_children_with(self);
}
if !node.arg.span().is_dummy() {
lte!(self, UnaryExpr, node.arg.span().hi(), node.span().hi())
}
node.fold_children_with(self)
}
fn fold_update_expr(&mut self, node: UpdateExpr) -> UpdateExpr {
if node.span.is_dummy() {
return node.fold_children_with(self);
}
if node.prefix {
if !node.arg.span().is_dummy() {
lte!(self, UpdateExpr, node.arg.span().hi(), node.span().hi())
}
} else if !node.arg.span().is_dummy() {
gte!(self, UpdateExpr, node.arg.span().lo(), node.span().lo())
}
node.fold_children_with(self)
#[inline(always)] // prevent stack overflow on debug build
fn fold_module(&mut self, module: Module) -> Module {
module
}
}

View File

@ -7,6 +7,10 @@ use swc_ecma_utils::ExprExt;
pub(crate) trait MapWithMut: Sized {
fn dummy() -> Self;
fn take(&mut self) -> Self {
replace(self, Self::dummy())
}
#[inline]
fn map_with_mut<F>(&mut self, op: F)
where
@ -71,6 +75,22 @@ where
}
}
impl MapWithMut for Ident {
fn dummy() -> Self {
Ident::new(js_word!(""), DUMMY_SP)
}
}
impl MapWithMut for ObjectPatProp {
fn dummy() -> Self {
ObjectPatProp::Assign(AssignPatProp {
span: DUMMY_SP,
key: Ident::dummy(),
value: None,
})
}
}
pub(crate) trait PatOrExprExt: AsOptExpr {
fn as_ref(&self) -> &PatOrExpr;
fn as_mut(&mut self) -> &mut PatOrExpr;

View File

@ -1,22 +1,15 @@
use crate::{
ext::{AsOptExpr, PatOrExprExt},
util::ExprFactory,
};
use crate::ext::{AsOptExpr, MapWithMut, PatOrExprExt};
use fxhash::FxHashMap;
use swc_common::{
comments::Comments,
util::{map::Map, move_map::MoveMap},
Span, Spanned,
};
use swc_common::{comments::Comments, Span, Spanned};
use swc_ecma_ast::*;
use swc_ecma_visit::{noop_fold_type, Fold, FoldWith};
use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith};
pub fn fixer<'a>(comments: Option<&'a dyn Comments>) -> impl 'a + Fold {
Fixer {
as_folder(Fixer {
comments,
ctx: Default::default(),
span_map: Default::default(),
}
})
}
struct Fixer<'a> {
@ -56,123 +49,90 @@ impl Default for Context {
macro_rules! array {
($name:ident, $T:tt) => {
fn $name(&mut self, e: $T) -> $T {
fn $name(&mut self, e: &mut $T) {
let old = self.ctx;
self.ctx = Context::ForcedExpr { is_var_decl: false }.into();
let elems = e.elems.fold_with(self);
e.elems.visit_mut_with(self);
self.ctx = old;
$T { elems, ..e }
}
};
}
impl Fold for Fixer<'_> {
noop_fold_type!();
impl VisitMut for Fixer<'_> {
noop_visit_mut_type!();
array!(fold_array_lit, ArrayLit);
array!(visit_mut_array_lit, ArrayLit);
// array!(ArrayPat);
fn fold_new_expr(&mut self, node: NewExpr) -> NewExpr {
let NewExpr {
span,
mut callee,
args,
type_args,
} = node;
fn visit_mut_new_expr(&mut self, node: &mut NewExpr) {
let old = self.ctx;
self.ctx = Context::ForcedExpr { is_var_decl: false };
let args = args.fold_with(self);
node.args.visit_mut_with(self);
self.ctx = old;
let old = self.ctx;
self.ctx = Context::Callee { is_new: true };
callee = callee.fold_with(self);
match *callee {
Expr::Call(..) | Expr::Bin(..) => callee = Box::new(self.wrap(*callee)),
node.callee.visit_mut_with(self);
match *node.callee {
Expr::Call(..) | Expr::Bin(..) => self.wrap(&mut node.callee),
_ => {}
}
self.ctx = old;
NewExpr {
span,
callee,
args,
type_args,
}
}
fn fold_call_expr(&mut self, node: CallExpr) -> CallExpr {
let CallExpr {
span,
callee,
args,
type_args,
} = node;
fn visit_mut_call_expr(&mut self, node: &mut CallExpr) {
let old = self.ctx;
self.ctx = Context::ForcedExpr { is_var_decl: false };
let args = args.fold_with(self);
node.args.visit_mut_with(self);
self.ctx = old;
let old = self.ctx;
self.ctx = Context::Callee { is_new: false };
let callee = callee.fold_with(self);
node.callee.visit_mut_with(self);
self.ctx = old;
CallExpr {
span,
callee,
args,
type_args,
}
}
fn fold_arrow_expr(&mut self, node: ArrowExpr) -> ArrowExpr {
fn visit_mut_arrow_expr(&mut self, node: &mut ArrowExpr) {
let old = self.ctx;
self.ctx = Context::Default;
let mut node = node.fold_children_with(self);
node.body = match node.body {
BlockStmtOrExpr::Expr(e) if e.is_seq() => {
BlockStmtOrExpr::Expr(Box::new(self.wrap(*e)))
node.visit_mut_children_with(self);
match &mut node.body {
BlockStmtOrExpr::Expr(ref mut e) if e.is_seq() => {
self.wrap(&mut **e);
}
_ => node.body,
_ => {}
};
self.ctx = old;
node
}
fn fold_assign_pat_prop(&mut self, node: AssignPatProp) -> AssignPatProp {
let key = node.key.fold_children_with(self);
fn visit_mut_assign_pat_prop(&mut self, node: &mut AssignPatProp) {
node.key.visit_mut_children_with(self);
let old = self.ctx;
self.ctx = Context::ForcedExpr { is_var_decl: false };
let value = node.value.fold_with(self);
node.value.visit_mut_with(self);
self.ctx = old;
AssignPatProp { key, value, ..node }
}
fn fold_block_stmt_or_expr(&mut self, body: BlockStmtOrExpr) -> BlockStmtOrExpr {
let body = body.fold_children_with(self);
fn visit_mut_block_stmt_or_expr(&mut self, body: &mut BlockStmtOrExpr) {
body.visit_mut_children_with(self);
match body {
BlockStmtOrExpr::Expr(expr) if expr.is_object() => {
BlockStmtOrExpr::Expr(Box::new(self.wrap(*expr)))
BlockStmtOrExpr::Expr(ref mut expr) if expr.is_object() => {
self.wrap(&mut **expr);
}
_ => body,
_ => {}
}
}
fn fold_class(&mut self, node: Class) -> Class {
fn visit_mut_class(&mut self, node: &mut Class) {
let old = self.ctx;
self.ctx = Context::Default;
let mut node: Class = node.fold_children_with(self);
node.super_class = match node.super_class {
Some(e) if e.is_seq() || e.is_await_expr() => Some(Box::new(self.wrap(*e))),
_ => node.super_class,
node.visit_mut_children_with(self);
match &mut node.super_class {
Some(ref mut e) if e.is_seq() || e.is_await_expr() => self.wrap(&mut **e),
_ => {}
};
self.ctx = old;
@ -180,52 +140,159 @@ impl Fold for Fixer<'_> {
ClassMember::Empty(..) => false,
_ => true,
});
node
}
fn fold_export_default_expr(&mut self, node: ExportDefaultExpr) -> ExportDefaultExpr {
fn visit_mut_export_default_expr(&mut self, node: &mut ExportDefaultExpr) {
let old = self.ctx;
self.ctx = Context::Default;
let mut node = node.fold_children_with(self);
node.expr = match *node.expr {
Expr::Arrow(..) | Expr::Seq(..) => Box::new(self.wrap(*node.expr)),
_ => node.expr,
node.visit_mut_children_with(self);
match &mut *node.expr {
Expr::Arrow(..) | Expr::Seq(..) => self.wrap(&mut node.expr),
_ => {}
};
self.ctx = old;
node
}
fn fold_expr(&mut self, expr: Expr) -> Expr {
let expr = expr.fold_children_with(self);
let expr = self.unwrap_expr(expr);
fn visit_mut_expr(&mut self, e: &mut Expr) {
e.visit_mut_children_with(self);
self.unwrap_expr(e);
match expr {
Expr::Member(MemberExpr {
span,
computed,
obj,
prop,
}) if obj.as_expr().map(|e| e.is_object()).unwrap_or(false)
self.wrap_with_paren_if_required(e)
}
fn visit_mut_expr_or_spread(&mut self, e: &mut ExprOrSpread) {
e.visit_mut_children_with(self);
if e.spread.is_none() {
match *e.expr {
Expr::Yield(..) => {
self.wrap(&mut e.expr);
}
_ => {}
}
}
}
fn visit_mut_if_stmt(&mut self, node: &mut IfStmt) {
node.visit_mut_children_with(self);
match *node.cons {
Stmt::If(..) => {
node.cons = Box::new(Stmt::Block(BlockStmt {
span: node.cons.span(),
stmts: vec![*node.cons.take()],
}));
}
_ => {}
}
}
fn visit_mut_key_value_pat_prop(&mut self, node: &mut KeyValuePatProp) {
let old = self.ctx;
self.ctx = Context::ForcedExpr { is_var_decl: false };
node.key.visit_mut_with(self);
self.ctx = old;
node.value.visit_mut_with(self);
}
fn visit_mut_key_value_prop(&mut self, prop: &mut KeyValueProp) {
prop.visit_mut_children_with(self);
match *prop.value {
Expr::Seq(..) => self.wrap(&mut prop.value),
_ => {}
}
}
fn visit_mut_prop_name(&mut self, name: &mut PropName) {
name.visit_mut_children_with(self);
match name {
PropName::Computed(c) if c.expr.is_seq() => {
self.wrap(&mut c.expr);
}
_ => {}
}
}
fn visit_mut_stmt(&mut self, stmt: &mut Stmt) {
match stmt {
Stmt::Expr(expr) => {
let old = self.ctx;
self.ctx = Context::Default;
expr.visit_mut_with(self);
self.ctx = old;
}
_ => stmt.visit_mut_children_with(self),
};
match stmt {
Stmt::Expr(ExprStmt { ref mut expr, .. }) => {
self.handle_expr_stmt(&mut **expr);
}
_ => {}
}
}
fn visit_mut_var_declarator(&mut self, node: &mut VarDeclarator) {
node.name.visit_mut_children_with(self);
let old = self.ctx;
self.ctx = Context::ForcedExpr { is_var_decl: true };
node.init.visit_mut_with(self);
self.ctx = old;
}
fn visit_mut_module(&mut self, n: &mut Module) {
debug_assert!(self.span_map.is_empty());
self.span_map.clear();
let n = n.visit_mut_children_with(self);
if let Some(c) = self.comments {
for (to, from) in self.span_map.drain() {
c.move_leading(from.lo, to.lo);
c.move_trailing(from.hi, to.hi);
}
}
n
}
fn visit_mut_script(&mut self, n: &mut Script) {
debug_assert!(self.span_map.is_empty());
self.span_map.clear();
let n = n.visit_mut_children_with(self);
if let Some(c) = self.comments {
for (to, from) in self.span_map.drain() {
c.move_leading(from.lo, to.lo);
c.move_trailing(from.hi, to.hi);
}
}
n
}
}
impl Fixer<'_> {
fn wrap_with_paren_if_required(&mut self, e: &mut Expr) {
match e {
Expr::Member(MemberExpr { obj, .. })
if obj.as_expr().map(|e| e.is_object()).unwrap_or(false)
&& match self.ctx {
Context::ForcedExpr { is_var_decl: true } => true,
_ => false,
} =>
{
MemberExpr {
span,
computed,
obj,
prop,
}
.into()
return
}
Expr::Member(MemberExpr {
span,
computed,
obj: ExprOrSuper::Expr(obj),
prop,
obj: ExprOrSuper::Expr(ref mut obj),
..
}) if obj.is_fn_expr()
|| obj.is_cond()
|| obj.is_unary()
@ -238,18 +305,12 @@ impl Fold for Fixer<'_> {
|| obj.is_class()
|| obj.is_yield_expr()
|| obj.is_await_expr()
|| match *obj {
|| match **obj {
Expr::New(NewExpr { args: None, .. }) => true,
_ => false,
} =>
{
MemberExpr {
span,
computed,
obj: self.wrap(*obj).as_obj(),
prop,
}
.into()
self.wrap(&mut **obj);
}
// Flatten seq expr
@ -270,23 +331,25 @@ impl Fold for Fixer<'_> {
.filter_map(|(i, e)| {
let is_last = i + 1 == exprs_len;
if is_last {
Some(e)
Some(e.take())
} else {
ignore_return_value(e)
ignore_return_value(e.take())
}
})
.collect::<Vec<_>>();
if exprs.len() == 1 {
return *exprs.pop().unwrap();
*e = *exprs.pop().unwrap();
return;
}
Expr::Seq(SeqExpr { span, exprs })
Expr::Seq(SeqExpr { span: *span, exprs })
} else {
let mut buf = Vec::with_capacity(len);
for (i, expr) in exprs.into_iter().enumerate() {
let is_last = i + 1 == exprs_len;
match *expr {
Expr::Seq(SeqExpr { exprs, .. }) => {
match **expr {
Expr::Seq(SeqExpr { ref mut exprs, .. }) => {
let exprs = exprs.take();
if !is_last {
buf.extend(exprs.into_iter().filter_map(ignore_return_value));
} else {
@ -301,360 +364,166 @@ impl Fold for Fixer<'_> {
}
}
}
_ => buf.push(expr),
_ => buf.push(expr.take()),
}
}
if buf.len() == 1 {
return *buf.pop().unwrap();
*e = *buf.pop().unwrap();
return;
}
buf.shrink_to_fit();
Expr::Seq(SeqExpr { span, exprs: buf })
Expr::Seq(SeqExpr {
span: *span,
exprs: buf,
})
};
match self.ctx {
Context::ForcedExpr { .. } => Expr::Paren(ParenExpr {
span,
Context::ForcedExpr { .. } => {
*e = Expr::Paren(ParenExpr {
span: *span,
expr: Box::new(expr),
}),
_ => expr,
})
}
_ => *e = expr,
};
}
Expr::Bin(mut expr) => {
expr.right = match *expr.right {
e @ Expr::Assign(..)
| e @ Expr::Seq(..)
| e @ Expr::Yield(..)
| e @ Expr::Cond(..)
| e @ Expr::Arrow(..) => Box::new(self.wrap(e)),
Expr::Bin(ref mut expr) => {
match &mut *expr.right {
Expr::Assign(..)
| Expr::Seq(..)
| Expr::Yield(..)
| Expr::Cond(..)
| Expr::Arrow(..) => {
self.wrap(&mut expr.right);
}
Expr::Bin(BinExpr { op: op_of_rhs, .. }) => {
if op_of_rhs.precedence() <= expr.op.precedence() {
Box::new(self.wrap(*expr.right))
} else {
expr.right
self.wrap(&mut expr.right);
}
}
_ => expr.right,
_ => {}
};
match *expr.left {
match &mut *expr.left {
// While simplifying, (1 + x) * Nan becomes `1 + x * Nan`.
// But it should be `(1 + x) * Nan`
Expr::Bin(BinExpr { op: op_of_lhs, .. }) => {
if op_of_lhs.precedence() < expr.op.precedence() {
Expr::Bin(BinExpr {
left: Box::new(self.wrap(*expr.left)),
..expr
})
} else {
Expr::Bin(expr)
self.wrap(&mut expr.left);
}
}
e @ Expr::Seq(..)
| e @ Expr::Update(..)
| e
@
Expr::Unary(UnaryExpr {
Expr::Seq(..)
| Expr::Update(..)
| Expr::Unary(UnaryExpr {
op: op!("delete"), ..
})
| e
@
Expr::Unary(UnaryExpr {
| Expr::Unary(UnaryExpr {
op: op!("void"), ..
})
| e @ Expr::Yield(..)
| e @ Expr::Cond(..)
| e @ Expr::Assign(..)
| e @ Expr::Arrow(..) => Expr::Bin(BinExpr {
left: Box::new(self.wrap(e)),
..expr
}),
e @ Expr::Object(..)
| Expr::Yield(..)
| Expr::Cond(..)
| Expr::Assign(..)
| Expr::Arrow(..) => {
self.wrap(&mut expr.left);
}
Expr::Object(..)
if expr.op == op!("instanceof")
|| expr.op == op!("==")
|| expr.op == op!("===")
|| expr.op == op!("!=")
|| expr.op == op!("!==") =>
{
Expr::Bin(BinExpr {
left: Box::new(e.wrap_with_paren()),
..expr
})
self.wrap(&mut expr.left)
}
_ => Expr::Bin(expr),
_ => {}
}
}
Expr::Cond(expr) => {
let test = match *expr.test {
e @ Expr::Seq(..)
| e @ Expr::Assign(..)
| e @ Expr::Cond(..)
| e @ Expr::Arrow(..) => Box::new(self.wrap(e)),
match &mut *expr.test {
Expr::Seq(..) | Expr::Assign(..) | Expr::Cond(..) | Expr::Arrow(..) => {
self.wrap(&mut expr.test)
}
e @ Expr::Object(..) | e @ Expr::Fn(..) | e @ Expr::Class(..) => {
Expr::Object(..) | Expr::Fn(..) | Expr::Class(..) => {
if self.ctx == Context::Default {
Box::new(self.wrap(e))
} else {
Box::new(e)
self.wrap(&mut expr.test)
}
}
_ => expr.test,
_ => {}
};
let cons = match *expr.cons {
e @ Expr::Seq(..) => Box::new(self.wrap(e)),
_ => expr.cons,
match *expr.cons {
Expr::Seq(..) => self.wrap(&mut expr.cons),
_ => {}
};
let alt = match *expr.alt {
e @ Expr::Seq(..) => Box::new(self.wrap(e)),
_ => expr.alt,
match *expr.alt {
Expr::Seq(..) => self.wrap(&mut expr.alt),
_ => {}
};
let expr = Expr::Cond(CondExpr {
test,
cons,
alt,
..expr
});
match self.ctx {
Context::Callee { is_new: true } => self.wrap(expr),
_ => expr,
Context::Callee { is_new: true } => self.wrap(e),
_ => {}
}
}
Expr::Unary(expr) => {
let arg = match *expr.arg {
e @ Expr::Assign(..)
| e @ Expr::Bin(..)
| e @ Expr::Seq(..)
| e @ Expr::Cond(..)
| e @ Expr::Arrow(..)
| e @ Expr::Yield(..) => Box::new(self.wrap(e)),
_ => expr.arg,
};
Expr::Unary(UnaryExpr { arg, ..expr })
}
Expr::Unary(expr) => match *expr.arg {
Expr::Assign(..)
| Expr::Bin(..)
| Expr::Seq(..)
| Expr::Cond(..)
| Expr::Arrow(..)
| Expr::Yield(..) => self.wrap(&mut expr.arg),
_ => {}
},
Expr::Assign(expr) => {
let right = match *expr.right {
match &mut *expr.right {
// `foo = (bar = baz)` => foo = bar = baz
Expr::Assign(AssignExpr { ref left, .. }) if left.as_ident().is_some() => {
expr.right
}
Expr::Assign(AssignExpr { ref left, .. }) if left.as_ident().is_some() => {}
// Handle `foo = bar = init()
Expr::Seq(right) => Box::new(self.wrap(right)),
_ => expr.right,
};
Expr::Assign(AssignExpr { right, ..expr })
Expr::Seq(..) => self.wrap(&mut expr.right),
_ => {}
}
}
Expr::Call(CallExpr {
span,
callee: ExprOrSuper::Expr(callee),
args,
type_args,
}) if callee.is_arrow() => Expr::Call(CallExpr {
span,
callee: self.wrap(*callee).as_callee(),
args,
type_args,
}),
callee: ExprOrSuper::Expr(ref mut callee),
..
}) if callee.is_arrow() => {
self.wrap(&mut **callee);
}
// Function expression cannot start with `function`
Expr::Call(CallExpr {
span,
callee: ExprOrSuper::Expr(callee),
args,
type_args,
callee: ExprOrSuper::Expr(ref mut callee),
..
}) if callee.is_fn_expr() => match self.ctx {
Context::ForcedExpr { .. } => Expr::Call(CallExpr {
span,
callee: callee.as_callee(),
args,
type_args,
}),
Context::ForcedExpr { .. } => {}
Context::Callee { is_new: true } => self.wrap(CallExpr {
span,
callee: callee.as_callee(),
args,
type_args,
}),
Context::Callee { is_new: true } => self.wrap(e),
_ => Expr::Call(CallExpr {
span,
callee: self.wrap(*callee).as_callee(),
args,
type_args,
}),
_ => self.wrap(&mut **callee),
},
Expr::Call(CallExpr {
span,
callee: ExprOrSuper::Expr(callee),
args,
type_args,
}) if callee.is_assign() => Expr::Call(CallExpr {
span,
callee: self.wrap(*callee).as_callee(),
args,
type_args,
}),
_ => expr,
}
}
fn fold_expr_or_spread(&mut self, e: ExprOrSpread) -> ExprOrSpread {
let e = e.fold_children_with(self);
if e.spread.is_none() {
match *e.expr {
Expr::Yield(..) => {
return ExprOrSpread {
spread: None,
expr: Box::new(self.wrap(*e.expr)),
}
}
callee: ExprOrSuper::Expr(ref mut callee),
..
}) if callee.is_assign() => self.wrap(&mut **callee),
_ => {}
}
}
e
}
fn fold_if_stmt(&mut self, node: IfStmt) -> IfStmt {
let node: IfStmt = node.fold_children_with(self);
match *node.cons {
Stmt::If(..) => IfStmt {
cons: Box::new(Stmt::Block(BlockStmt {
span: node.cons.span(),
stmts: vec![*node.cons],
})),
..node
},
_ => node,
}
}
fn fold_key_value_pat_prop(&mut self, node: KeyValuePatProp) -> KeyValuePatProp {
let old = self.ctx;
self.ctx = Context::ForcedExpr { is_var_decl: false };
let key = node.key.fold_with(self);
self.ctx = old;
let value = node.value.fold_with(self);
KeyValuePatProp { key, value }
}
fn fold_key_value_prop(&mut self, prop: KeyValueProp) -> KeyValueProp {
let prop = prop.fold_children_with(self);
match *prop.value {
Expr::Seq(..) => KeyValueProp {
value: Box::new(self.wrap(*prop.value)),
..prop
},
_ => prop,
}
}
fn fold_prop_name(&mut self, mut name: PropName) -> PropName {
name = name.fold_children_with(self);
match name {
PropName::Computed(c) if c.expr.is_seq() => {
return PropName::Computed(ComputedPropName {
span: c.span,
expr: Box::new(self.wrap(*c.expr)),
});
}
_ => {}
}
name
}
fn fold_stmt(&mut self, stmt: Stmt) -> Stmt {
let stmt = match stmt {
Stmt::Expr(expr) => {
let old = self.ctx;
self.ctx = Context::Default;
let expr = expr.fold_with(self);
self.ctx = old;
Stmt::Expr(expr)
}
_ => stmt.fold_children_with(self),
};
let stmt = match stmt {
Stmt::Expr(ExprStmt { span, expr }) => Stmt::Expr(ExprStmt {
span,
expr: expr.map(|e| self.handle_expr_stmt(e)),
}),
_ => stmt,
};
stmt
}
fn fold_var_declarator(&mut self, node: VarDeclarator) -> VarDeclarator {
let name = node.name.fold_children_with(self);
let old = self.ctx;
self.ctx = Context::ForcedExpr { is_var_decl: true };
let init = node.init.fold_with(self);
self.ctx = old;
VarDeclarator { name, init, ..node }
}
fn fold_module(&mut self, n: Module) -> Module {
debug_assert!(self.span_map.is_empty());
self.span_map.clear();
let n = n.fold_children_with(self);
if let Some(c) = self.comments {
for (to, from) in self.span_map.drain() {
c.move_leading(from.lo, to.lo);
c.move_trailing(from.hi, to.hi);
}
}
n
}
fn fold_script(&mut self, n: Script) -> Script {
debug_assert!(self.span_map.is_empty());
self.span_map.clear();
let n = n.fold_children_with(self);
if let Some(c) = self.comments {
for (to, from) in self.span_map.drain() {
c.move_leading(from.lo, to.lo);
c.move_trailing(from.hi, to.hi);
}
}
n
}
}
impl Fixer<'_> {
fn wrap<T>(&mut self, e: T) -> Expr
where
T: Into<Expr>,
{
let expr = Box::new(e.into());
let span = expr.span();
/// Wrap with a paren.
fn wrap(&mut self, e: &mut Expr) {
let span = e.span();
let span = if let Some(span) = self.span_map.remove(&span) {
span
@ -662,71 +531,61 @@ impl Fixer<'_> {
span
};
Expr::Paren(ParenExpr { expr, span })
let expr = Box::new(e.take());
*e = Expr::Paren(ParenExpr { expr, span })
}
/// Removes paren
fn unwrap_expr(&mut self, mut e: Expr) -> Expr {
fn unwrap_expr(&mut self, e: &mut Expr) {
match e {
Expr::Seq(SeqExpr { ref mut exprs, .. }) if exprs.len() == 1 => {
self.unwrap_expr(*exprs.pop().unwrap())
self.unwrap_expr(exprs.last_mut().unwrap());
*e = *exprs.last_mut().unwrap().take();
}
Expr::Paren(ParenExpr {
span: paren_span,
expr,
ref mut expr,
..
}) => {
let e = self.unwrap_expr(*expr);
let expr_span = expr.span();
let paren_span = *paren_span;
self.unwrap_expr(&mut **expr);
*e = *expr.take();
self.span_map.insert(e.span(), paren_span);
e
self.span_map.insert(expr_span, paren_span);
}
_ => e,
_ => {}
}
}
fn handle_expr_stmt(&mut self, expr: Expr) -> Expr {
fn handle_expr_stmt(&mut self, expr: &mut Expr) {
match expr {
// It's important for arrow pass to work properly.
Expr::Object(..) | Expr::Class(..) | Expr::Fn(..) => self.wrap(expr),
// ({ a } = foo)
Expr::Assign(AssignExpr {
span,
left: PatOrExpr::Pat(left),
op,
right,
}) if left.is_object() => self.wrap(AssignExpr {
span,
left: PatOrExpr::Pat(left),
op,
right,
}),
..
}) if left.is_object() => self.wrap(expr),
Expr::Seq(SeqExpr { span, exprs }) => {
Expr::Seq(SeqExpr { exprs, .. }) => {
debug_assert!(
exprs.len() != 1,
"SeqExpr should be unwrapped if exprs.len() == 1, but length is 1"
);
let mut i = 0;
let len = exprs.len();
Expr::Seq(SeqExpr {
span,
exprs: exprs.move_map(|expr| {
i += 1;
let is_last = len == i;
exprs.into_iter().enumerate().for_each(|(i, mut expr)| {
let is_last = len == i + 1;
if !is_last {
expr.map(|e| self.handle_expr_stmt(e))
} else {
expr
self.handle_expr_stmt(&mut expr);
}
}),
})
});
}
_ => expr,
_ => {}
}
}
}

View File

@ -1,14 +1,15 @@
use self::ops::{Operator, ScopeOp};
use self::ops::{Operations, Operator};
use crate::{
compat::es2015::classes::native::is_native,
compat::es2015::classes::native::{is_native, is_native_word},
scope::{IdentType, ScopeKind},
};
use fxhash::FxHashMap;
use smallvec::{smallvec, SmallVec};
use std::{cell::RefCell, collections::HashMap};
use std::cell::RefCell;
use swc_atoms::JsWord;
use swc_common::{chain, Span, SyntaxContext};
use swc_common::{chain, SyntaxContext};
use swc_ecma_ast::*;
use swc_ecma_visit::{as_folder, noop_fold_type, noop_visit_mut_type, Fold, FoldWith, VisitMut};
use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith};
mod ops;
#[cfg(test)]
@ -16,6 +17,16 @@ mod tests;
const LOG: bool = false;
trait ToBoxedStr {
fn to_boxed_str(&self) -> Box<str>;
}
impl ToBoxedStr for JsWord {
fn to_boxed_str(&self) -> Box<str> {
(**self).to_owned().into_boxed_str()
}
}
struct Hygiene<'a> {
current: Scope<'a>,
ident_type: IdentType,
@ -31,14 +42,19 @@ impl<'a> Hygiene<'a> {
eprintln!("Declaring {}{:?} ", ident.sym, ctxt);
}
let can_declare_without_renaming = self.current.can_declare(ident.sym.clone(), ctxt);
let can_declare_without_renaming =
self.current.can_declare(&ident.sym.to_boxed_str(), ctxt);
let sym = self.current.change_symbol(ident.sym, ctxt);
if cfg!(debug_assertions) && LOG {
eprintln!("Changed symbol to {}{:?} ", sym, ctxt);
}
self.current
.declared_symbols
.borrow_mut()
.entry(sym.clone())
.or_insert_with(Vec::new)
.entry(sym.to_boxed_str())
.or_default()
.push(ctxt);
if can_declare_without_renaming {
@ -86,7 +102,7 @@ impl<'a> Hygiene<'a> {
let mut i = 0;
loop {
i += 1;
let sym: JsWord = format!("{}{}", sym, i).into();
let sym = format!("{}{}", sym, i);
if !self.current.is_declared(&sym) {
break sym;
@ -99,14 +115,13 @@ impl<'a> Hygiene<'a> {
}
let sym = self.current.change_symbol(sym, ctxt);
let scope = self.current.scope_of(sym.clone(), ctxt);
let boxed_sym = sym.to_boxed_str();
let scope = self.current.scope_of(&boxed_sym, ctxt);
// Update symbol list
let mut declared_symbols = scope.declared_symbols.borrow_mut();
let is_not_renamed = scope.ops.borrow().iter().all(|op| match *op {
ScopeOp::Rename { ref from, .. } => from.0 != sym || from.1 != ctxt,
});
let is_not_renamed = !scope.ops.borrow().rename.contains_key(&(sym.clone(), ctxt));
debug_assert!(
is_not_renamed,
@ -116,7 +131,7 @@ impl<'a> Hygiene<'a> {
scope.ops.borrow(),
);
let old = declared_symbols.entry(sym.clone()).or_default();
let old = declared_symbols.entry(sym.to_boxed_str()).or_default();
assert!(
old.contains(&ctxt),
"{:?} does not contain {}{:?}",
@ -127,23 +142,26 @@ impl<'a> Hygiene<'a> {
old.retain(|c| *c != ctxt);
// debug_assert!(old.is_empty() || old.len() == 1);
let new = declared_symbols.entry(renamed.clone()).or_default();
let new = declared_symbols
.entry(renamed.clone().into_boxed_str())
.or_insert_with(|| Vec::with_capacity(2));
new.push(ctxt);
debug_assert!(new.len() == 1);
scope.ops.borrow_mut().push(ScopeOp::Rename {
from: (sym, ctxt),
to: renamed,
});
scope
.ops
.borrow_mut()
.rename
.insert((sym, ctxt), renamed.into());
}
}
pub fn hygiene() -> impl Fold + 'static {
chain!(
Hygiene {
as_folder(Hygiene {
current: Default::default(),
ident_type: IdentType::Ref,
},
}),
as_folder(MarkClearer)
)
}
@ -153,27 +171,27 @@ struct MarkClearer;
impl VisitMut for MarkClearer {
noop_visit_mut_type!();
fn visit_mut_span(&mut self, span: &mut Span) {
*span = span.with_ctxt(SyntaxContext::empty());
fn visit_mut_ident(&mut self, ident: &mut Ident) {
ident.span.ctxt = SyntaxContext::empty();
}
}
impl<'a> Hygiene<'a> {
fn apply_ops<N>(&mut self, node: N) -> N
fn apply_ops<N>(&mut self, node: &mut N)
where
for<'o> N: FoldWith<Operator<'o>>,
for<'o> N: VisitMutWith<Operator<'o>>,
{
let ops = self.current.ops.borrow();
if ops.is_empty() {
return node;
if ops.rename.is_empty() {
return;
}
node.fold_with(&mut Operator(&ops))
node.visit_mut_with(&mut Operator(&ops))
}
}
impl<'a> Hygiene<'a> {
fn fold_fn(&mut self, ident: Option<Ident>, mut node: Function) -> Function {
fn visit_mut_fn(&mut self, ident: Option<Ident>, node: &mut Function) {
match ident {
Some(ident) => {
self.add_declared_ref(ident);
@ -187,13 +205,15 @@ impl<'a> Hygiene<'a> {
};
folder.ident_type = IdentType::Ref;
node.decorators = node.decorators.fold_with(&mut folder);
node.decorators.visit_mut_with(&mut folder);
folder.ident_type = IdentType::Binding;
node.params = node.params.fold_with(&mut folder);
node.params.visit_mut_with(&mut folder);
folder.ident_type = IdentType::Ref;
node.body = node.body.map(|stmt| stmt.fold_children_with(&mut folder));
node.body
.as_mut()
.map(|stmt| stmt.visit_mut_children_with(&mut folder));
folder.apply_ops(node)
}
@ -208,9 +228,9 @@ struct Scope<'a> {
pub kind: ScopeKind,
/// All references declared in this scope
pub declared_symbols: RefCell<HashMap<JsWord, Vec<SyntaxContext>>>,
pub declared_symbols: RefCell<FxHashMap<Box<str>, Vec<SyntaxContext>>>,
pub(crate) ops: RefCell<Vec<ScopeOp>>,
pub(crate) ops: RefCell<Operations>,
}
impl<'a> Default for Scope<'a> {
@ -230,8 +250,8 @@ impl<'a> Scope<'a> {
}
}
fn scope_of(&self, sym: JsWord, ctxt: SyntaxContext) -> &'a Scope<'_> {
if let Some(prev) = self.declared_symbols.borrow().get(&sym) {
fn scope_of(&self, sym: &Box<str>, ctxt: SyntaxContext) -> &'a Scope<'_> {
if let Some(prev) = self.declared_symbols.borrow().get(sym) {
if prev.contains(&ctxt) {
return self;
}
@ -243,11 +263,11 @@ impl<'a> Scope<'a> {
}
}
fn can_declare(&self, sym: JsWord, ctxt: SyntaxContext) -> bool {
fn can_declare(&self, sym: &Box<str>, ctxt: SyntaxContext) -> bool {
match self.parent {
None => {}
Some(parent) => {
if !parent.can_declare(sym.clone(), ctxt) {
if !parent.can_declare(sym, ctxt) {
return false;
}
}
@ -257,7 +277,7 @@ impl<'a> Scope<'a> {
return false;
}
if let Some(ctxts) = self.declared_symbols.borrow().get(&sym) {
if let Some(ctxts) = self.declared_symbols.borrow().get(sym) {
ctxts.contains(&ctxt)
} else {
// No variable named `sym` is declared
@ -279,7 +299,7 @@ impl<'a> Scope<'a> {
let mut ctxts = smallvec![];
{
if let Some(cxs) = self.declared_symbols.get_mut().get(&sym) {
if let Some(cxs) = self.declared_symbols.get_mut().get(&*sym) {
if cxs.len() != 1 || cxs[0] != ctxt {
ctxts.extend_from_slice(&cxs);
}
@ -289,7 +309,7 @@ impl<'a> Scope<'a> {
let mut cur = self.parent;
while let Some(scope) = cur {
if let Some(cxs) = scope.declared_symbols.borrow().get(&sym) {
if let Some(cxs) = scope.declared_symbols.borrow().get(&*sym) {
if cxs.len() != 1 || cxs[0] != ctxt {
ctxts.extend_from_slice(&cxs);
}
@ -308,17 +328,12 @@ impl<'a> Scope<'a> {
let mut cur = Some(self);
while let Some(scope) = cur {
for op in scope.ops.borrow().iter() {
match *op {
ScopeOp::Rename { ref from, ref to } if from.0 == *sym && from.1 == ctxt => {
if let Some(to) = scope.ops.borrow().rename.get(&(sym.clone(), ctxt)) {
if cfg!(debug_assertions) && LOG {
eprintln!("Changing symbol: {}{:?} -> {}", sym, ctxt, to);
}
sym = to.clone()
}
_ => {}
}
}
cur = scope.parent;
}
@ -326,14 +341,13 @@ impl<'a> Scope<'a> {
sym
}
fn is_declared(&self, sym: &JsWord) -> bool {
fn is_declared(&self, sym: &str) -> bool {
if self.declared_symbols.borrow().contains_key(sym) {
return true;
}
for op in self.ops.borrow().iter() {
match *op {
ScopeOp::Rename { ref to, .. } if sym == to => return true,
_ => {}
for (_, to) in &self.ops.borrow().rename {
if to == sym {
return true;
}
}
match self.parent {
@ -344,6 +358,7 @@ impl<'a> Scope<'a> {
}
#[macro_export]
#[deprecated = "Not a public api"]
macro_rules! track_ident {
() => {
fn fold_export_specifier(&mut self, s: ExportSpecifier) -> ExportSpecifier {
@ -492,168 +507,260 @@ macro_rules! track_ident {
};
}
impl<'a> Fold for Hygiene<'a> {
noop_fold_type!();
macro_rules! track_ident_mut {
() => {
fn visit_mut_export_specifier(&mut self, s: &mut ExportSpecifier) {
let old = self.ident_type;
self.ident_type = IdentType::Ref;
s.visit_mut_children_with(self);
self.ident_type = old;
}
track_ident!();
fn visit_mut_import_specifier(&mut self, s: &mut ImportSpecifier) {
let old = self.ident_type;
self.ident_type = IdentType::Binding;
fn fold_arrow_expr(&mut self, mut node: ArrowExpr) -> ArrowExpr {
match s {
ImportSpecifier::Named(ImportNamedSpecifier { imported: None, .. })
| ImportSpecifier::Namespace(..)
| ImportSpecifier::Default(..) => s.visit_mut_children_with(self),
ImportSpecifier::Named(s) => s.local.visit_mut_with(self),
};
self.ident_type = old;
}
fn visit_mut_setter_prop(&mut self, f: &mut SetterProp) {
let old = self.ident_type;
self.ident_type = IdentType::Binding;
f.param.visit_mut_with(self);
self.ident_type = old;
f.body.visit_mut_with(self);
}
// impl<'a> Fold for $T<'a> {
// fn fold(&mut self, f: GetterProp) -> GetterProp {
// let body = f.body.visit_mut_with(self);
// GetterProp { body, ..c }
// }
// }
fn visit_mut_labeled_stmt(&mut self, s: &mut LabeledStmt) {
let old = self.ident_type;
self.ident_type = IdentType::Label;
s.label.visit_mut_with(self);
self.ident_type = old;
s.body.visit_mut_with(self);
}
fn visit_mut_break_stmt(&mut self, s: &mut BreakStmt) {
let old = self.ident_type;
self.ident_type = IdentType::Label;
s.label.visit_mut_with(self);
self.ident_type = old;
}
fn visit_mut_continue_stmt(&mut self, s: &mut ContinueStmt) {
let old = self.ident_type;
self.ident_type = IdentType::Label;
s.label.visit_mut_with(self);
self.ident_type = old;
}
fn visit_mut_class_decl(&mut self, n: &mut ClassDecl) {
let old = self.ident_type;
self.ident_type = IdentType::Binding;
n.ident.visit_mut_with(self);
self.ident_type = old;
n.class.visit_mut_with(self);
}
fn visit_mut_class_expr(&mut self, n: &mut ClassExpr) {
let old = self.ident_type;
self.ident_type = IdentType::Binding;
n.ident.visit_mut_with(self);
self.ident_type = old;
n.class.visit_mut_with(self);
}
fn visit_mut_key_value_pat_prop(&mut self, n: &mut KeyValuePatProp) {
n.key.visit_mut_with(self);
n.value.visit_mut_with(self);
}
fn visit_mut_class(&mut self, c: &mut Class) {
let old = self.ident_type;
self.ident_type = IdentType::Ref;
c.decorators.visit_mut_with(self);
self.ident_type = IdentType::Ref;
c.super_class.visit_mut_with(self);
self.ident_type = IdentType::Binding;
c.type_params.visit_mut_with(self);
self.ident_type = IdentType::Ref;
c.super_type_params.visit_mut_with(self);
self.ident_type = IdentType::Ref;
c.implements.visit_mut_with(self);
self.ident_type = old;
c.body.visit_mut_with(self);
}
fn visit_mut_prop_name(&mut self, n: &mut PropName) {
match n {
PropName::Computed(c) => {
c.visit_mut_with(self);
}
_ => {}
}
}
};
}
impl<'a> VisitMut for Hygiene<'a> {
noop_visit_mut_type!();
track_ident_mut!();
fn visit_mut_arrow_expr(&mut self, node: &mut ArrowExpr) {
let mut folder = Hygiene {
current: Scope::new(ScopeKind::Fn, Some(&self.current)),
ident_type: IdentType::Ref,
};
folder.ident_type = IdentType::Binding;
node.params = node.params.fold_with(&mut folder);
node.params.visit_mut_with(&mut folder);
folder.ident_type = IdentType::Ref;
node.body = node.body.fold_with(&mut folder);
node.body.visit_mut_with(&mut folder);
folder.apply_ops(node)
}
fn fold_block_stmt(&mut self, node: BlockStmt) -> BlockStmt {
fn visit_mut_block_stmt(&mut self, node: &mut BlockStmt) {
let mut folder = Hygiene {
current: Scope::new(ScopeKind::Block, Some(&self.current)),
ident_type: IdentType::Ref,
};
let node = node.fold_children_with(&mut folder);
node.visit_mut_children_with(&mut folder);
folder.apply_ops(node)
}
fn fold_catch_clause(&mut self, c: CatchClause) -> CatchClause {
fn visit_mut_catch_clause(&mut self, c: &mut CatchClause) {
let mut folder = Hygiene {
current: Scope::new(ScopeKind::Fn, Some(&self.current)),
ident_type: IdentType::Ref,
};
folder.ident_type = IdentType::Binding;
let param = c.param.fold_with(&mut folder);
c.param.visit_mut_with(&mut folder);
folder.ident_type = IdentType::Ref;
let body = c.body.fold_with(&mut folder);
CatchClause { param, body, ..c }
c.body.visit_mut_with(&mut folder);
}
fn fold_constructor(&mut self, c: Constructor) -> Constructor {
fn visit_mut_constructor(&mut self, c: &mut Constructor) {
let old = self.ident_type;
self.ident_type = IdentType::Binding;
let params = c.params.fold_with(self);
c.params.visit_mut_with(self);
self.ident_type = old;
let body = c.body.map(|bs| bs.fold_children_with(self));
let key = c.key.fold_with(self);
let c = Constructor {
params,
body,
key,
..c
};
c.body.as_mut().map(|bs| bs.visit_mut_children_with(self));
c.key.visit_mut_with(self);
self.apply_ops(c)
}
fn fold_expr(&mut self, node: Expr) -> Expr {
fn visit_mut_expr(&mut self, node: &mut Expr) {
let old = self.ident_type;
self.ident_type = IdentType::Ref;
let node = match node {
Expr::Ident(..) => node.fold_children_with(self),
match node {
Expr::Ident(..) => node.visit_mut_children_with(self),
Expr::Member(e) => {
if e.computed {
Expr::Member(MemberExpr {
obj: e.obj.fold_with(self),
prop: e.prop.fold_with(self),
..e
})
e.obj.visit_mut_with(self);
e.prop.visit_mut_with(self);
} else {
Expr::Member(MemberExpr {
obj: e.obj.fold_with(self),
..e
})
e.obj.visit_mut_with(self)
}
}
Expr::This(..) => node,
Expr::This(..) => {}
_ => node.fold_children_with(self),
_ => node.visit_mut_children_with(self),
};
self.ident_type = old;
node
}
fn fold_fn_decl(&mut self, mut node: FnDecl) -> FnDecl {
node.function = self.fold_fn(Some(node.ident.clone()), node.function);
node
fn visit_mut_fn_decl(&mut self, node: &mut FnDecl) {
self.visit_mut_fn(Some(node.ident.clone()), &mut node.function);
}
fn fold_fn_expr(&mut self, mut node: FnExpr) -> FnExpr {
node.function = self.fold_fn(node.ident.clone(), node.function);
node
fn visit_mut_fn_expr(&mut self, node: &mut FnExpr) {
self.visit_mut_fn(node.ident.clone(), &mut node.function);
}
/// Invoked for `IdetifierRefrence` / `BindingIdentifier`
fn fold_ident(&mut self, i: Ident) -> Ident {
fn visit_mut_ident(&mut self, i: &mut Ident) {
if i.sym == js_word!("arguments") || i.sym == js_word!("undefined") {
return i;
return;
}
match self.ident_type {
IdentType::Binding => self.add_declared_ref(i.clone()),
IdentType::Ref => {
// Special cases
if is_native(&i.sym) {
return i;
if is_native_word(&i.sym) {
return;
}
self.add_used_ref(&i);
}
IdentType::Label => {
// We currently does not touch labels
return i;
return;
}
}
}
i
fn visit_mut_module(&mut self, module: &mut Module) {
module.visit_mut_children_with(self);
self.apply_ops(module)
}
fn fold_module(&mut self, module: Module) -> Module {
let module = validate!(module.fold_children_with(self));
validate!(self.apply_ops(module))
}
fn fold_object_lit(&mut self, node: ObjectLit) -> ObjectLit {
fn visit_mut_object_lit(&mut self, node: &mut ObjectLit) {
let mut folder = Hygiene {
current: Scope::new(ScopeKind::Block, Some(&self.current)),
ident_type: IdentType::Ref,
};
let node = node.fold_children_with(&mut folder);
node.visit_mut_children_with(&mut folder);
folder.apply_ops(node)
}
fn fold_try_stmt(&mut self, node: TryStmt) -> TryStmt {
TryStmt {
span: node.span,
block: node.block.fold_children_with(self),
handler: node.handler.fold_with(self),
finalizer: node.finalizer.fold_children_with(self),
}
fn visit_mut_try_stmt(&mut self, node: &mut TryStmt) {
node.block.visit_mut_children_with(self);
node.handler.visit_mut_with(self);
node.finalizer.visit_mut_children_with(self);
}
fn fold_var_declarator(&mut self, decl: VarDeclarator) -> VarDeclarator {
fn visit_mut_var_declarator(&mut self, decl: &mut VarDeclarator) {
let old = self.ident_type;
self.ident_type = IdentType::Binding;
let name = decl.name.fold_with(self);
decl.name.visit_mut_with(self);
self.ident_type = old;
let init = decl.init.fold_with(self);
VarDeclarator { name, init, ..decl }
decl.init.visit_mut_with(self);
}
}

View File

@ -1,25 +1,24 @@
use crate::ext::MapWithMut;
use fxhash::FxHashMap;
use swc_atoms::JsWord;
use swc_common::{util::move_map::MoveMap, Spanned, SyntaxContext, DUMMY_SP};
use swc_ecma_ast::*;
use swc_ecma_visit::{noop_fold_type, Fold, FoldWith};
#[derive(Debug)]
pub(super) enum ScopeOp {
Rename {
from: (JsWord, SyntaxContext),
to: JsWord,
},
use swc_ecma_utils::{ident::IdentLike, Id};
use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith};
#[derive(Debug, Default)]
pub(super) struct Operations {
pub rename: FxHashMap<Id, JsWord>,
}
pub(super) struct Operator<'a>(pub &'a [ScopeOp]);
pub(super) struct Operator<'a>(pub &'a Operations);
impl<'a> Fold for Operator<'a> {
noop_fold_type!();
impl<'a> VisitMut for Operator<'a> {
noop_visit_mut_type!();
fn fold_module_items(&mut self, items: Vec<ModuleItem>) -> Vec<ModuleItem> {
fn visit_mut_module_items(&mut self, items: &mut Vec<ModuleItem>) {
let mut stmts = Vec::with_capacity(items.len());
for item in items {
for mut item in items.take() {
let span = item.span();
macro_rules! export {
@ -44,15 +43,15 @@ impl<'a> Fold for Operator<'a> {
span,
decl:
Decl::Class(ClassDecl {
ident,
class,
mut ident,
mut class,
declare,
}),
})) => {
let class = class.fold_with(self);
class.visit_mut_with(self);
let orig_ident = ident.clone();
match self.rename_ident(ident) {
Ok(ident) => {
match self.rename_ident(&mut ident) {
Ok(..) => {
stmts.push(ModuleItem::Stmt(Stmt::Decl(Decl::Class(ClassDecl {
ident: ident.clone(),
class,
@ -60,7 +59,7 @@ impl<'a> Fold for Operator<'a> {
}))));
export!(orig_ident, ident);
}
Err(ident) => {
Err(..) => {
stmts.push(ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
span,
decl: Decl::Class(ClassDecl {
@ -76,15 +75,15 @@ impl<'a> Fold for Operator<'a> {
span,
decl:
Decl::Fn(FnDecl {
ident,
function,
mut ident,
mut function,
declare,
}),
})) => {
let function = function.fold_with(self);
function.visit_mut_with(self);
let orig_ident = ident.clone();
match self.rename_ident(ident) {
Ok(ident) => {
match self.rename_ident(&mut ident) {
Ok(..) => {
stmts.push(ModuleItem::Stmt(Stmt::Decl(Decl::Fn(FnDecl {
ident: ident.clone(),
function,
@ -92,7 +91,7 @@ impl<'a> Fold for Operator<'a> {
}))));
export!(orig_ident, ident);
}
Err(ident) => {
Err(..) => {
stmts.push(ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
span,
decl: Decl::Fn(FnDecl {
@ -111,13 +110,13 @@ impl<'a> Fold for Operator<'a> {
let decls = var.decls;
let mut renamed: Vec<ExportSpecifier> = vec![];
let decls = decls.move_map(|decl| {
let name = decl.name.fold_with(&mut VarFolder {
let decls = decls.move_map(|mut decl| {
decl.name.visit_mut_with(&mut VarFolder {
orig: self,
renamed: &mut renamed,
});
let init = decl.init.fold_with(self);
VarDeclarator { name, init, ..decl }
decl.init.visit_mut_with(self);
decl
});
if renamed.is_empty() {
stmts.push(ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
@ -139,144 +138,132 @@ impl<'a> Fold for Operator<'a> {
},
)));
}
_ => stmts.push(item.fold_with(self)),
_ => {
item.visit_mut_with(self);
stmts.push(item)
}
}
}
stmts
*items = stmts
}
/// Preserve key of properties.
fn fold_assign_pat_prop(&mut self, p: AssignPatProp) -> AssignPatProp {
match p.value {
Some(value) => AssignPatProp {
value: Some(value.fold_children_with(self)),
..p
},
None => p,
fn visit_mut_assign_pat_prop(&mut self, p: &mut AssignPatProp) {
match &mut p.value {
Some(value) => {
value.visit_mut_children_with(self);
}
None => {}
}
}
fn fold_export_named_specifier(&mut self, s: ExportNamedSpecifier) -> ExportNamedSpecifier {
fn visit_mut_export_named_specifier(&mut self, s: &mut ExportNamedSpecifier) {
if s.exported.is_some() {
return ExportNamedSpecifier {
orig: s.orig.fold_with(self),
..s
};
s.orig.visit_mut_with(self);
return;
}
let exported = s.orig.clone();
match self.rename_ident(s.orig) {
Ok(orig) => ExportNamedSpecifier {
exported: Some(exported),
orig,
..s
},
Err(orig) => ExportNamedSpecifier { orig, ..s },
match self.rename_ident(&mut s.orig) {
Ok(..) => {
s.exported = Some(exported);
}
Err(..) => {}
}
}
fn fold_ident(&mut self, ident: Ident) -> Ident {
fn visit_mut_ident(&mut self, ident: &mut Ident) {
match self.rename_ident(ident) {
Ok(i) | Err(i) => i,
}
}
fn fold_import_named_specifier(&mut self, s: ImportNamedSpecifier) -> ImportNamedSpecifier {
fn visit_mut_import_named_specifier(&mut self, s: &mut ImportNamedSpecifier) {
if s.imported.is_some() {
return ImportNamedSpecifier {
local: s.local.fold_with(self),
..s
};
s.local.visit_mut_with(self);
return;
}
let imported = s.local.clone();
let local = self.rename_ident(s.local);
let local = self.rename_ident(&mut s.local);
match local {
Ok(local) => ImportNamedSpecifier {
imported: Some(imported),
local,
..s
},
Err(local) => ImportNamedSpecifier { local, ..s },
Ok(..) => {
s.imported = Some(imported);
}
Err(..) => {}
}
}
/// Preserve key of properties.
fn fold_key_value_pat_prop(&mut self, p: KeyValuePatProp) -> KeyValuePatProp {
KeyValuePatProp {
key: p.key.fold_with(self),
value: p.value.fold_with(self),
..p
fn visit_mut_key_value_pat_prop(&mut self, p: &mut KeyValuePatProp) {
p.key.visit_mut_with(self);
p.value.visit_mut_with(self);
}
fn visit_mut_key_value_prop(&mut self, p: &mut KeyValueProp) {
p.value.visit_mut_with(self);
}
fn visit_mut_member_expr(&mut self, expr: &mut MemberExpr) {
expr.span.visit_mut_with(self);
expr.obj.visit_mut_with(self);
if expr.computed {
expr.prop.visit_mut_with(self)
}
}
fn fold_key_value_prop(&mut self, p: KeyValueProp) -> KeyValueProp {
KeyValueProp {
value: p.value.fold_with(self),
..p
}
}
fn fold_member_expr(&mut self, expr: MemberExpr) -> MemberExpr {
let span = expr.span.fold_with(self);
let obj = expr.obj.fold_with(self);
let prop = if expr.computed {
expr.prop.fold_with(self)
} else {
expr.prop
};
MemberExpr {
span,
obj,
prop,
computed: expr.computed,
}
}
fn fold_object_pat_prop(&mut self, p: ObjectPatProp) -> ObjectPatProp {
let p = p.fold_children_with(self);
match p {
ObjectPatProp::Assign(p) => match self.rename_ident(p.key.clone()) {
Ok(renamed) => KeyValuePatProp {
key: PropName::Ident(p.key),
fn visit_mut_object_pat_prop(&mut self, n: &mut ObjectPatProp) {
n.visit_mut_children_with(self);
match n {
ObjectPatProp::Assign(p) => {
let mut renamed = p.key.clone();
match self.rename_ident(&mut renamed) {
Ok(..) => {
*n = KeyValuePatProp {
key: PropName::Ident(p.key.take()),
value: Box::new(Pat::Ident(renamed)),
}
.into(),
Err(_) => p.into(),
},
_ => p,
.into();
}
Err(_) => {}
}
}
_ => {}
}
}
fn fold_prop(&mut self, prop: Prop) -> Prop {
fn visit_mut_prop(&mut self, prop: &mut Prop) {
match prop {
Prop::Shorthand(i) => {
match self.rename_ident(i.clone()) {
Ok(renamed) => Prop::KeyValue(KeyValueProp {
let mut renamed = i.clone();
match self.rename_ident(&mut renamed) {
Ok(..) => {
*prop = Prop::KeyValue(KeyValueProp {
key: PropName::Ident(Ident {
// clear mark
span: i.span.with_ctxt(SyntaxContext::empty()),
..i
type_ann: None,
..i.clone()
}),
value: Box::new(Expr::Ident(renamed)),
}),
Err(i) => Prop::Shorthand(i),
})
}
Err(..) => {}
}
}
_ => prop.fold_children_with(self),
_ => prop.visit_mut_children_with(self),
}
}
fn fold_prop_name(&mut self, n: PropName) -> PropName {
fn visit_mut_prop_name(&mut self, n: &mut PropName) {
match n {
PropName::Computed(c) => PropName::Computed(c.fold_with(self)),
_ => n,
PropName::Computed(c) => c.visit_mut_with(self),
_ => {}
}
}
}
@ -286,48 +273,37 @@ struct VarFolder<'a, 'b> {
renamed: &'a mut Vec<ExportSpecifier>,
}
impl Fold for VarFolder<'_, '_> {
noop_fold_type!();
impl VisitMut for VarFolder<'_, '_> {
noop_visit_mut_type!();
fn fold_expr(&mut self, n: Expr) -> Expr {
n
}
#[inline]
fn visit_mut_expr(&mut self, _: &mut Expr) {}
fn fold_ident(&mut self, i: Ident) -> Ident {
fn visit_mut_ident(&mut self, i: &mut Ident) {
let orig = i.clone();
match self.orig.rename_ident(i) {
Ok(i) => {
Ok(..) => {
self.renamed
.push(ExportSpecifier::Named(ExportNamedSpecifier {
span: i.span,
exported: Some(orig),
orig: i.clone(),
}));
i
}
Err(i) => i,
Err(..) => {}
}
}
}
impl<'a> Operator<'a> {
/// Returns `Ok(renamed_ident)` if ident should be renamed.
fn rename_ident(&mut self, ident: Ident) -> Result<Ident, Ident> {
for op in self.0 {
match *op {
ScopeOp::Rename { ref from, ref to }
if *from.0 == ident.sym && from.1 == ident.span.ctxt() =>
{
return Ok(Ident {
// Clear mark
span: ident.span.with_ctxt(SyntaxContext::empty()),
sym: to.clone(),
..ident
});
fn rename_ident(&mut self, ident: &mut Ident) -> Result<(), ()> {
if let Some(sym) = self.0.rename.get(&ident.to_id()) {
ident.span = ident.span.with_ctxt(SyntaxContext::empty());
ident.sym = sym.clone();
return Ok(());
}
_ => {}
}
}
Err(ident)
Err(())
}
}

View File

@ -3,6 +3,7 @@ use crate::tests::HygieneVisualizer;
use std::collections::HashMap;
use swc_common::{hygiene::*, DUMMY_SP};
use swc_ecma_parser::Syntax;
use swc_ecma_visit::{Fold, FoldWith};
struct Marker {
map: HashMap<JsWord, Mark>,
@ -1183,93 +1184,3 @@ fn exported_class_1() {
export { Foo1 as Foo };",
);
}
// #[test]
// fn issue_598() {
// test_module(
// |tester| {
// let mark1 = Mark::fresh(Mark::root());
// let mark2 = Mark::fresh(Mark::root());
// Ok(tester
// .parse_module(
// "actual1.js",
// "export function foo() {
// console.log(i18n(_templateObject()));
// console.log(i18n(_templateObject()));
// }",
// )?
// .fold_with(&mut OnceMarker::new(&[(
// "_templateObject",
// &[mark1, mark2],
// )])))
// },
// "export function foo() {
// console.log(i18n(_templateObject1()));
// console.log(i18n(_templateObject()));
// }",
// );
// }
// #[test]
// fn issue_598_2() {
// test_module(
// |tester| {
// let mark1 = Mark::fresh(Mark::root());
// let mark2 = Mark::fresh(Mark::root());
// let mark3 = Mark::fresh(Mark::root());
// Ok(tester
// .parse_module(
// "actual1.js",
// "export function foo() {
// console.log(i18n(_templateObject()));
// console.log(i18n(_templateObject()));
// console.log(i18n(_templateObject()));
// }",
// )?
// .fold_with(&mut OnceMarker::new(&[(
// "_templateObject",
// &[mark1, mark2, mark3],
// )])))
// },
// "export function foo() {
// console.log(i18n(_templateObject1()));
// console.log(i18n(_templateObject2()));
// console.log(i18n(_templateObject()));
// }",
// );
// }
// #[test]
// fn issue_598_3() {
// test_module(
// |tester| {
// let mark1 = Mark::fresh(Mark::root());
// let mark2 = Mark::fresh(Mark::root());
// let mark3 = Mark::fresh(Mark::root());
// let mark4 = Mark::fresh(Mark::root());
// Ok(tester
// .parse_module(
// "actual1.js",
// "export function foo() {
// console.log(i18n(_templateObject()));
// console.log(i18n(_templateObject()));
// console.log(i18n(_templateObject()));
// console.log(i18n(_templateObject()));
// }",
// )?
// .fold_with(&mut OnceMarker::new(&[(
// "_templateObject",
// &[mark1, mark2, mark3, mark4],
// )])))
// },
// "export function foo() {
// console.log(i18n(_templateObject1()));
// console.log(i18n(_templateObject2()));
// console.log(i18n(_templateObject3()));
// console.log(i18n(_templateObject()));
// }",
// );
// }

File diff suppressed because it is too large Load Diff

View File

@ -1096,7 +1096,8 @@ var instance = new SomeClass({
console.log('CORRECT FUNCTION CALLED');
}
});
instance.call();"
instance.call();",
ok_if_code_eq
);
test!(
@ -1220,7 +1221,8 @@ export class HygieneTest {
_defineProperty(this, 'duration', DURATION);
this.duration = duration ?? DURATION;
}
}"
}",
ok_if_code_eq
);
identical_ts!(ts_resolver_001, "type A = B;");

View File

@ -201,11 +201,11 @@ pub(crate) fn test_transform<F, P>(
}
let actual = actual
.fold_with(&mut crate::debug::validator::Validator { name: "actual-1" })
.fold_with(&mut crate::hygiene::hygiene())
.fold_with(&mut crate::debug::validator::Validator { name: "actual-2" })
.fold_with(&mut crate::fixer::fixer(None))
.fold_with(&mut crate::debug::validator::Validator { name: "actual-3" });
.fold_with(&mut as_folder(DropSpan {
preserve_ctxt: false,
}));
if actual == expected {
return Ok(());

View File

@ -101,6 +101,70 @@ impl Strip {
}
impl Strip {
/// Returns [Some] if the method should be called again.
fn handle_expr<'a>(&mut self, n: &'a mut Expr) -> Vec<&'a mut Expr> {
match n {
Expr::Bin(BinExpr { left, right, .. }) => return vec![&mut **left, &mut **right],
_ => {}
}
n.map_with_mut(|expr| {
let mut expr = match expr {
Expr::TsAs(TsAsExpr { expr, .. })
| Expr::TsNonNull(TsNonNullExpr { expr, .. })
| Expr::TsTypeAssertion(TsTypeAssertion { expr, .. })
| Expr::TsConstAssertion(TsConstAssertion { expr, .. })
| Expr::TsTypeCast(TsTypeCastExpr { expr, .. }) => {
let mut expr = *expr;
expr.visit_mut_with(self);
expr
}
_ => expr,
};
let expr = match expr {
Expr::Bin(..) => expr,
Expr::Member(MemberExpr {
span,
mut obj,
mut prop,
computed,
}) => {
obj.visit_mut_with(self);
let prop = if computed {
prop.visit_mut_with(self);
prop
} else {
match *prop {
Expr::Ident(i) => Box::new(Expr::Ident(Ident {
optional: false,
type_ann: None,
..i
})),
_ => prop,
}
};
Expr::Member(MemberExpr {
span,
obj,
prop,
computed,
})
}
_ => {
expr.visit_mut_children_with(self);
expr
}
};
expr
});
vec![]
}
fn handle_enum<T>(&mut self, e: TsEnumDecl, stmts: &mut Vec<T>)
where
T: StmtLike,
@ -577,59 +641,22 @@ impl VisitMut for Strip {
};
}
fn visit_mut_expr(&mut self, expr: &mut Expr) {
expr.map_with_mut(|expr| {
let mut expr = match expr {
Expr::TsAs(TsAsExpr { expr, .. })
| Expr::TsNonNull(TsNonNullExpr { expr, .. })
| Expr::TsTypeAssertion(TsTypeAssertion { expr, .. })
| Expr::TsConstAssertion(TsConstAssertion { expr, .. })
| Expr::TsTypeCast(TsTypeCastExpr { expr, .. }) => {
let mut expr = *expr;
expr.visit_mut_with(self);
expr
}
_ => expr,
};
fn visit_mut_expr(&mut self, n: &mut Expr) {
let mut stack = vec![n];
loop {
let mut new_stack = vec![];
for expr in stack {
let res = self.handle_expr(expr);
let expr = match expr {
Expr::Member(MemberExpr {
span,
mut obj,
mut prop,
computed,
}) => {
obj.visit_mut_with(self);
let prop = if computed {
prop.visit_mut_with(self);
prop
} else {
match *prop {
Expr::Ident(i) => Box::new(Expr::Ident(Ident {
optional: false,
type_ann: None,
..i
})),
_ => prop,
new_stack.extend(res)
}
};
Expr::Member(MemberExpr {
span,
obj,
prop,
computed,
})
if new_stack.is_empty() {
return;
}
_ => {
expr.visit_mut_children_with(self);
expr
}
};
expr
});
stack = new_stack;
}
}
fn visit_mut_ident(&mut self, i: &mut Ident) {

View File

@ -5,46 +5,22 @@ use std::{
fmt,
fs::{create_dir_all, remove_dir_all, OpenOptions},
io::{self, Write},
mem::replace,
path::Path,
process::Command,
sync::{Arc, RwLock},
};
use swc_common::{
chain, comments::SingleThreadedComments, errors::Handler, sync::Lrc, FileName, SourceMap,
comments::SingleThreadedComments, errors::Handler, sync::Lrc, FileName, SourceMap, DUMMY_SP,
};
use swc_ecma_ast::*;
use swc_ecma_codegen::Emitter;
use swc_ecma_parser::{error::Error, lexer::Lexer, Parser, StringInput, Syntax};
use swc_ecma_transforms::helpers::{inject_helpers, HELPERS};
use swc_ecma_utils::DropSpan;
use swc_ecma_visit::{as_folder, Fold, FoldWith};
use swc_ecma_visit::{as_folder, Fold, FoldWith, VisitMut, VisitMutWith};
use tempfile::tempdir_in;
pub fn validating(name: &'static str, tr: impl Fold + 'static) -> Box<dyn Fold> {
Box::new(chain!(
tr,
swc_ecma_transforms::debug::validator::Validator { name },
))
}
macro_rules! validating {
($folder:expr) => {{
common::validating(stringify!($folder), $folder)
}};
}
macro_rules! validate {
($e:expr) => {{
use swc_ecma_visit::FoldWith;
if cfg!(debug_assertions) {
$e.fold_with(&mut swc_ecma_transforms::debug::validator::Validator {
name: concat!(file!(), ':', line!(), ':', column!()),
})
} else {
$e
}
}};
}
use testing::assert_eq;
pub struct Tester<'a> {
pub cm: Lrc<SourceMap>,
@ -127,12 +103,12 @@ impl<'a> Tester<'a> {
res?
};
let module = validate!(module)
.fold_with(&mut tr)
.fold_with(&mut as_folder(DropSpan {
let mut module = module.fold_with(&mut tr);
module.visit_mut_with(&mut DropSpan {
preserve_ctxt: true,
}))
.fold_with(&mut Normalizer);
});
module.visit_mut_with(&mut Normalizer);
Ok(module)
}
@ -211,11 +187,11 @@ where
}
let actual = actual
.fold_with(&mut swc_ecma_transforms::debug::validator::Validator { name: "actual-1" })
.fold_with(&mut swc_ecma_transforms::hygiene())
.fold_with(&mut swc_ecma_transforms::debug::validator::Validator { name: "actual-2" })
.fold_with(&mut swc_ecma_transforms::fixer(None))
.fold_with(&mut swc_ecma_transforms::debug::validator::Validator { name: "actual-3" });
.fold_with(&mut as_folder(DropSpan {
preserve_ctxt: false,
}));
if actual == expected {
return Ok(());
@ -401,16 +377,19 @@ impl Write for Buf {
}
struct Normalizer;
impl Fold for Normalizer {
fn fold_pat_or_expr(&mut self, node: PatOrExpr) -> PatOrExpr {
let node = node.fold_children_with(self);
impl VisitMut for Normalizer {
fn visit_mut_pat_or_expr(&mut self, node: &mut PatOrExpr) {
node.visit_mut_children_with(self);
match node {
PatOrExpr::Pat(pat) => match *pat {
Pat::Expr(expr) => PatOrExpr::Expr(expr),
_ => PatOrExpr::Pat(pat),
PatOrExpr::Pat(pat) => match &mut **pat {
Pat::Expr(e) => {
let e = replace(e, Box::new(Expr::Invalid(Invalid { span: DUMMY_SP })));
*node = PatOrExpr::Expr(e);
}
_ => {}
},
_ => node,
_ => {}
}
}
}

View File

@ -17,19 +17,19 @@ mod common;
struct ParenRemover;
impl Fold for ParenRemover {
fn fold_expr(&mut self, expr: Expr) -> Expr {
let expr = validate!(expr);
let expr = expr;
let span = expr.span();
let expr = expr.fold_children_with(self);
validate!(match expr {
match expr {
Expr::Paren(ParenExpr { expr, .. }) => match *expr {
Expr::Member(e) => Expr::Member(MemberExpr { span, ..e }),
Expr::New(e) => Expr::New(NewExpr { span, ..e }),
_ => *expr,
},
_ => expr,
})
}
}
}
@ -40,10 +40,10 @@ fn syntax() -> Syntax {
fn tr() -> impl Fold {
chain!(
ParenRemover,
validating!(arrow()),
validating!(parameters()),
validating!(destructuring(destructuring::Config { loose: false })),
validating!(function_name()),
arrow(),
parameters(),
destructuring(destructuring::Config { loose: false }),
function_name(),
async_to_generator(),
fixer(None)
)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,17 +1,16 @@
[package]
name = "swc_ecma_visit"
version = "0.17.0"
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
license = "Apache-2.0/MIT"
repository = "https://github.com/swc-project/swc.git"
documentation = "https://swc-project.github.io/rustdoc/swc_ecma_visit/"
description = "Visitors for swc ecmascript nodes which works on stable rustc"
documentation = "https://swc-project.github.io/rustdoc/swc_ecma_visit/"
edition = "2018"
license = "Apache-2.0/MIT"
name = "swc_ecma_visit"
repository = "https://github.com/swc-project/swc.git"
version = "0.17.1"
[dependencies]
swc_atoms = { version = "0.2", path = "../../atoms" }
swc_common = { version = "0.10.0", path = "../../common" }
swc_ecma_ast = { version = "0.31.0", path ="../ast" }
swc_visit = { version = "0.2.0", path ="../../visit" }
num-bigint = { version = "0.2", features = ["serde"] }
num-bigint = {version = "0.2", features = ["serde"]}
swc_atoms = {version = "0.2", path = "../../atoms"}
swc_common = {version = "0.10.0", path = "../../common"}
swc_ecma_ast = {version = "0.31.0", path = "../ast"}
swc_visit = {version = "0.2.0", path = "../../visit"}

View File

@ -20,11 +20,13 @@ where
B: Fold,
{
#[inline(always)]
fn fold_module(&mut self, n: Module) -> Module {
let n = self.first.fold_module(n);
self.second.fold_module(n)
}
#[inline(always)]
fn fold_script(&mut self, n: Script) -> Script {
let n = self.first.fold_script(n);
self.second.fold_script(n)

View File

@ -1,6 +1,6 @@
{
"name": "@swc/core",
"version": "1.2.23",
"version": "1.2.23-alpha.1",
"description": "Super-fast alternative for babel",
"homepage": "https://swc-project.github.io",
"main": "./index.js",

View File

@ -1,19 +1,19 @@
[package]
name = "testing"
version = "0.10.0"
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
license = "Apache-2.0/MIT"
repository = "https://github.com/swc-project/swc.git"
documentation = "https://swc-project.github.io/rustdoc/testing/"
description = "Testing utilities for the swc project."
documentation = "https://swc-project.github.io/rustdoc/testing/"
edition = "2018"
license = "Apache-2.0/MIT"
name = "testing"
repository = "https://github.com/swc-project/swc.git"
version = "0.10.1"
[dependencies]
swc_common = { version = "0.10.0", path ="../common", features = ["tty-emitter"] }
once_cell = "1"
regex = "1"
difference = "2"
log = "0.4"
env_logger = "0.7.1"
ansi_term = "0.12.1"
difference = "2"
env_logger = "0.7.1"
log = "0.4"
once_cell = "1"
pretty_assertions = "0.6.1"
regex = "1"
swc_common = {version = "0.10.0", path = "../common", features = ["tty-emitter"]}

View File

@ -3,6 +3,7 @@
pub use self::output::{NormalizedOutput, StdErr, StdOut, TestOutput};
use difference::Changeset;
use once_cell::sync::Lazy;
pub use pretty_assertions::{assert_eq, assert_ne};
use regex::Regex;
use std::{
fmt,