fix(es/transforms/compat): Fix block scoping (#2916)

swc_ecma_transforms_compat:
 - `block_scoping`: Track if we are in nested loops.
 - `block_scoping`: Don't treat `break` nor `continue` in nested loops as leaper. (https://github.com/vercel/next.js/issues/31757, Closes #2799, Closes #2915)
 - `block_scoping`: Don't recurse into nested loops while looking for functions. (Closes #2622)
This commit is contained in:
Donny/강동윤 2021-11-30 13:11:09 +09:00 committed by GitHub
parent 57fb69262d
commit 028d0ce2c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 161 additions and 5 deletions

View File

@ -163,6 +163,7 @@ impl BlockScoping {
has_return: false,
mutated,
in_switch_case: false,
in_nested_loop: false,
};
body_stmt.visit_mut_with(&mut flow_helper);
@ -699,6 +700,8 @@ struct FlowHelper<'a> {
all: &'a Vec<Id>,
mutated: AHashMap<Id, SyntaxContext>,
in_switch_case: bool,
in_nested_loop: bool,
}
impl<'a> FlowHelper<'a> {
@ -738,6 +741,38 @@ impl VisitMut for FlowHelper<'_> {
n.visit_mut_children_with(self);
}
/// https://github.com/swc-project/swc/pull/2916
fn visit_mut_do_while_stmt(&mut self, s: &mut DoWhileStmt) {
let old = self.in_nested_loop;
self.in_nested_loop = true;
s.visit_mut_children_with(self);
self.in_nested_loop = old;
}
/// https://github.com/swc-project/swc/pull/2916
fn visit_mut_for_in_stmt(&mut self, s: &mut ForInStmt) {
let old = self.in_nested_loop;
self.in_nested_loop = true;
s.visit_mut_children_with(self);
self.in_nested_loop = old;
}
/// https://github.com/swc-project/swc/pull/2916
fn visit_mut_for_of_stmt(&mut self, s: &mut ForOfStmt) {
let old = self.in_nested_loop;
self.in_nested_loop = true;
s.visit_mut_children_with(self);
self.in_nested_loop = old;
}
/// https://github.com/swc-project/swc/pull/2916
fn visit_mut_for_stmt(&mut self, s: &mut ForStmt) {
let old = self.in_nested_loop;
self.in_nested_loop = true;
s.visit_mut_children_with(self);
self.in_nested_loop = old;
}
/// noop
fn visit_mut_function(&mut self, _f: &mut Function) {}
@ -746,6 +781,10 @@ impl VisitMut for FlowHelper<'_> {
match node {
Stmt::Continue(..) => {
if self.in_nested_loop {
return;
}
self.has_continue = true;
*node = Stmt::Return(ReturnStmt {
span,
@ -761,7 +800,7 @@ impl VisitMut for FlowHelper<'_> {
});
}
Stmt::Break(..) => {
if self.in_switch_case {
if self.in_switch_case || self.in_nested_loop {
return;
}
self.has_break = true;
@ -816,6 +855,14 @@ impl VisitMut for FlowHelper<'_> {
}
n.visit_mut_children_with(self);
}
/// https://github.com/swc-project/swc/pull/2916
fn visit_mut_while_stmt(&mut self, s: &mut WhileStmt) {
let old = self.in_nested_loop;
self.in_nested_loop = true;
s.visit_mut_children_with(self);
self.in_nested_loop = old;
}
}
struct MutationHandler<'a> {
@ -938,7 +985,32 @@ impl Visit for FunctionFinder {
self.found = true;
}
/// Do not recurse into nested loop.
///
/// https://github.com/swc-project/swc/issues/2622
fn visit_do_while_stmt(&mut self, _: &DoWhileStmt, _: &dyn Node) {}
/// Do not recurse into nested loop.
///
/// https://github.com/swc-project/swc/issues/2622
fn visit_for_in_stmt(&mut self, _: &ForInStmt, _: &dyn Node) {}
/// Do not recurse into nested loop.
///
/// https://github.com/swc-project/swc/issues/2622
fn visit_for_of_stmt(&mut self, _: &ForOfStmt, _: &dyn Node) {}
/// Do not recurse into nested loop.
///
/// https://github.com/swc-project/swc/issues/2622
fn visit_for_stmt(&mut self, _: &ForStmt, _: &dyn Node) {}
fn visit_function(&mut self, _: &Function, _: &dyn Node) {
self.found = true
}
/// Do not recurse into nested loop.
///
/// https://github.com/swc-project/swc/issues/2622
fn visit_while_stmt(&mut self, _: &WhileStmt, _: &dyn Node) {}
}

View File

@ -1,6 +1,11 @@
use std::{fs::read_to_string, path::PathBuf};
use swc_common::{chain, comments::NoopComments, Mark};
use swc_ecma_parser::Syntax;
use swc_ecma_transforms_compat::es2015::for_of::{for_of, Config};
use swc_ecma_transforms_base::resolver::resolver_with_mark;
use swc_ecma_transforms_compat::es2015::{
self,
for_of::{for_of, Config},
};
use swc_ecma_transforms_testing::{compare_stdout, test, test_exec};
fn syntax() -> Syntax {
@ -579,9 +584,32 @@ fn fixture(input: PathBuf) {
compare_stdout(
Syntax::default(),
|_| {
for_of(Config {
assume_array: false,
})
let top_level_mark = Mark::fresh(Mark::root());
chain!(
resolver_with_mark(top_level_mark),
for_of(Config {
assume_array: false,
})
)
},
&input,
);
}
#[testing::fixture("tests/fixture/for-of/**/exec.js")]
fn fixture_es2015(input: PathBuf) {
let input = read_to_string(&input).unwrap();
compare_stdout(
Syntax::default(),
|_| {
let top_level_mark = Mark::fresh(Mark::root());
chain!(
resolver_with_mark(top_level_mark),
es2015::es2015(top_level_mark, Some(NoopComments), Default::default())
)
},
&input,
);

View File

@ -0,0 +1,8 @@
for (let a = 0; a < 2; a++) {
for (let b = 0; b < 2; b++) {
() => { };
for (let c = 0; c < 2; c++) {
console.log(b);
}
}
}

View File

@ -0,0 +1,12 @@
const block = () => {
for (const value in { a: 1, b: 2, c: 3 }) {
for (let i = 0; i < 10; i++) {
function something() { }
continue
return { value }
}
}
}
console.log(block());
console.log('OK?')

View File

@ -0,0 +1,17 @@
function foo(baz) {
for (const g in baz) {
console.log(g);
for (let j = 0; j < g.length; j++) {
console.log(j);
}
}
}
console.log(foo({
a: [1],
b: [2, 2],
c: [3, 3, 3]
}))

View File

@ -0,0 +1,10 @@
let message = 0;
for (let x of [1, 2, 3, 4, 5]) {
for (let y of ["a", "b", "c", "d"]) {
console.log("Message", ++message, x, y);
[].forEach(() => { });
break;
}
}
console.log("WHY", message == 5);

View File

@ -0,0 +1,9 @@
let message = 0;
for (let x of [1, 2, 3, 4, 5]) {
for (let y of ["a", "b", "c", "d"]) {
console.log("Message", ++message, x, y);
break;
}
}
console.log("WHY", message == 5);