diff --git a/lib/6to5/transformation/file.js b/lib/6to5/transformation/file.js index 5b33ee1933..6d6d19fecc 100644 --- a/lib/6to5/transformation/file.js +++ b/lib/6to5/transformation/file.js @@ -226,24 +226,6 @@ File.prototype.debug = function (msg) { util.debug(parts); }; -File.prototype.toArray = function (node, i, forceHelperName) { - if (t.isArrayExpression(node)) { - return node; - } else if (t.isIdentifier(node) && node.name === "arguments") { - return t.callExpression(t.memberExpression(this.addHelper("slice"), t.identifier("call")), [node]); - } else { - var helperName = "to-array"; - var args = [node]; - if (i === true) { - helperName = "to-consumable-array"; - } else if (i) { - args.push(t.literal(i)); - helperName = "sliced-to-array"; - } - return t.callExpression(this.addHelper(helperName), args); - } -}; - File.prototype.getModuleFormatter = function (type) { var ModuleFormatter = isFunction(type) ? type : transform.moduleFormatters[type]; diff --git a/lib/6to5/transformation/transformers/es6/destructuring.js b/lib/6to5/transformation/transformers/es6/destructuring.js index 8f0ffe2428..c7fd3830d0 100644 --- a/lib/6to5/transformation/transformers/es6/destructuring.js +++ b/lib/6to5/transformation/transformers/es6/destructuring.js @@ -144,12 +144,14 @@ DestructuringTransformer.prototype.pushArrayPattern = function (pattern, parentI // if we have a rest then we need all the elements var count = !hasRest(pattern) && pattern.elements.length; - var toArray = this.file.toArray(parentId, count); + var toArray = this.scope.toArray(parentId, count); - var _parentId = this.scope.generateUidBasedOnNode(parentId, this.file); + var _parentId = this.scope.generateUidBasedOnNode(parentId); this.nodes.push(this.buildVariableDeclaration(_parentId, toArray)); parentId = _parentId; + this.scope.assignTypeGeneric(parentId.name, "Array"); + for (var i = 0; i < pattern.elements.length; i++) { var elem = pattern.elements[i]; @@ -159,7 +161,7 @@ DestructuringTransformer.prototype.pushArrayPattern = function (pattern, parentI var newPatternId; if (t.isRestElement(elem)) { - newPatternId = this.file.toArray(parentId); + newPatternId = this.scope.toArray(parentId); if (i > 0) { newPatternId = t.callExpression(t.memberExpression(newPatternId, t.identifier("slice")), [t.literal(i)]); diff --git a/lib/6to5/transformation/transformers/es6/parameters.rest.js b/lib/6to5/transformation/transformers/es6/parameters.rest.js index 60a8aca8b2..2538def92e 100644 --- a/lib/6to5/transformation/transformers/es6/parameters.rest.js +++ b/lib/6to5/transformation/transformers/es6/parameters.rest.js @@ -60,6 +60,8 @@ exports.Function = function (node, parent, scope) { node.body.body.unshift(restDeclar); } + scope.assignTypeGeneric(rest.name, "Array"); + var loop = util.template("rest", { ARGUMENTS: argsId, ARRAY_KEY: arrKey, diff --git a/lib/6to5/transformation/transformers/es6/spread.js b/lib/6to5/transformation/transformers/es6/spread.js index a6181dd4f1..1aa67aa3b3 100644 --- a/lib/6to5/transformation/transformers/es6/spread.js +++ b/lib/6to5/transformation/transformers/es6/spread.js @@ -5,8 +5,8 @@ var t = require("../../../types"); exports.check = t.isSpreadElement; -var getSpreadLiteral = function (spread, file) { - return file.toArray(spread.argument, true); +var getSpreadLiteral = function (spread, scope) { + return scope.toArray(spread.argument, true); }; var hasSpread = function (nodes) { @@ -18,7 +18,7 @@ var hasSpread = function (nodes) { return false; }; -var build = function (props, file) { +var build = function (props, scope) { var nodes = []; var _props = []; @@ -33,7 +33,7 @@ var build = function (props, file) { var prop = props[i]; if (t.isSpreadElement(prop)) { push(); - nodes.push(getSpreadLiteral(prop, file)); + nodes.push(getSpreadLiteral(prop, scope)); } else { _props.push(prop); } @@ -44,11 +44,11 @@ var build = function (props, file) { return nodes; }; -exports.ArrayExpression = function (node, parent, scope, file) { +exports.ArrayExpression = function (node, parent, scope) { var elements = node.elements; if (!hasSpread(elements)) return; - var nodes = build(elements, file); + var nodes = build(elements, scope); var first = nodes.shift(); if (!t.isArrayExpression(first)) { @@ -59,7 +59,7 @@ exports.ArrayExpression = function (node, parent, scope, file) { return t.callExpression(t.memberExpression(first, t.identifier("concat")), nodes); }; -exports.CallExpression = function (node, parent, scope, file) { +exports.CallExpression = function (node, parent, scope) { var args = node.arguments; if (!hasSpread(args)) return; @@ -71,7 +71,7 @@ exports.CallExpression = function (node, parent, scope, file) { if (args.length === 1 && args[0].argument.name === "arguments") { nodes = [args[0].argument]; } else { - nodes = build(args, file); + nodes = build(args, scope); } var first = nodes.shift(); @@ -105,7 +105,7 @@ exports.NewExpression = function (node, parent, scope, file) { var nativeType = t.isIdentifier(node.callee) && includes(t.NATIVE_TYPE_NAMES, node.callee.name); - var nodes = build(args, file); + var nodes = build(args, scope); if (nativeType) { nodes.unshift(t.arrayExpression([t.literal(null)])); diff --git a/lib/6to5/transformation/transformers/index.js b/lib/6to5/transformation/transformers/index.js index 36829cda49..118e50352d 100644 --- a/lib/6to5/transformation/transformers/index.js +++ b/lib/6to5/transformation/transformers/index.js @@ -31,7 +31,6 @@ module.exports = { "es6.objectSuper": require("./es6/object-super"), "es7.objectRestSpread": require("./es7/object-rest-spread"), "es7.exponentiationOperator": require("./es7/exponentiation-operator"), - "es6.spread": require("./es6/spread"), "es6.templateLiterals": require("./es6/template-literals"), "es5.properties.mutators": require("./es5/properties.mutators"), @@ -50,6 +49,9 @@ module.exports = { // needs to be before `es6.parameters.default` as default parameters will destroy the rest param "es6.parameters.rest": require("./es6/parameters.rest"), + // needs to be after `es6.parameters.rest` as we use `toArray` and avoid turning an already known array into one + "es6.spread": require("./es6/spread"), + // needs to be before `es6.blockScoping` as default parameters have a TDZ "es6.parameters.default": require("./es6/parameters.default"), diff --git a/lib/6to5/traversal/scope.js b/lib/6to5/traversal/scope.js index 55f4ebca48..40927123ac 100644 --- a/lib/6to5/traversal/scope.js +++ b/lib/6to5/traversal/scope.js @@ -198,6 +198,25 @@ Scope.prototype.inferType = function (node) { } }; +Scope.prototype.isTypeGeneric = function (name, genericName) { + var info = this.getBindingInfo(name); + if (!info) return false; + + var type = info.typeAnnotation; + return t.isGenericTypeAnnotation(type) && t.isIdentifier(type.id, { name: genericName }); +}; + +Scope.prototype.assignTypeGeneric = function (name, type) { + this.assignType(name, t.genericTypeAnnotation(t.identifier(type))); +}; + +Scope.prototype.assignType = function (name, type) { + var info = this.getBindingInfo(name); + if (!info) return; + + info.identifier.typeAnnotation = info.typeAnnotation = type; +}; + Scope.prototype.getTypeAnnotation = function (key, id, node) { var type; @@ -215,6 +234,32 @@ Scope.prototype.getTypeAnnotation = function (key, id, node) { } }; +Scope.prototype.toArray = function (node, i) { + var file = this.file; + + if (t.isIdentifier(node) && this.isTypeGeneric(node.name, "Array")) { + return node; + } + + if (t.isArrayExpression(node)) { + return node; + } + + if (t.isIdentifier(node, { name: "arguments" })) { + return t.callExpression(t.memberExpression(file.addHelper("slice"), t.identifier("call")), [node]); + } + + var helperName = "to-array"; + var args = [node]; + if (i === true) { + helperName = "to-consumable-array"; + } else if (i) { + args.push(t.literal(i)); + helperName = "sliced-to-array"; + } + return t.callExpression(file.addHelper(helperName), args); +}; + Scope.prototype.clearOwnBinding = function (name) { delete this.bindings[name]; }; diff --git a/lib/6to5/types/builder-keys.json b/lib/6to5/types/builder-keys.json index 556ac40d2a..147f6ea28d 100644 --- a/lib/6to5/types/builder-keys.json +++ b/lib/6to5/types/builder-keys.json @@ -59,6 +59,11 @@ "generator": false }, + "GenericTypeAnnotation": { + "id": null, + "typeParameters": null + }, + "Identifier": { "name": null }, diff --git a/test/fixtures/transformation/es6-destructuring/known-array/actual.js b/test/fixtures/transformation/es6-destructuring/known-array/actual.js new file mode 100644 index 0000000000..8975797007 --- /dev/null +++ b/test/fixtures/transformation/es6-destructuring/known-array/actual.js @@ -0,0 +1 @@ +let [x, ...y] = z; diff --git a/test/fixtures/transformation/es6-destructuring/known-array/expected.js b/test/fixtures/transformation/es6-destructuring/known-array/expected.js new file mode 100644 index 0000000000..13b2f5def6 --- /dev/null +++ b/test/fixtures/transformation/es6-destructuring/known-array/expected.js @@ -0,0 +1,8 @@ +"use strict"; + +var _toArray = function (arr) { return Array.isArray(arr) ? arr : Array.from(arr); }; + +var _z = _toArray(z); + +var x = _z[0]; +var y = _z.slice(1); diff --git a/test/fixtures/transformation/es6-destructuring/spread/expected.js b/test/fixtures/transformation/es6-destructuring/spread/expected.js index d7543faac6..41a784e5f2 100644 --- a/test/fixtures/transformation/es6-destructuring/spread/expected.js +++ b/test/fixtures/transformation/es6-destructuring/spread/expected.js @@ -7,9 +7,9 @@ var isSorted = function (_ref) { var x = _ref2[0]; var y = _ref2[1]; - var wow = _toArray(_ref2).slice(2); + var wow = _ref2.slice(2); if (!zs.length) return true; if (y > x) return isSorted(zs); return false; -}; \ No newline at end of file +}; diff --git a/test/fixtures/transformation/es6-parameters.rest/pattern/expected.js b/test/fixtures/transformation/es6-parameters.rest/pattern/expected.js index 093a5bb361..56ad12447e 100644 --- a/test/fixtures/transformation/es6-parameters.rest/pattern/expected.js +++ b/test/fixtures/transformation/es6-parameters.rest/pattern/expected.js @@ -1,14 +1,11 @@ "use strict"; -var _slicedToArray = function (arr, i) { if (Array.isArray(arr)) { return arr; } else { var _arr = []; for (var _iterator = arr[Symbol.iterator](), _step; !(_step = _iterator.next()).done;) { _arr.push(_step.value); if (i && _arr.length === i) break; } return _arr; } }; - var foo = function () { for (var _len = arguments.length, _ref = Array(_len), _key = 0; _key < _len; _key++) { _ref[_key] = arguments[_key]; } - var _ref2 = _slicedToArray(_ref, 2); - + var _ref2 = _ref; var a = _ref2[0]; var b = _ref2[1]; }; diff --git a/test/fixtures/transformation/es6-spread/known-array/actual.js b/test/fixtures/transformation/es6-spread/known-array/actual.js new file mode 100644 index 0000000000..af3c9258a8 --- /dev/null +++ b/test/fixtures/transformation/es6-spread/known-array/actual.js @@ -0,0 +1,2 @@ +var arr: Array = bar; +[...arr]; diff --git a/test/fixtures/transformation/es6-spread/known-array/expected.js b/test/fixtures/transformation/es6-spread/known-array/expected.js new file mode 100644 index 0000000000..cba7214bfd --- /dev/null +++ b/test/fixtures/transformation/es6-spread/known-array/expected.js @@ -0,0 +1,4 @@ +"use strict"; + +var arr = bar; +[].concat(arr); diff --git a/test/fixtures/transformation/es6-spread/known-rest/actual.js b/test/fixtures/transformation/es6-spread/known-rest/actual.js new file mode 100644 index 0000000000..a555213e34 --- /dev/null +++ b/test/fixtures/transformation/es6-spread/known-rest/actual.js @@ -0,0 +1,3 @@ +function foo(...bar) { + return [...bar]; +} diff --git a/test/fixtures/transformation/es6-spread/known-rest/expected.js b/test/fixtures/transformation/es6-spread/known-rest/expected.js new file mode 100644 index 0000000000..9c8a2feacf --- /dev/null +++ b/test/fixtures/transformation/es6-spread/known-rest/expected.js @@ -0,0 +1,9 @@ +"use strict"; + +function foo() { + for (var _len = arguments.length, bar = Array(_len), _key = 0; _key < _len; _key++) { + bar[_key] = arguments[_key]; + } + + return [].concat(bar); +}