From 80da0981bd31e612d97d5c9fb10101891cabac90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Tue, 4 Oct 2022 21:42:39 +0900 Subject: [PATCH] feat(es/compat): Support iterator protocol on loose mode (#6034) --- crates/swc/src/builder.rs | 3 +- .../swc/tests/exec/issues-6xxx/6028/1/.swcrc | 64 +++++++++ .../swc/tests/exec/issues-6xxx/6028/1/exec.js | 4 + .../issues-2xxx/2091/case1/output/index.js | 2 +- .../fixture/issues-6xxx/6028/input/.swcrc | 11 ++ .../fixture/issues-6xxx/6028/input/input.js | 4 + .../fixture/issues-6xxx/6028/output/input.js | 11 ++ crates/swc_ecma_preset_env/src/lib.rs | 1 + .../_create_for_of_iterator_helper_loose.js | 19 +++ .../src/helpers/mod.rs | 2 + .../src/es2015/for_of.rs | 126 +++++++++++++++++- .../tests/es2015_for_of.rs | 51 +++++-- .../swc_ecma_transforms_module/tests/amd.rs | 5 +- .../tests/common_js.rs | 5 +- .../_create_for_of_iterator_helper_loose.mjs | 20 +++ 15 files changed, 312 insertions(+), 16 deletions(-) create mode 100644 crates/swc/tests/exec/issues-6xxx/6028/1/.swcrc create mode 100644 crates/swc/tests/exec/issues-6xxx/6028/1/exec.js create mode 100644 crates/swc/tests/fixture/issues-6xxx/6028/input/.swcrc create mode 100644 crates/swc/tests/fixture/issues-6xxx/6028/input/input.js create mode 100644 crates/swc/tests/fixture/issues-6xxx/6028/output/input.js create mode 100644 crates/swc_ecma_transforms_base/src/helpers/_create_for_of_iterator_helper_loose.js create mode 100644 packages/swc-helpers/src/_create_for_of_iterator_helper_loose.mjs diff --git a/crates/swc/src/builder.rs b/crates/swc/src/builder.rs index bf9282a15ae..6c4ceb73361 100644 --- a/crates/swc/src/builder.rs +++ b/crates/swc/src/builder.rs @@ -291,7 +291,8 @@ impl<'a, 'b, P: swc_ecma_visit::Fold> PassBuilder<'a, 'b, P> { loose: self.loose }, for_of: compat::es2015::for_of::Config { - assume_array: self.loose + assume_array: false, + loose: self.loose }, spread: compat::es2015::spread::Config { loose: self.loose }, destructuring: compat::es2015::destructuring::Config { diff --git a/crates/swc/tests/exec/issues-6xxx/6028/1/.swcrc b/crates/swc/tests/exec/issues-6xxx/6028/1/.swcrc new file mode 100644 index 00000000000..3c422ff70e1 --- /dev/null +++ b/crates/swc/tests/exec/issues-6xxx/6028/1/.swcrc @@ -0,0 +1,64 @@ +{ + "jsc": { + "parser": { + "syntax": "ecmascript", + "jsx": false + }, + "target": "es5", + "loose": true, + "minify": { + "compress": { + "arguments": false, + "arrows": true, + "booleans": true, + "booleans_as_integers": false, + "collapse_vars": true, + "comparisons": true, + "computed_props": true, + "conditionals": true, + "dead_code": true, + "directives": false, + "drop_console": false, + "drop_debugger": true, + "evaluate": true, + "expression": false, + "hoist_funs": false, + "hoist_props": true, + "hoist_vars": false, + "if_return": true, + "join_vars": true, + "keep_classnames": false, + "keep_fargs": true, + "keep_fnames": false, + "keep_infinity": false, + "loops": true, + "negate_iife": true, + "properties": true, + "reduce_funcs": false, + "reduce_vars": false, + "side_effects": true, + "switches": true, + "typeofs": true, + "unsafe": false, + "unsafe_arrows": false, + "unsafe_comps": false, + "unsafe_Function": false, + "unsafe_math": false, + "unsafe_symbols": false, + "unsafe_methods": false, + "unsafe_proto": false, + "unsafe_regexp": false, + "unsafe_undefined": false, + "unused": true, + "const_to_let": true, + "pristine_globals": true + }, + "mangle": false + } + }, + "module": { + "type": "commonjs" + }, + "minify": false, + "isModule": false +} \ No newline at end of file diff --git a/crates/swc/tests/exec/issues-6xxx/6028/1/exec.js b/crates/swc/tests/exec/issues-6xxx/6028/1/exec.js new file mode 100644 index 00000000000..759f00fc2b0 --- /dev/null +++ b/crates/swc/tests/exec/issues-6xxx/6028/1/exec.js @@ -0,0 +1,4 @@ +const m = new Map([[1, 2]]) +for (const k of m.keys()) { + console.log(k) +} \ No newline at end of file diff --git a/crates/swc/tests/fixture/issues-2xxx/2091/case1/output/index.js b/crates/swc/tests/fixture/issues-2xxx/2091/case1/output/index.js index e58def27e92..992a1d4c170 100644 --- a/crates/swc/tests/fixture/issues-2xxx/2091/case1/output/index.js +++ b/crates/swc/tests/fixture/issues-2xxx/2091/case1/output/index.js @@ -1 +1 @@ -export function test(list){var cur=list.findIndex(function(p){return 1==p});~cur||(cur=list.findIndex(function(p){return 0!==p}));for(var _i=0;_i= o.length) return { done: true }; + return { done: false, value: o[i++] }; + } + } + throw new TypeError("Invalid attempt to iterate non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); +} \ No newline at end of file diff --git a/crates/swc_ecma_transforms_base/src/helpers/mod.rs b/crates/swc_ecma_transforms_base/src/helpers/mod.rs index 0d9f0b63037..a9bb77eab16 100644 --- a/crates/swc_ecma_transforms_base/src/helpers/mod.rs +++ b/crates/swc_ecma_transforms_base/src/helpers/mod.rs @@ -356,6 +356,8 @@ define_helpers!(Helpers { possible_constructor_return ), + create_for_of_iterator_helper_loose: (unsupported_iterable_to_array), + ts_decorate: (), ts_generator: (), ts_metadata: (), diff --git a/crates/swc_ecma_transforms_compat/src/es2015/for_of.rs b/crates/swc_ecma_transforms_compat/src/es2015/for_of.rs index 85d1e292090..4eaa7c6359e 100644 --- a/crates/swc_ecma_transforms_compat/src/es2015/for_of.rs +++ b/crates/swc_ecma_transforms_compat/src/es2015/for_of.rs @@ -4,7 +4,10 @@ use serde::Deserialize; use swc_atoms::js_word; use swc_common::{util::take::Take, Mark, Spanned, DUMMY_SP}; use swc_ecma_ast::*; -use swc_ecma_transforms_base::perf::{ParExplode, Parallel}; +use swc_ecma_transforms_base::{ + helper, + perf::{ParExplode, Parallel}, +}; use swc_ecma_transforms_macros::parallel; use swc_ecma_utils::{ alias_if_required, member_expr, prepend_stmt, private_ident, quote_ident, ExprFactory, @@ -57,6 +60,7 @@ pub fn for_of(c: Config) -> impl Fold + VisitMut { #[derive(Debug, Clone, Copy, Default, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Config { + pub loose: bool, pub assume_array: bool, } @@ -84,7 +88,7 @@ impl ForOf { .. }: ForOfStmt, ) -> Stmt { - if right.is_array() || self.c.assume_array { + if right.is_array() || (self.c.assume_array && !self.c.loose) { // Convert to normal for loop if rhs is array // // babel's output: @@ -199,6 +203,124 @@ impl ForOf { }; } + // Loose mode + if self.c.loose { + let iterator = private_ident!("_iterator"); + let step = private_ident!("_step"); + + let decls = vec![ + VarDeclarator { + span: DUMMY_SP, + name: iterator.clone().into(), + init: Some(Box::new(Expr::Call(CallExpr { + span: DUMMY_SP, + callee: helper!( + create_for_of_iterator_helper_loose, + "createForOfIteratorHelperLoose" + ), + args: vec![right.as_arg()], + type_args: Default::default(), + }))), + definite: Default::default(), + }, + VarDeclarator { + span: DUMMY_SP, + name: step.clone().into(), + init: None, + definite: Default::default(), + }, + ]; + + let mut body = match *body { + Stmt::Block(b) => b, + _ => BlockStmt { + span: DUMMY_SP, + stmts: vec![*body], + }, + }; + + match left { + VarDeclOrPat::VarDecl(var) => { + assert_eq!( + var.decls.len(), + 1, + "Variable declarator of for of loop cannot contain multiple entries" + ); + prepend_stmt( + &mut body.stmts, + VarDecl { + span: DUMMY_SP, + kind: var.kind, + declare: false, + decls: vec![VarDeclarator { + span: DUMMY_SP, + name: var.decls.into_iter().next().unwrap().name, + init: Some(step.clone().make_member(quote_ident!("value")).into()), + definite: false, + }], + } + .into(), + ) + } + + VarDeclOrPat::Pat(pat) => prepend_stmt( + &mut body.stmts, + AssignExpr { + span: DUMMY_SP, + left: pat.into(), + op: op!("="), + right: step.clone().make_member(quote_ident!("value")).into(), + } + .into_stmt(), + ), + } + + // !(_step = _iterator()).done; + let test = Box::new(Expr::Unary(UnaryExpr { + span: DUMMY_SP, + op: op!("!"), + arg: AssignExpr { + span: DUMMY_SP, + op: op!("="), + left: step.into(), + right: CallExpr { + span: DUMMY_SP, + callee: iterator.as_callee(), + args: vec![], + type_args: Default::default(), + } + .into(), + } + .make_member(quote_ident!("done")) + .into(), + })); + + let stmt = Stmt::For(ForStmt { + span, + init: Some( + VarDecl { + span: DUMMY_SP, + kind: VarDeclKind::Var, + declare: false, + decls, + } + .into(), + ), + test: Some(test), + update: None, + body: Box::new(Stmt::Block(body)), + }); + return match label { + Some(label) => LabeledStmt { + span, + label, + body: Box::new(stmt), + } + .into(), + _ => stmt, + }; + } + let var_span = left.span().apply_mark(Mark::fresh(Mark::root())); let mut body = match *body { diff --git a/crates/swc_ecma_transforms_compat/tests/es2015_for_of.rs b/crates/swc_ecma_transforms_compat/tests/es2015_for_of.rs index 681bd3f4e81..d7b0572b894 100644 --- a/crates/swc_ecma_transforms_compat/tests/es2015_for_of.rs +++ b/crates/swc_ecma_transforms_compat/tests/es2015_for_of.rs @@ -293,7 +293,10 @@ try { // for_of_as_array_for_of test!( syntax(), - |_| for_of(Config { assume_array: true }), + |_| for_of(Config { + assume_array: true, + ..Default::default() + }), for_of_as_array_for_of, r#" let elm; @@ -317,7 +320,10 @@ for(let _i = 0; _i < array.length; _i++){ // for_of_as_array_for_of_array_pattern test!( syntax(), - |_| for_of(Config { assume_array: true }), + |_| for_of(Config { + assume_array: true, + ..Default::default() + }), for_of_as_array_for_of_array_pattern, r#" let elm; @@ -340,7 +346,10 @@ for(let _i = 0; _i < array.length; _i++){ // regression_redeclare_array_8913 test!( syntax(), - |_| for_of(Config { assume_array: true }), + |_| for_of(Config { + assume_array: true, + ..Default::default() + }), regression_redeclare_array_8913, r#" function f(...t) { @@ -364,7 +373,10 @@ function f(...t) { // for_of_as_array_for_of_declaration_array_pattern test!( syntax(), - |_| for_of(Config { assume_array: true }), + |_| for_of(Config { + assume_array: true, + ..Default::default() + }), for_of_as_array_for_of_declaration_array_pattern, r#" for (const [elm] of array) { @@ -383,7 +395,10 @@ for(let _i = 0; _i < array.length; _i++){ // for_of_as_array_for_of_expression test!( syntax(), - |_| for_of(Config { assume_array: true }), + |_| for_of(Config { + assume_array: true, + ..Default::default() + }), for_of_as_array_for_of_expression, r#" let i; @@ -404,7 +419,10 @@ for(let _i = 0; _i < items.length; _i++){ // for_of_as_array_for_of_declaration test!( syntax(), - |_| for_of(Config { assume_array: true }), + |_| for_of(Config { + assume_array: true, + ..Default::default() + }), for_of_as_array_for_of_declaration, r#" for (const elm of array) { @@ -445,7 +463,10 @@ expect(results).toEqual([1, 2, 3]); // for_of_as_array_for_of_static_declaration test!( syntax(), - |_| for_of(Config { assume_array: true }), + |_| for_of(Config { + assume_array: true, + ..Default::default() + }), for_of_as_array_for_of_static_declaration, r#" const array = []; @@ -469,7 +490,10 @@ for(let _i = 0; _i < array.length; _i++){ // for_of_as_array_for_of_static test!( syntax(), - |_| for_of(Config { assume_array: true }), + |_| for_of(Config { + assume_array: true, + ..Default::default() + }), for_of_as_array_for_of_static, r#" const array = []; @@ -495,7 +519,10 @@ for (let _i = 0; _i < array.length; _i++) { // for_of_as_array_for_of_import_es2015 test!( syntax(), - |_| for_of(Config { assume_array: true }), + |_| for_of(Config { + assume_array: true, + ..Default::default() + }), for_of_as_array_for_of_import_es2015, r#" import { array } from "foo"; @@ -560,7 +587,10 @@ try { // regression_if_label_3858 test!( syntax(), - |_| for_of(Config { assume_array: true }), + |_| for_of(Config { + assume_array: true, + ..Default::default() + }), regression_if_label_3858, r#" if ( true ) @@ -591,6 +621,7 @@ fn fixture(input: PathBuf) { resolver(Mark::new(), top_level_mark, false), for_of(Config { assume_array: false, + ..Default::default() }) ) }, diff --git a/crates/swc_ecma_transforms_module/tests/amd.rs b/crates/swc_ecma_transforms_module/tests/amd.rs index 3001ba13b2a..dc0be64f4bd 100644 --- a/crates/swc_ecma_transforms_module/tests/amd.rs +++ b/crates/swc_ecma_transforms_module/tests/amd.rs @@ -63,7 +63,10 @@ fn esm_to_amd(input: PathBuf) { test!( syntax(), |t| chain!( - for_of(for_of::Config { assume_array: true }), + for_of(for_of::Config { + assume_array: true, + ..Default::default() + }), tr(Default::default(), false, t.comments.clone()) ), for_of_as_array_for_of_import_amd, diff --git a/crates/swc_ecma_transforms_module/tests/common_js.rs b/crates/swc_ecma_transforms_module/tests/common_js.rs index b9658347205..e9d5ae6d537 100644 --- a/crates/swc_ecma_transforms_module/tests/common_js.rs +++ b/crates/swc_ecma_transforms_module/tests/common_js.rs @@ -65,7 +65,10 @@ fn esm_to_cjs(input: PathBuf) { test!( syntax(), |tester| chain!( - for_of(for_of::Config { assume_array: true }), + for_of(for_of::Config { + assume_array: true, + ..Default::default() + }), tr(Default::default(), false, tester.comments.clone()) ), for_of_as_array_for_of_import_commonjs, diff --git a/packages/swc-helpers/src/_create_for_of_iterator_helper_loose.mjs b/packages/swc-helpers/src/_create_for_of_iterator_helper_loose.mjs new file mode 100644 index 00000000000..443c7fad65c --- /dev/null +++ b/packages/swc-helpers/src/_create_for_of_iterator_helper_loose.mjs @@ -0,0 +1,20 @@ +import unsupportedIterableToArray from "./_unsupported_iterable_to_array.mjs"; + +export default function _createForOfIteratorHelperLoose(o, allowArrayLike) { + var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; + if (it) return (it = it.call(o)).next.bind(it); + // Fallback for engines without symbol support + if ( + Array.isArray(o) || + (it = unsupportedIterableToArray(o)) || + (allowArrayLike && o && typeof o.length === "number") + ) { + if (it) o = it; + var i = 0; + return function () { + if (i >= o.length) return { done: true }; + return { done: false, value: o[i++] }; + } + } + throw new TypeError("Invalid attempt to iterate non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); +} \ No newline at end of file