diff --git a/packages/babel-helpers/src/helpers.js b/packages/babel-helpers/src/helpers.js index d008cce43af5..591fcb55e903 100644 --- a/packages/babel-helpers/src/helpers.js +++ b/packages/babel-helpers/src/helpers.js @@ -1079,10 +1079,16 @@ helpers.createForOfIteratorHelper = helper("7.9.0")` // e: error (called whenever something throws) // f: finish (always called at the end) - export default function _createForOfIteratorHelper(o) { + export default function _createForOfIteratorHelper(o, allowArrayLike) { + var it; if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) { // Fallback for engines without symbol support - if (Array.isArray(o) || (o = unsupportedIterableToArray(o))) { + if ( + Array.isArray(o) || + (it = unsupportedIterableToArray(o)) || + (allowArrayLike && o && typeof o.length === "number") + ) { + if (it) o = it; var i = 0; var F = function(){}; return { @@ -1099,7 +1105,7 @@ helpers.createForOfIteratorHelper = helper("7.9.0")` throw new TypeError("Invalid attempt to iterate non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } - var it, normalCompletion = true, didErr = false, err; + var normalCompletion = true, didErr = false, err; return { s() { @@ -1128,22 +1134,29 @@ helpers.createForOfIteratorHelper = helper("7.9.0")` helpers.createForOfIteratorHelperLoose = helper("7.9.0")` import unsupportedIterableToArray from "unsupportedIterableToArray"; - export default function _createForOfIteratorHelperLoose(o) { - var i = 0; + export default function _createForOfIteratorHelperLoose(o, allowArrayLike) { + var it; if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) { // Fallback for engines without symbol support - if (Array.isArray(o) || (o = unsupportedIterableToArray(o))) + 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."); } - i = o[Symbol.iterator](); - return i.next.bind(i); + it = o[Symbol.iterator](); + return it.next.bind(it); } `; diff --git a/packages/babel-plugin-transform-for-of/src/index.js b/packages/babel-plugin-transform-for-of/src/index.js index 429f2899a93a..0b964f1fd35e 100644 --- a/packages/babel-plugin-transform-for-of/src/index.js +++ b/packages/babel-plugin-transform-for-of/src/index.js @@ -6,7 +6,7 @@ import transformWithoutHelper from "./no-helper-implementation"; export default declare((api, options) => { api.assertVersion(7); - const { loose, assumeArray } = options; + const { loose, assumeArray, allowArrayLike } = options; if (loose === true && assumeArray === true) { throw new Error( @@ -14,6 +14,12 @@ export default declare((api, options) => { ); } + if (assumeArray === true && allowArrayLike === true) { + throw new Error( + `The assumeArray and allowArrayLike options cannot be used together in @babel/plugin-transform-for-of`, + ); + } + if (assumeArray) { return { name: "transform-for-of", @@ -86,12 +92,12 @@ export default declare((api, options) => { `); const buildForOfLoose = template.statements(` - for (var ITERATOR_HELPER = CREATE_ITERATOR_HELPER(OBJECT), STEP_KEY; + for (var ITERATOR_HELPER = CREATE_ITERATOR_HELPER(OBJECT, ALLOW_ARRAY_LIKE), STEP_KEY; !(STEP_KEY = ITERATOR_HELPER()).done;) BODY; `); const buildForOf = template.statements(` - var ITERATOR_HELPER = CREATE_ITERATOR_HELPER(OBJECT), STEP_KEY; + var ITERATOR_HELPER = CREATE_ITERATOR_HELPER(OBJECT, ALLOW_ARRAY_LIKE), STEP_KEY; try { for (ITERATOR_HELPER.s(); !(STEP_KEY = ITERATOR_HELPER.n()).done;) BODY; } catch (err) { @@ -200,6 +206,7 @@ export default declare((api, options) => { const nodes = builder.build({ CREATE_ITERATOR_HELPER: state.addHelper(builder.helper), ITERATOR_HELPER: scope.generateUidIdentifier("iterator"), + ALLOW_ARRAY_LIKE: allowArrayLike ? t.booleanLiteral(true) : null, STEP_KEY: t.identifier(stepKey), OBJECT: node.right, BODY: node.body, diff --git a/packages/babel-plugin-transform-for-of/test/fixtures/loose-allowArrayLike/holes/exec.js b/packages/babel-plugin-transform-for-of/test/fixtures/loose-allowArrayLike/holes/exec.js new file mode 100644 index 000000000000..4bd0a5df0097 --- /dev/null +++ b/packages/babel-plugin-transform-for-of/test/fixtures/loose-allowArrayLike/holes/exec.js @@ -0,0 +1,7 @@ +var p2 = { 0: "a", 2: "c", length: 3 }; + +var arr = []; +for (var x of p2) arr.push(x); + +expect(arr).toEqual(["a", undefined, "c"]); +expect(1 in arr).toBe(true); // Not holey diff --git a/packages/babel-plugin-transform-for-of/test/fixtures/loose-allowArrayLike/holes/options.json b/packages/babel-plugin-transform-for-of/test/fixtures/loose-allowArrayLike/holes/options.json new file mode 100644 index 000000000000..92f525cc511d --- /dev/null +++ b/packages/babel-plugin-transform-for-of/test/fixtures/loose-allowArrayLike/holes/options.json @@ -0,0 +1,6 @@ +{ + "plugins": [ + ["external-helpers", { "helperVersion": "7.100.0" }], + ["transform-for-of", { "loose": true, "allowArrayLike": true }] + ] +} diff --git a/packages/babel-plugin-transform-for-of/test/fixtures/loose-allowArrayLike/length-cropped/exec.js b/packages/babel-plugin-transform-for-of/test/fixtures/loose-allowArrayLike/length-cropped/exec.js new file mode 100644 index 000000000000..16d81476a95e --- /dev/null +++ b/packages/babel-plugin-transform-for-of/test/fixtures/loose-allowArrayLike/length-cropped/exec.js @@ -0,0 +1,6 @@ +var p2 = { 0: "b", 1: "c", 2: "d", length: 2 }; + +var arr = []; +for (var x of p2) arr.push(x); + +expect(arr).toEqual(["b", "c"]); diff --git a/packages/babel-plugin-transform-for-of/test/fixtures/loose-allowArrayLike/length-cropped/options.json b/packages/babel-plugin-transform-for-of/test/fixtures/loose-allowArrayLike/length-cropped/options.json new file mode 100644 index 000000000000..92f525cc511d --- /dev/null +++ b/packages/babel-plugin-transform-for-of/test/fixtures/loose-allowArrayLike/length-cropped/options.json @@ -0,0 +1,6 @@ +{ + "plugins": [ + ["external-helpers", { "helperVersion": "7.100.0" }], + ["transform-for-of", { "loose": true, "allowArrayLike": true }] + ] +} diff --git a/packages/babel-plugin-transform-for-of/test/fixtures/loose-allowArrayLike/simple/exec.js b/packages/babel-plugin-transform-for-of/test/fixtures/loose-allowArrayLike/simple/exec.js new file mode 100644 index 000000000000..d016662a6048 --- /dev/null +++ b/packages/babel-plugin-transform-for-of/test/fixtures/loose-allowArrayLike/simple/exec.js @@ -0,0 +1,6 @@ +var p2 = { 0: "b", 1: "c", 2: "d", length: 3 }; + +var arr = []; +for (var x of p2) arr.push(x); + +expect(arr).toEqual(["b", "c", "d"]); diff --git a/packages/babel-plugin-transform-for-of/test/fixtures/loose-allowArrayLike/simple/input.js b/packages/babel-plugin-transform-for-of/test/fixtures/loose-allowArrayLike/simple/input.js new file mode 100644 index 000000000000..9bcb06d816be --- /dev/null +++ b/packages/babel-plugin-transform-for-of/test/fixtures/loose-allowArrayLike/simple/input.js @@ -0,0 +1 @@ +for (var x of p2) arr.push(x); diff --git a/packages/babel-plugin-transform-for-of/test/fixtures/loose-allowArrayLike/simple/options.json b/packages/babel-plugin-transform-for-of/test/fixtures/loose-allowArrayLike/simple/options.json new file mode 100644 index 000000000000..92f525cc511d --- /dev/null +++ b/packages/babel-plugin-transform-for-of/test/fixtures/loose-allowArrayLike/simple/options.json @@ -0,0 +1,6 @@ +{ + "plugins": [ + ["external-helpers", { "helperVersion": "7.100.0" }], + ["transform-for-of", { "loose": true, "allowArrayLike": true }] + ] +} diff --git a/packages/babel-plugin-transform-for-of/test/fixtures/loose-allowArrayLike/simple/output.js b/packages/babel-plugin-transform-for-of/test/fixtures/loose-allowArrayLike/simple/output.js new file mode 100644 index 000000000000..1675334b0961 --- /dev/null +++ b/packages/babel-plugin-transform-for-of/test/fixtures/loose-allowArrayLike/simple/output.js @@ -0,0 +1,4 @@ +for (var _iterator = babelHelpers.createForOfIteratorHelperLoose(p2, true), _step; !(_step = _iterator()).done;) { + var x = _step.value; + arr.push(x); +} diff --git a/packages/babel-plugin-transform-for-of/test/fixtures/spec-allowArrayLike/holes/exec.js b/packages/babel-plugin-transform-for-of/test/fixtures/spec-allowArrayLike/holes/exec.js new file mode 100644 index 000000000000..4bd0a5df0097 --- /dev/null +++ b/packages/babel-plugin-transform-for-of/test/fixtures/spec-allowArrayLike/holes/exec.js @@ -0,0 +1,7 @@ +var p2 = { 0: "a", 2: "c", length: 3 }; + +var arr = []; +for (var x of p2) arr.push(x); + +expect(arr).toEqual(["a", undefined, "c"]); +expect(1 in arr).toBe(true); // Not holey diff --git a/packages/babel-plugin-transform-for-of/test/fixtures/spec-allowArrayLike/holes/options.json b/packages/babel-plugin-transform-for-of/test/fixtures/spec-allowArrayLike/holes/options.json new file mode 100644 index 000000000000..d708a284b707 --- /dev/null +++ b/packages/babel-plugin-transform-for-of/test/fixtures/spec-allowArrayLike/holes/options.json @@ -0,0 +1,6 @@ +{ + "plugins": [ + ["external-helpers", { "helperVersion": "7.100.0" }], + ["transform-for-of", { "allowArrayLike": true }] + ] +} diff --git a/packages/babel-plugin-transform-for-of/test/fixtures/spec-allowArrayLike/length-cropped/exec.js b/packages/babel-plugin-transform-for-of/test/fixtures/spec-allowArrayLike/length-cropped/exec.js new file mode 100644 index 000000000000..16d81476a95e --- /dev/null +++ b/packages/babel-plugin-transform-for-of/test/fixtures/spec-allowArrayLike/length-cropped/exec.js @@ -0,0 +1,6 @@ +var p2 = { 0: "b", 1: "c", 2: "d", length: 2 }; + +var arr = []; +for (var x of p2) arr.push(x); + +expect(arr).toEqual(["b", "c"]); diff --git a/packages/babel-plugin-transform-for-of/test/fixtures/spec-allowArrayLike/length-cropped/options.json b/packages/babel-plugin-transform-for-of/test/fixtures/spec-allowArrayLike/length-cropped/options.json new file mode 100644 index 000000000000..d708a284b707 --- /dev/null +++ b/packages/babel-plugin-transform-for-of/test/fixtures/spec-allowArrayLike/length-cropped/options.json @@ -0,0 +1,6 @@ +{ + "plugins": [ + ["external-helpers", { "helperVersion": "7.100.0" }], + ["transform-for-of", { "allowArrayLike": true }] + ] +} diff --git a/packages/babel-plugin-transform-for-of/test/fixtures/spec-allowArrayLike/simple/exec.js b/packages/babel-plugin-transform-for-of/test/fixtures/spec-allowArrayLike/simple/exec.js new file mode 100644 index 000000000000..d016662a6048 --- /dev/null +++ b/packages/babel-plugin-transform-for-of/test/fixtures/spec-allowArrayLike/simple/exec.js @@ -0,0 +1,6 @@ +var p2 = { 0: "b", 1: "c", 2: "d", length: 3 }; + +var arr = []; +for (var x of p2) arr.push(x); + +expect(arr).toEqual(["b", "c", "d"]); diff --git a/packages/babel-plugin-transform-for-of/test/fixtures/spec-allowArrayLike/simple/input.js b/packages/babel-plugin-transform-for-of/test/fixtures/spec-allowArrayLike/simple/input.js new file mode 100644 index 000000000000..9bcb06d816be --- /dev/null +++ b/packages/babel-plugin-transform-for-of/test/fixtures/spec-allowArrayLike/simple/input.js @@ -0,0 +1 @@ +for (var x of p2) arr.push(x); diff --git a/packages/babel-plugin-transform-for-of/test/fixtures/spec-allowArrayLike/simple/options.json b/packages/babel-plugin-transform-for-of/test/fixtures/spec-allowArrayLike/simple/options.json new file mode 100644 index 000000000000..d708a284b707 --- /dev/null +++ b/packages/babel-plugin-transform-for-of/test/fixtures/spec-allowArrayLike/simple/options.json @@ -0,0 +1,6 @@ +{ + "plugins": [ + ["external-helpers", { "helperVersion": "7.100.0" }], + ["transform-for-of", { "allowArrayLike": true }] + ] +} diff --git a/packages/babel-plugin-transform-for-of/test/fixtures/spec-allowArrayLike/simple/output.js b/packages/babel-plugin-transform-for-of/test/fixtures/spec-allowArrayLike/simple/output.js new file mode 100644 index 000000000000..9dbae0e22992 --- /dev/null +++ b/packages/babel-plugin-transform-for-of/test/fixtures/spec-allowArrayLike/simple/output.js @@ -0,0 +1,13 @@ +var _iterator = babelHelpers.createForOfIteratorHelper(p2, true), + _step; + +try { + for (_iterator.s(); !(_step = _iterator.n()).done;) { + var x = _step.value; + arr.push(x); + } +} catch (err) { + _iterator.e(err); +} finally { + _iterator.f(); +} diff --git a/packages/babel-preset-env/test/fixtures/sanity/block-scoping-for-of/output.js b/packages/babel-preset-env/test/fixtures/sanity/block-scoping-for-of/output.js index 8deec063a326..b34cca803f71 100644 --- a/packages/babel-preset-env/test/fixtures/sanity/block-scoping-for-of/output.js +++ b/packages/babel-preset-env/test/fixtures/sanity/block-scoping-for-of/output.js @@ -6,7 +6,7 @@ function _iterableToArrayLimit(arr, i) { if (typeof Symbol === "undefined" || !( function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } -function _createForOfIteratorHelper(o) { if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) { if (Array.isArray(o) || (o = _unsupportedIterableToArray(o))) { var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var it, normalCompletion = true, didErr = false, err; return { s: function s() { it = o[Symbol.iterator](); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; } +function _createForOfIteratorHelper(o, allowArrayLike) { var it; if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = o[Symbol.iterator](); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(n); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }