From 71d0f21d4bb2a5821e99ec80365be91e227dd75a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Sat, 14 Mar 2020 18:02:13 +0100 Subject: [PATCH] Add "allowArrayLike" option to the destructuring and spread transforms When enabled, this option makes Babel treat array-like objects as if they were iterables. This makes it possible to avoid a Symbol polyfill when spreading or destructuring DOM lists in browsers without Symbol.iterator support. --- packages/babel-helpers/src/helpers.js | 12 ++++++++++++ .../src/index.js | 13 +++++++++++-- .../test/fixtures/allowArrayLike/holes/exec.js | 6 ++++++ .../test/fixtures/allowArrayLike/holes/options.json | 6 ++++++ .../fixtures/allowArrayLike/length-cropped/exec.js | 6 ++++++ .../allowArrayLike/length-cropped/options.json | 6 ++++++ .../test/fixtures/allowArrayLike/simple/exec.js | 6 ++++++ .../test/fixtures/allowArrayLike/simple/input.js | 1 + .../fixtures/allowArrayLike/simple/options.json | 6 ++++++ .../test/fixtures/allowArrayLike/simple/output.js | 4 ++++ packages/babel-plugin-transform-spread/src/index.js | 4 ++-- .../test/fixtures/allowArrayLike/holes/exec.js | 6 ++++++ .../test/fixtures/allowArrayLike/holes/options.json | 6 ++++++ .../fixtures/allowArrayLike/length-cropped/exec.js | 5 +++++ .../allowArrayLike/length-cropped/options.json | 6 ++++++ .../test/fixtures/allowArrayLike/simple/exec.js | 5 +++++ .../test/fixtures/allowArrayLike/simple/input.js | 1 + .../fixtures/allowArrayLike/simple/options.json | 6 ++++++ .../test/fixtures/allowArrayLike/simple/output.js | 1 + packages/babel-traverse/src/scope/index.js | 9 ++++++++- 20 files changed, 110 insertions(+), 5 deletions(-) create mode 100644 packages/babel-plugin-transform-destructuring/test/fixtures/allowArrayLike/holes/exec.js create mode 100644 packages/babel-plugin-transform-destructuring/test/fixtures/allowArrayLike/holes/options.json create mode 100644 packages/babel-plugin-transform-destructuring/test/fixtures/allowArrayLike/length-cropped/exec.js create mode 100644 packages/babel-plugin-transform-destructuring/test/fixtures/allowArrayLike/length-cropped/options.json create mode 100644 packages/babel-plugin-transform-destructuring/test/fixtures/allowArrayLike/simple/exec.js create mode 100644 packages/babel-plugin-transform-destructuring/test/fixtures/allowArrayLike/simple/input.js create mode 100644 packages/babel-plugin-transform-destructuring/test/fixtures/allowArrayLike/simple/options.json create mode 100644 packages/babel-plugin-transform-destructuring/test/fixtures/allowArrayLike/simple/output.js create mode 100644 packages/babel-plugin-transform-spread/test/fixtures/allowArrayLike/holes/exec.js create mode 100644 packages/babel-plugin-transform-spread/test/fixtures/allowArrayLike/holes/options.json create mode 100644 packages/babel-plugin-transform-spread/test/fixtures/allowArrayLike/length-cropped/exec.js create mode 100644 packages/babel-plugin-transform-spread/test/fixtures/allowArrayLike/length-cropped/options.json create mode 100644 packages/babel-plugin-transform-spread/test/fixtures/allowArrayLike/simple/exec.js create mode 100644 packages/babel-plugin-transform-spread/test/fixtures/allowArrayLike/simple/input.js create mode 100644 packages/babel-plugin-transform-spread/test/fixtures/allowArrayLike/simple/options.json create mode 100644 packages/babel-plugin-transform-spread/test/fixtures/allowArrayLike/simple/output.js diff --git a/packages/babel-helpers/src/helpers.js b/packages/babel-helpers/src/helpers.js index d008cce43af5..69304082716b 100644 --- a/packages/babel-helpers/src/helpers.js +++ b/packages/babel-helpers/src/helpers.js @@ -977,6 +977,18 @@ helpers.arrayWithHoles = helper("7.0.0-beta.0")` } `; +helpers.maybeArrayLike = helper("7.9.0")` + import arrayLikeToArray from "arrayLikeToArray"; + + export default function _maybeArrayLike(next, arr, i) { + if (arr && !Array.isArray(arr) && typeof arr.length === "number") { + var len = arr.length; + return arrayLikeToArray(arr, i !== void 0 && i < len ? i : len); + } + return next(arr, i); + } +`; + helpers.iterableToArray = helper("7.0.0-beta.0")` export default function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter); diff --git a/packages/babel-plugin-transform-destructuring/src/index.js b/packages/babel-plugin-transform-destructuring/src/index.js index 714e5f5afbd6..5a71f0be641d 100644 --- a/packages/babel-plugin-transform-destructuring/src/index.js +++ b/packages/babel-plugin-transform-destructuring/src/index.js @@ -4,7 +4,11 @@ import { types as t } from "@babel/core"; export default declare((api, options) => { api.assertVersion(7); - const { loose = false, useBuiltIns = false } = options; + const { + loose = false, + useBuiltIns = false, + allowArrayLike = false, + } = options; if (typeof loose !== "boolean") { throw new Error(`.loose must be a boolean or undefined`); @@ -85,6 +89,7 @@ export default declare((api, options) => { this.scope = opts.scope; this.kind = opts.kind; this.arrayOnlySpread = opts.arrayOnlySpread; + this.allowArrayLike = opts.allowArrayLike; this.addHelper = opts.addHelper; } @@ -141,7 +146,7 @@ export default declare((api, options) => { ) { return node; } else { - return this.scope.toArray(node, count); + return this.scope.toArray(node, count, this.allowArrayLike); } } @@ -523,6 +528,7 @@ export default declare((api, options) => { scope: scope, nodes: nodes, arrayOnlySpread, + allowArrayLike, addHelper: name => this.addHelper(name), }); @@ -548,6 +554,7 @@ export default declare((api, options) => { scope: scope, nodes: nodes, arrayOnlySpread, + allowArrayLike, addHelper: name => this.addHelper(name), }); destructuring.init(pattern, ref); @@ -566,6 +573,7 @@ export default declare((api, options) => { scope: scope, nodes: nodes, arrayOnlySpread, + allowArrayLike, addHelper: name => this.addHelper(name), }); @@ -624,6 +632,7 @@ export default declare((api, options) => { scope: scope, kind: node.kind, arrayOnlySpread, + allowArrayLike, addHelper: name => this.addHelper(name), }); diff --git a/packages/babel-plugin-transform-destructuring/test/fixtures/allowArrayLike/holes/exec.js b/packages/babel-plugin-transform-destructuring/test/fixtures/allowArrayLike/holes/exec.js new file mode 100644 index 000000000000..108b033a0cc7 --- /dev/null +++ b/packages/babel-plugin-transform-destructuring/test/fixtures/allowArrayLike/holes/exec.js @@ -0,0 +1,6 @@ +var o = { 0: "a", 2: "c", length: 3 }; + +var [...rest] = o; + +expect(rest).toEqual(["a", undefined, "c"]); +expect(1 in rest).toBe(true); // Not holey diff --git a/packages/babel-plugin-transform-destructuring/test/fixtures/allowArrayLike/holes/options.json b/packages/babel-plugin-transform-destructuring/test/fixtures/allowArrayLike/holes/options.json new file mode 100644 index 000000000000..928c878a89eb --- /dev/null +++ b/packages/babel-plugin-transform-destructuring/test/fixtures/allowArrayLike/holes/options.json @@ -0,0 +1,6 @@ +{ + "plugins": [ + ["external-helpers", { "helperVersion": "7.100.0" }], + ["transform-destructuring", { "allowArrayLike": true }] + ] +} diff --git a/packages/babel-plugin-transform-destructuring/test/fixtures/allowArrayLike/length-cropped/exec.js b/packages/babel-plugin-transform-destructuring/test/fixtures/allowArrayLike/length-cropped/exec.js new file mode 100644 index 000000000000..ec1b3fc7dfc3 --- /dev/null +++ b/packages/babel-plugin-transform-destructuring/test/fixtures/allowArrayLike/length-cropped/exec.js @@ -0,0 +1,6 @@ +var o = { 0: "a", 1: "b", 2: "c", length: 2 }; + +var [first, ...rest] = o; + +expect(first).toBe("a"); +expect(rest).toEqual(["b"]); diff --git a/packages/babel-plugin-transform-destructuring/test/fixtures/allowArrayLike/length-cropped/options.json b/packages/babel-plugin-transform-destructuring/test/fixtures/allowArrayLike/length-cropped/options.json new file mode 100644 index 000000000000..928c878a89eb --- /dev/null +++ b/packages/babel-plugin-transform-destructuring/test/fixtures/allowArrayLike/length-cropped/options.json @@ -0,0 +1,6 @@ +{ + "plugins": [ + ["external-helpers", { "helperVersion": "7.100.0" }], + ["transform-destructuring", { "allowArrayLike": true }] + ] +} diff --git a/packages/babel-plugin-transform-destructuring/test/fixtures/allowArrayLike/simple/exec.js b/packages/babel-plugin-transform-destructuring/test/fixtures/allowArrayLike/simple/exec.js new file mode 100644 index 000000000000..8e71b1e45cfa --- /dev/null +++ b/packages/babel-plugin-transform-destructuring/test/fixtures/allowArrayLike/simple/exec.js @@ -0,0 +1,6 @@ +var o = { 0: "a", 1: "b", 2: "c", length: 3 }; + +var [first, ...rest] = o; + +expect(first).toBe("a"); +expect(rest).toEqual(["b", "c"]); diff --git a/packages/babel-plugin-transform-destructuring/test/fixtures/allowArrayLike/simple/input.js b/packages/babel-plugin-transform-destructuring/test/fixtures/allowArrayLike/simple/input.js new file mode 100644 index 000000000000..7d37e9622d78 --- /dev/null +++ b/packages/babel-plugin-transform-destructuring/test/fixtures/allowArrayLike/simple/input.js @@ -0,0 +1 @@ +var [first, ...rest] = o; diff --git a/packages/babel-plugin-transform-destructuring/test/fixtures/allowArrayLike/simple/options.json b/packages/babel-plugin-transform-destructuring/test/fixtures/allowArrayLike/simple/options.json new file mode 100644 index 000000000000..928c878a89eb --- /dev/null +++ b/packages/babel-plugin-transform-destructuring/test/fixtures/allowArrayLike/simple/options.json @@ -0,0 +1,6 @@ +{ + "plugins": [ + ["external-helpers", { "helperVersion": "7.100.0" }], + ["transform-destructuring", { "allowArrayLike": true }] + ] +} diff --git a/packages/babel-plugin-transform-destructuring/test/fixtures/allowArrayLike/simple/output.js b/packages/babel-plugin-transform-destructuring/test/fixtures/allowArrayLike/simple/output.js new file mode 100644 index 000000000000..50615d4e3e13 --- /dev/null +++ b/packages/babel-plugin-transform-destructuring/test/fixtures/allowArrayLike/simple/output.js @@ -0,0 +1,4 @@ +var _o = o, + _o2 = babelHelpers.maybeArrayLike(babelHelpers.toArray, _o), + first = _o2[0], + rest = _o2.slice(1); diff --git a/packages/babel-plugin-transform-spread/src/index.js b/packages/babel-plugin-transform-spread/src/index.js index 16af42b08fd6..f320d50884d3 100644 --- a/packages/babel-plugin-transform-spread/src/index.js +++ b/packages/babel-plugin-transform-spread/src/index.js @@ -4,13 +4,13 @@ import { types as t } from "@babel/core"; export default declare((api, options) => { api.assertVersion(7); - const { loose } = options; + const { loose, allowArrayLike } = options; function getSpreadLiteral(spread, scope) { if (loose && !t.isIdentifier(spread.argument, { name: "arguments" })) { return spread.argument; } else { - return scope.toArray(spread.argument, true); + return scope.toArray(spread.argument, true, allowArrayLike); } } diff --git a/packages/babel-plugin-transform-spread/test/fixtures/allowArrayLike/holes/exec.js b/packages/babel-plugin-transform-spread/test/fixtures/allowArrayLike/holes/exec.js new file mode 100644 index 000000000000..c5f809fae86f --- /dev/null +++ b/packages/babel-plugin-transform-spread/test/fixtures/allowArrayLike/holes/exec.js @@ -0,0 +1,6 @@ +var p2 = { 0: "a", 2: "c", length: 3 }; + +var arr = [...p2, "d"]; + +expect(arr).toEqual(["a", undefined, "c", "d"]); +expect(1 in arr).toBe(true); // Not holey diff --git a/packages/babel-plugin-transform-spread/test/fixtures/allowArrayLike/holes/options.json b/packages/babel-plugin-transform-spread/test/fixtures/allowArrayLike/holes/options.json new file mode 100644 index 000000000000..142b51685a9e --- /dev/null +++ b/packages/babel-plugin-transform-spread/test/fixtures/allowArrayLike/holes/options.json @@ -0,0 +1,6 @@ +{ + "plugins": [ + ["external-helpers", { "helperVersion": "7.100.0" }], + ["transform-spread", { "allowArrayLike": true }] + ] +} diff --git a/packages/babel-plugin-transform-spread/test/fixtures/allowArrayLike/length-cropped/exec.js b/packages/babel-plugin-transform-spread/test/fixtures/allowArrayLike/length-cropped/exec.js new file mode 100644 index 000000000000..dbb1602da453 --- /dev/null +++ b/packages/babel-plugin-transform-spread/test/fixtures/allowArrayLike/length-cropped/exec.js @@ -0,0 +1,5 @@ +var p2 = { 0: "b", 1: "c", 2: "d", length: 2 }; + +var arr = ["a", ...p2, "e"]; + +expect(arr).toEqual(["a", "b", "c", "e"]); diff --git a/packages/babel-plugin-transform-spread/test/fixtures/allowArrayLike/length-cropped/options.json b/packages/babel-plugin-transform-spread/test/fixtures/allowArrayLike/length-cropped/options.json new file mode 100644 index 000000000000..142b51685a9e --- /dev/null +++ b/packages/babel-plugin-transform-spread/test/fixtures/allowArrayLike/length-cropped/options.json @@ -0,0 +1,6 @@ +{ + "plugins": [ + ["external-helpers", { "helperVersion": "7.100.0" }], + ["transform-spread", { "allowArrayLike": true }] + ] +} diff --git a/packages/babel-plugin-transform-spread/test/fixtures/allowArrayLike/simple/exec.js b/packages/babel-plugin-transform-spread/test/fixtures/allowArrayLike/simple/exec.js new file mode 100644 index 000000000000..786324084b00 --- /dev/null +++ b/packages/babel-plugin-transform-spread/test/fixtures/allowArrayLike/simple/exec.js @@ -0,0 +1,5 @@ +var p2 = { 0: "b", 1: "c", 2: "d", length: 3 }; + +var arr = ["a", ...p2, "e"]; + +expect(arr).toEqual(["a", "b", "c", "d", "e"]); diff --git a/packages/babel-plugin-transform-spread/test/fixtures/allowArrayLike/simple/input.js b/packages/babel-plugin-transform-spread/test/fixtures/allowArrayLike/simple/input.js new file mode 100644 index 000000000000..266012c540ec --- /dev/null +++ b/packages/babel-plugin-transform-spread/test/fixtures/allowArrayLike/simple/input.js @@ -0,0 +1 @@ +var arr = ["a", ...p2, "e"]; diff --git a/packages/babel-plugin-transform-spread/test/fixtures/allowArrayLike/simple/options.json b/packages/babel-plugin-transform-spread/test/fixtures/allowArrayLike/simple/options.json new file mode 100644 index 000000000000..142b51685a9e --- /dev/null +++ b/packages/babel-plugin-transform-spread/test/fixtures/allowArrayLike/simple/options.json @@ -0,0 +1,6 @@ +{ + "plugins": [ + ["external-helpers", { "helperVersion": "7.100.0" }], + ["transform-spread", { "allowArrayLike": true }] + ] +} diff --git a/packages/babel-plugin-transform-spread/test/fixtures/allowArrayLike/simple/output.js b/packages/babel-plugin-transform-spread/test/fixtures/allowArrayLike/simple/output.js new file mode 100644 index 000000000000..caa46f72da3c --- /dev/null +++ b/packages/babel-plugin-transform-spread/test/fixtures/allowArrayLike/simple/output.js @@ -0,0 +1 @@ +var arr = ["a"].concat(babelHelpers.maybeArrayLike(babelHelpers.toConsumableArray, p2), ["e"]); diff --git a/packages/babel-traverse/src/scope/index.js b/packages/babel-traverse/src/scope/index.js index df8f81f3560d..957d7730891b 100644 --- a/packages/babel-traverse/src/scope/index.js +++ b/packages/babel-traverse/src/scope/index.js @@ -493,7 +493,8 @@ export default class Scope { console.log(sep); } - toArray(node: Object, i?: number) { + // TODO: (Babel 8) Split i in two parameters, and use an object of flags + toArray(node: Object, i?: number | boolean, allowArrayLike?: boolean) { if (t.isIdentifier(node)) { const binding = this.getBinding(node.name); if (binding && binding.constant && binding.path.isGenericType("Array")) { @@ -536,6 +537,12 @@ export default class Scope { // Used in array-rest to create an array helperName = "toArray"; } + + if (allowArrayLike) { + args.unshift(this.hub.addHelper(helperName)); + helperName = "maybeArrayLike"; + } + return t.callExpression(this.hub.addHelper(helperName), args); }