diff --git a/crates/swc/tests/fixture/issues-3xxx/issue-3417/1/output/index.ts b/crates/swc/tests/fixture/issues-3xxx/issue-3417/1/output/index.ts index 7001b38a892..85d23db7a14 100644 --- a/crates/swc/tests/fixture/issues-3xxx/issue-3417/1/output/index.ts +++ b/crates/swc/tests/fixture/issues-3xxx/issue-3417/1/output/index.ts @@ -79,10 +79,7 @@ export function selectRooms(building) { FLOOR_MAPPING[levelId], ]; }; - for(var _iterator2 = sortedRooms[Symbol.iterator](), _step2; !(_iteratorNormalCompletion1 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion1 = true){ - var _ret = _loop(_iterator2, _step2); - if (_ret === "continue") continue; - } + for(var _iterator2 = sortedRooms[Symbol.iterator](), _step2; !(_iteratorNormalCompletion1 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion1 = true)_loop(_iterator2, _step2); } catch (err) { _didIteratorError1 = true; _iteratorError1 = err; diff --git a/crates/swc_ecma_transforms_compat/src/es2015/block_scoping/mod.rs b/crates/swc_ecma_transforms_compat/src/es2015/block_scoping/mod.rs index 94454d68622..0aaaec32a8a 100644 --- a/crates/swc_ecma_transforms_compat/src/es2015/block_scoping/mod.rs +++ b/crates/swc_ecma_transforms_compat/src/es2015/block_scoping/mod.rs @@ -1,7 +1,8 @@ use std::mem::take; +use indexmap::IndexMap; use smallvec::SmallVec; -use swc_atoms::js_word; +use swc_atoms::{js_word, JsWord}; use swc_common::{ chain, collections::AHashMap, util::take::Take, Mark, Spanned, SyntaxContext, DUMMY_SP, }; @@ -176,9 +177,9 @@ impl BlockScoping { let mut flow_helper = FlowHelper { all: &args, - has_continue: false, has_break: false, has_return: false, + label: IndexMap::new(), mutated, in_switch_case: false, in_nested_loop: false, @@ -264,7 +265,7 @@ impl BlockScoping { type_args: None, }; - if flow_helper.has_return || flow_helper.has_continue || flow_helper.has_break { + if flow_helper.has_return || flow_helper.has_break || !flow_helper.label.is_empty() { let ret = private_ident!("_ret"); let mut stmts = vec![ @@ -282,11 +283,9 @@ impl BlockScoping { })), ]; - let use_switch = flow_helper.has_break && flow_helper.has_continue; - - let check_ret = if flow_helper.has_return { + if flow_helper.has_return { // if (_typeof(_ret) === "object") return _ret.v; - Some( + stmts.push( IfStmt { span: DUMMY_SP, test: Box::new(Expr::Bin(BinExpr { @@ -315,88 +314,51 @@ impl BlockScoping { } .into(), ) - } else { - None - }; + } - if use_switch { - let mut cases = vec![]; - - if flow_helper.has_break { - cases.push(SwitchCase { + if flow_helper.has_break { + stmts.push( + IfStmt { span: DUMMY_SP, - test: Some(Box::new(quote_str!("break").into())), - // TODO: Handle labelled statements - cons: vec![Stmt::Break(BreakStmt { + test: ret.clone().make_eq(quote_str!("break")).into(), + cons: Stmt::Break(BreakStmt { span: DUMMY_SP, label: None, - })], - }); - } - - if flow_helper.has_continue { - cases.push(SwitchCase { - span: DUMMY_SP, - test: Some(Box::new(quote_str!("continue").into())), - // TODO: Handle labelled statements - cons: vec![Stmt::Continue(ContinueStmt { - span: DUMMY_SP, - label: None, - })], - }); - } - - cases.extend(check_ret.map(|stmt| SwitchCase { - span: DUMMY_SP, - test: None, - cons: vec![stmt], - })); + }) + .into(), + alt: None, + } + .into(), + ); + } + if !flow_helper.label.is_empty() { stmts.push( SwitchStmt { span: DUMMY_SP, discriminant: Box::new(ret.into()), - cases, + cases: flow_helper + .label + .into_iter() + .map(|(key, label)| SwitchCase { + span: DUMMY_SP, + test: Some(Box::new(key.into())), + cons: vec![match label { + Label::Break(id) => Stmt::Break(BreakStmt { + span: DUMMY_SP, + label: Some(id), + }), + + Label::Continue(id) => Stmt::Continue(ContinueStmt { + span: DUMMY_SP, + label: Some(id), + }), + }], + }) + .collect(), } .into(), ); - } else { - // - if flow_helper.has_break { - stmts.push( - IfStmt { - span: DUMMY_SP, - test: ret.clone().make_eq(quote_str!("break")).into(), - // TODO: Handle labelled statements - cons: Stmt::Break(BreakStmt { - span: DUMMY_SP, - label: None, - }) - .into(), - alt: None, - } - .into(), - ); - } - - if flow_helper.has_continue { - stmts.push( - IfStmt { - span: DUMMY_SP, - test: ret.make_eq(quote_str!("continue")).into(), - // TODO: Handle labelled statements - cons: Stmt::Continue(ContinueStmt { - span: DUMMY_SP, - label: None, - }) - .into(), - alt: None, - } - .into(), - ); - } - - stmts.extend(check_ret); } *body = Box::new( @@ -715,11 +677,11 @@ impl Visit for InfectionFinder<'_> { } } -#[derive(Debug)] struct FlowHelper<'a> { - has_continue: bool, has_break: bool, has_return: bool, + // label cannot be shadowed, so it's pretty safe to use JsWord + label: IndexMap, all: &'a Vec, mutated: AHashMap, in_switch_case: bool, @@ -727,6 +689,11 @@ struct FlowHelper<'a> { in_nested_loop: bool, } +enum Label { + Break(Ident), + Continue(Ident), +} + impl<'a> FlowHelper<'a> { fn check(&mut self, i: Id) { if self.all.contains(&i) { @@ -803,34 +770,50 @@ impl VisitMut for FlowHelper<'_> { let span = node.span(); match node { - Stmt::Continue(..) => { + Stmt::Continue(ContinueStmt { label, .. }) => { if self.in_nested_loop { return; } - self.has_continue = true; + let value = if let Some(label) = label { + let value: JsWord = format!("continue|{}", label.sym).into(); + self.label + .insert(value.clone(), Label::Continue(label.clone())); + value + } else { + "continue".into() + }; + *node = Stmt::Return(ReturnStmt { span, arg: Some( Expr::Lit(Lit::Str(Str { span, - value: "continue".into(), + value, raw: None, })) .into(), ), }); } - Stmt::Break(..) => { + Stmt::Break(BreakStmt { label, .. }) => { if self.in_switch_case || self.in_nested_loop { return; } - self.has_break = true; + let value = if let Some(label) = label { + let value: JsWord = format!("break|{}", label.sym).into(); + self.label + .insert(value.clone(), Label::Break(label.clone())); + value + } else { + self.has_break = true; + "break".into() + }; *node = Stmt::Return(ReturnStmt { span, arg: Some(Box::new(Expr::Lit(Lit::Str(Str { span, - value: "break".into(), + value, raw: None, })))), }); diff --git a/crates/swc_ecma_transforms_compat/tests/es2015_block_scoping.rs b/crates/swc_ecma_transforms_compat/tests/es2015_block_scoping.rs index 3604061e2dd..ccd4fc818ff 100644 --- a/crates/swc_ecma_transforms_compat/tests/es2015_block_scoping.rs +++ b/crates/swc_ecma_transforms_compat/tests/es2015_block_scoping.rs @@ -241,10 +241,7 @@ test!( }; var vars = []; var elem = null, name, val; - for(var i = 0; i < this.elements.length; i++){ - var _ret = _loop(i); - if (_ret === \"continue\") continue; - } + for(var i = 0; i < this.elements.length; i++)_loop(i); return vars; }; " @@ -361,10 +358,7 @@ test!( if (i1 % 2 === 0) return i = i1, "continue"; i = i1, void 0; }; - for(var i = 0; i < 5; i++){ - var _ret = _loop(i); - if (_ret === "continue") continue; - } + for(var i = 0; i < 5; i++)_loop(i); "# ); @@ -998,6 +992,75 @@ test!( " ); +test!( + ::swc_ecma_parser::Syntax::default(), + |_| block_scoping(), + issue_4196, + " + for (let i = 0; i < 2; i++) { + if (i === 0) continue + if (i === 1) break + + [1].forEach((_) => { + console.log(i) + }) + } + ", + r#" + var _loop = function(i) { + if (i === 0) return "continue"; + if (i === 1) return "break"; + [ + 1 + ].forEach((_)=>{ + console.log(i); + }); + }; + for(var i = 0; i < 2; i++){ + var _ret = _loop(i); + if (_ret === "break") break; + } + "# +); + +test!( + ::swc_ecma_parser::Syntax::default(), + |_| block_scoping(), + labeled_break, + " + a: + b: + for (let i = 0; i < 2; i++) { + if (i === 0) continue a + if (i === 1) break b + + [1].forEach((_) => { + console.log(i) + }) + } + ", + r#" + var _loop = function(i) { + if (i === 0) return "continue|a"; + if (i === 1) return "break|b"; + [ + 1 + ].forEach((_)=>{ + console.log(i); + }); + }; + a: b: for(var i = 0; i < 2; i++){ + var _ret = _loop(i); + switch(_ret){ + case "continue|a": + continue a; + case "break|b": + break b; + } + } + "# +); + test_exec!( ::swc_ecma_parser::Syntax::default(), |_| block_scoping(),