diff --git a/lib/6to5/transformation/helpers/remap-async-to-generator.js b/lib/6to5/transformation/helpers/remap-async-to-generator.js index 7426862f71..447ac18b76 100644 --- a/lib/6to5/transformation/helpers/remap-async-to-generator.js +++ b/lib/6to5/transformation/helpers/remap-async-to-generator.js @@ -3,19 +3,21 @@ var traverse = require("../../traverse"); var t = require("../../types"); +var traverser = { + enter: function (node, parent, scope, context) { + if (t.isFunction(node)) context.skip(); + + if (t.isAwaitExpression(node)) { + node.type = "YieldExpression"; + } + } +}; + module.exports = function (node, callId) { node.async = false; node.generator = true; - traverse(node, { - enter: function (node, parent, scope, context) { - if (t.isFunction(node)) context.skip(); - - if (t.isAwaitExpression(node)) { - node.type = "YieldExpression"; - } - } - }); + traverse(node, traverser); var call = t.callExpression(callId, [node]); diff --git a/lib/6to5/transformation/helpers/replace-supers.js b/lib/6to5/transformation/helpers/replace-supers.js index 2403e878cb..738cb6346a 100644 --- a/lib/6to5/transformation/helpers/replace-supers.js +++ b/lib/6to5/transformation/helpers/replace-supers.js @@ -103,6 +103,34 @@ ReplaceSupers.prototype.replace = function () { this.traverseLevel(this.methodNode.value, true); }; +var traverser = { + enter: function (node, parent, scope, context, state) { + var topLevel = state.topLevel; + var self = state.self; + + if (t.isFunction(node) && !t.isArrowFunctionExpression(node)) { + // we need to call traverseLevel again so we're context aware + self.traverseLevel(node, false); + return context.skip(); + } + + if (t.isProperty(node, { method: true }) || t.isMethodDefinition(node)) { + // break on object methods + return context.skip(); + } + + var getThisReference = topLevel ? + // top level so `this` is the instance + t.thisExpression : + // not in the top level so we need to create a reference + self.getThisReference; + + var callback = self.specHandle; + if (self.isLoose) callback = self.looseHandle; + return callback.call(self, getThisReference, node, parent); + } +}; + /** * Description * @@ -111,36 +139,8 @@ ReplaceSupers.prototype.replace = function () { */ ReplaceSupers.prototype.traverseLevel = function (node, topLevel) { - var self = this; - - traverse(node, { - enter: function (node, parent, scope, context) { - if (t.isFunction(node) && !t.isArrowFunctionExpression(node)) { - // we need to call traverseLevel again so we're context aware - self.traverseLevel(node, false); - return context.skip(); - } - - if (t.isProperty(node, { method: true }) || t.isMethodDefinition(node)) { - // break on object methods - return context.skip(); - } - - var getThisReference = function () { - if (topLevel) { - // top level so `this` is the instance - return t.thisExpression(); - } else { - // not in the top level so we need to create a reference - return self.getThisReference(); - } - }; - - var callback = self.specHandle; - if (self.isLoose) callback = self.looseHandle; - return callback.call(self, getThisReference, node, parent); - } - }); + var state = { self: this, topLevel: topLevel }; + traverse(node, traverser, null, state); }; /** diff --git a/lib/6to5/transformation/modules/_default.js b/lib/6to5/transformation/modules/_default.js index 989d7b3f94..52ecda7cf8 100644 --- a/lib/6to5/transformation/modules/_default.js +++ b/lib/6to5/transformation/modules/_default.js @@ -47,8 +47,21 @@ DefaultFormatter.prototype.getLocalImports = function () { return localImports; }; +DefaultFormatter.prototype.isLocalReference = function (node) { + var localImports = this.localImports; + return t.isIdentifier(node) && _.has(localImports, node.name) && localImports[node.name] !== node; +}; + +DefaultFormatter.prototype.checkLocalReference = function (node) { + var file = this.file; + if (this.isLocalReference(node)) { + throw file.errorWithNode(node, "Illegal assignment of module import"); + } +}; + var collissionsTraverser = { - enter: function (node, parent, scope, context, check) { + enter: function (node, parent, scope, context, state) { + var self = state.self; if (t.isAssignmentExpression(node)) { var left = node.left; @@ -56,30 +69,17 @@ var collissionsTraverser = { while (left.object) left = left.object; } - check(left); + self.checkLocalReference(left); } else if (t.isDeclaration(node)) { - _.each(t.getIds(node, true), check); + _.each(t.getIds(node, true), self.checkLocalReference); } } }; DefaultFormatter.prototype.checkCollisions = function () { // todo: all check export collissions - - var localImports = this.localImports; - var file = this.file; - - var isLocalReference = function (node) { - return t.isIdentifier(node) && _.has(localImports, node.name) && localImports[node.name] !== node; - }; - - var check = function (node) { - if (isLocalReference(node)) { - throw file.errorWithNode(node, "Illegal assignment of module import"); - } - }; - - traverse(file.ast, collissionsTraverser, null, check); + var state = { self: this }; + traverse(this.file.ast, collissionsTraverser, null, state); }; DefaultFormatter.prototype.remapExportAssignment = function (node) { @@ -94,51 +94,53 @@ DefaultFormatter.prototype.remapExportAssignment = function (node) { ); }; -DefaultFormatter.prototype.remapAssignments = function () { +DefaultFormatter.prototype.isLocalReference = function (node, scope) { var localExports = this.localExports; - var self = this; + var name = node.name; + return t.isIdentifier(node) && localExports[name] && localExports[name] === scope.get(name, true); +}; - var isLocalReference = function (node, scope) { - var name = node.name; - return t.isIdentifier(node) && localExports[name] && localExports[name] === scope.get(name, true); - }; +var remapTraverser = { + enter: function (node, parent, scope, context, state) { + var self = state.self; + if (t.isUpdateExpression(node) && self.isLocalReference(node.argument, scope)) { + context.skip(); - traverse(this.file.ast, { - enter: function (node, parent, scope, context, isLocalReference) { - if (t.isUpdateExpression(node) && isLocalReference(node.argument, scope)) { - context.skip(); + // expand to long file assignment expression + var assign = t.assignmentExpression(node.operator[0] + "=", node.argument, t.literal(1)); - // expand to long file assignment expression - var assign = t.assignmentExpression(node.operator[0] + "=", node.argument, t.literal(1)); + // remap this assignment expression + var remapped = self.remapExportAssignment(assign); - // remap this assignment expression - var remapped = self.remapExportAssignment(assign); - - // we don't need to change the result - if (t.isExpressionStatement(parent) || node.prefix) { - return remapped; - } - - var nodes = []; - nodes.push(remapped); - - var operator; - if (node.operator === "--") { - operator = "+"; - } else { // "++" - operator = "-"; - } - nodes.push(t.binaryExpression(operator, node.argument, t.literal(1))); - - return t.sequenceExpression(nodes); + // we don't need to change the result + if (t.isExpressionStatement(parent) || node.prefix) { + return remapped; } - if (t.isAssignmentExpression(node) && isLocalReference(node.left, scope)) { - context.skip(); - return self.remapExportAssignment(node); + var nodes = []; + nodes.push(remapped); + + var operator; + if (node.operator === "--") { + operator = "+"; + } else { // "++" + operator = "-"; } + nodes.push(t.binaryExpression(operator, node.argument, t.literal(1))); + + return t.sequenceExpression(nodes); } - }, null, isLocalReference); + + if (t.isAssignmentExpression(node) && self.isLocalReference(node.left, scope)) { + context.skip(); + return self.remapExportAssignment(node); + } + } +}; + +DefaultFormatter.prototype.remapAssignments = function () { + var state = { self: this }; + traverse(this.file.ast, remapTraverser, null, state); }; DefaultFormatter.prototype.getModuleName = function () { diff --git a/lib/6to5/transformation/modules/common.js b/lib/6to5/transformation/modules/common.js index 73653c8d04..00c4c84567 100644 --- a/lib/6to5/transformation/modules/common.js +++ b/lib/6to5/transformation/modules/common.js @@ -8,16 +8,19 @@ var util = require("../../util"); var t = require("../../types"); var _ = require("lodash"); +var traverser = { + enter: function (node, parent, scope, context, state) { + if (t.isExportDeclaration(node) && !node.default) state.hasNonDefaultExports = true; + } +}; + function CommonJSFormatter(file) { DefaultFormatter.apply(this, arguments); - var hasNonDefaultExports = false; - traverse(file.ast, { - enter: function (node) { - if (t.isExportDeclaration(node) && !node.default) hasNonDefaultExports = true; - } - }); - this.hasNonDefaultExports = hasNonDefaultExports; + var state = { hasNonDefaultExports: false }; + traverse(file.ast, traverser, null, state); + + this.hasNonDefaultExports = state.hasNonDefaultExports; } util.inherits(CommonJSFormatter, DefaultFormatter); diff --git a/lib/6to5/transformation/modules/system.js b/lib/6to5/transformation/modules/system.js index 85f64662a9..2f373a7d80 100644 --- a/lib/6to5/transformation/modules/system.js +++ b/lib/6to5/transformation/modules/system.js @@ -63,36 +63,91 @@ SystemFormatter.prototype.importSpecifier = function (specifier, node, nodes) { this._addImportSource(_.last(nodes), node); }; +var runnerSettersTraverser = { + enter: function (node, parent, scope, context, state) { + if (node._importSource === state.source) { + if (t.isVariableDeclaration(node)) { + _.each(node.declarations, function (declar) { + state.hoistDeclarators.push(t.variableDeclarator(declar.id)); + state.nodes.push(t.expressionStatement( + t.assignmentExpression("=", declar.id, declar.init) + )); + }); + } else { + state.nodes.push(node); + } + + context.remove(); + } + } +}; + SystemFormatter.prototype.buildRunnerSetters = function (block, hoistDeclarators) { return t.arrayExpression(_.map(this.ids, function (uid, source) { var state = { - nodes: [], + source: source, + nodes: [], hoistDeclarators: hoistDeclarators }; - traverse(block, { - enter: function (node, parent, scope, context, state) { - if (node._importSource === source) { - if (t.isVariableDeclaration(node)) { - _.each(node.declarations, function (declar) { - state.hoistDeclarators.push(t.variableDeclarator(declar.id)); - state.nodes.push(t.expressionStatement( - t.assignmentExpression("=", declar.id, declar.init) - )); - }); - } else { - state.nodes.push(node); - } - - context.remove(); - } - } - }, null, state); + traverse(block, runnerSettersTraverser, null, state); return t.functionExpression(null, [uid], t.blockStatement(state.nodes)); })); }; +var hoistVariablesTraverser = { + enter: function (node, parent, scope, context, hoistDeclarators) { + if (t.isFunction(node)) { + // nothing inside is accessible + return context.skip(); + } + + if (t.isVariableDeclaration(node)) { + if (node.kind !== "var" && !t.isProgram(parent)) { // let, const + // can't be accessed + return; + } + + var nodes = []; + + _.each(node.declarations, function (declar) { + hoistDeclarators.push(t.variableDeclarator(declar.id)); + if (declar.init) { + // no initializer so we can just hoist it as-is + var assign = t.expressionStatement(t.assignmentExpression("=", declar.id, declar.init)); + nodes.push(assign); + } + }); + + // for (var i in test) + // for (var i = 0;;) + if (t.isFor(parent)) { + if (parent.left === node) { + return node.declarations[0].id; + } + + if (parent.init === node) { + return t.toSequenceExpression(nodes, scope); + } + } + + return nodes; + } + } +}; + +var hoistFunctionsTraverser = { + enter: function (node, parent, scope, context, handlerBody) { + if (t.isFunction(node)) context.skip(); + + if (t.isFunctionDeclaration(node) || node._blockHoist) { + handlerBody.push(node); + context.remove(); + } + } +}; + SystemFormatter.prototype.transform = function (ast) { var program = ast.program; @@ -116,46 +171,7 @@ SystemFormatter.prototype.transform = function (ast) { var returnStatement = handlerBody.pop(); // hoist up all variable declarations - traverse(block, { - enter: function (node, parent, scope, context, hoistDeclarators) { - if (t.isFunction(node)) { - // nothing inside is accessible - return context.skip(); - } - - if (t.isVariableDeclaration(node)) { - if (node.kind !== "var" && !t.isProgram(parent)) { // let, const - // can't be accessed - return; - } - - var nodes = []; - - _.each(node.declarations, function (declar) { - hoistDeclarators.push(t.variableDeclarator(declar.id)); - if (declar.init) { - // no initializer so we can just hoist it as-is - var assign = t.expressionStatement(t.assignmentExpression("=", declar.id, declar.init)); - nodes.push(assign); - } - }); - - // for (var i in test) - // for (var i = 0;;) - if (t.isFor(parent)) { - if (parent.left === node) { - return node.declarations[0].id; - } - - if (parent.init === node) { - return t.toSequenceExpression(nodes, scope); - } - } - - return nodes; - } - } - }, null, hoistDeclarators); + traverse(block, hoistVariablesTraverser, null, hoistDeclarators); if (hoistDeclarators.length) { var hoistDeclar = t.variableDeclaration("var", hoistDeclarators); @@ -164,16 +180,7 @@ SystemFormatter.prototype.transform = function (ast) { } // hoist up function declarations for circular references - traverse(block, { - enter: function (node, parent, scope, context, handlerBody) { - if (t.isFunction(node)) context.skip(); - - if (t.isFunctionDeclaration(node) || node._blockHoist) { - handlerBody.push(node); - context.remove(); - } - } - }, null, handlerBody); + traverse(block, hoistFunctionsTraverser, null, handlerBody); handlerBody.push(returnStatement); diff --git a/lib/6to5/transformation/transformer.js b/lib/6to5/transformation/transformer.js index d0a4e0dd0a..63c6288dfd 100644 --- a/lib/6to5/transformation/transformer.js +++ b/lib/6to5/transformation/transformer.js @@ -6,25 +6,6 @@ var traverse = require("../traverse"); var t = require("../types"); var _ = require("lodash"); -function noop() { } - -function enter(node, parent, scope, context, args) { - var fns = args[1][node.type]; - if (!fns) return; - return fns.enter(node, parent, scope, context, args[0]); -} - -function exit(node, parent, scope, context, args) { - var fns = args[1][node.type]; - if (!fns) return; - return fns.exit(node, parent, scope, context, args[0]); -} - -var traverseOpts = { - enter: enter, - exit: exit -}; - function Transformer(key, transformer, opts) { this.manipulateOptions = transformer.manipulateOptions; this.experimental = !!transformer.experimental; @@ -53,8 +34,8 @@ Transformer.prototype.normalise = function (transformer) { if (!_.isObject(fns)) return; - if (!fns.enter) fns.enter = noop; - if (!fns.exit) fns.exit = noop; + if (!fns.enter) fns.enter = _.noop; + if (!fns.exit) fns.exit = _.noop; transformer[type] = fns; @@ -77,9 +58,26 @@ Transformer.prototype.astRun = function (file, key) { } }; +var transformTraverser = { + enter: function (node, parent, scope, context, state) { + var fns = state.transformer[node.type]; + if (!fns) return; + return fns.enter(node, parent, scope, context, state.file); + }, + + exit: function (node, parent, scope, context, state) { + var fns = state.transformer[node.type]; + if (!fns) return; + return fns.exit(node, parent, scope, context, state.file); + } +}; + Transformer.prototype.transform = function (file) { this.astRun(file, "before"); - traverse(file.ast, traverseOpts, null, [file, this.transformer]); + + var state = { file: file, transformer: this.transformer }; + traverse(file.ast, transformTraverser, null, state); + this.astRun(file, "after"); }; diff --git a/lib/6to5/transformation/transformers/es6/block-scoping.js b/lib/6to5/transformation/transformers/es6/block-scoping.js index b342c9a347..ef0e2c0c7f 100644 --- a/lib/6to5/transformation/transformers/es6/block-scoping.js +++ b/lib/6to5/transformation/transformers/es6/block-scoping.js @@ -112,6 +112,31 @@ LetScoping.prototype.run = function () { } }; +function replace(node, parent, scope, context, remaps) { + if (!t.isReferencedIdentifier(node, parent)) return; + + var remap = remaps[node.name]; + if (!remap) return; + + var own = scope.get(node.name, true); + if (own === remap.node) { + node.name = remap.uid; + } else { + // scope already has it's own declaration that doesn't + // match the one we have a stored replacement for + if (context) context.skip(); + } +} + +var replaceTraverser = { + enter: replace +}; + +function traverseReplace(node, parent, scope, remaps) { + replace(node, parent, scope, null, remaps); + traverse(node, replaceTraverser, scope, remaps); +} + /** * Description */ @@ -149,35 +174,14 @@ LetScoping.prototype.remap = function () { // - var replace = function (node, parent, scope, context, remaps) { - if (!t.isReferencedIdentifier(node, parent)) return; - - var remap = remaps[node.name]; - if (!remap) return; - - var own = scope.get(node.name, true); - if (own === remap.node) { - node.name = remap.uid; - } else { - // scope already has it's own declaration that doesn't - // match the one we have a stored replacement for - if (context) context.skip(); - } - }; - - var traverseReplace = function (node, parent) { - replace(node, parent, scope, null, remaps); - traverse(node, { enter: replace }, scope, remaps); - }; - var loopParent = this.loopParent; if (loopParent) { - traverseReplace(loopParent.right, loopParent); - traverseReplace(loopParent.test, loopParent); - traverseReplace(loopParent.update, loopParent); + traverseReplace(loopParent.right, loopParent, scope, remaps); + traverseReplace(loopParent.test, loopParent, scope, remaps); + traverseReplace(loopParent.update, loopParent, scope, remaps); } - traverse(this.block, { enter: replace }, scope, remaps); + traverse(this.block, replaceTraverser, scope, remaps); }; /** @@ -217,6 +221,31 @@ LetScoping.prototype.needsClosure = function () { this.build(ret, call); }; +var letReferenceFunctionTraverser = { + enter: function (node, parent, scope, context, state) { + // not a direct reference + if (!t.isReferencedIdentifier(node, parent)) return; + + // this scope has a variable with the same name so it couldn't belong + // to our let scope + if (scope.hasOwn(node.name, true)) return; + + // not a part of our scope + if (!state.letReferences[node.name]) return; + + state.closurify = true; + } +}; + +var letReferenceBlockTraverser = { + enter: function (node, parent, scope, context, state) { + if (t.isFunction(node)) { + traverse(node, letReferenceFunctionTraverser, scope, state); + return context.skip(); + } + } +}; + /** * Description */ @@ -264,33 +293,42 @@ LetScoping.prototype.getLetReferences = function () { // traverse through this block, stopping on functions and checking if they // contain any local let references - traverse(this.block, { - enter: function (node, parent, scope, context, state) { - if (t.isFunction(node)) { - traverse(node, { - enter: function (node, parent) { - // not a direct reference - if (!t.isReferencedIdentifier(node, parent)) return; - - // this scope has a variable with the same name so it couldn't belong - // to our let scope - if (scope.hasOwn(node.name, true)) return; - - // not a part of our scope - if (!state.letReferences[node.name]) return; - - state.closurify = true; - } - }, scope, state); - - return context.skip(); - } - } - }, this.scope, state); + traverse(this.block, letReferenceBlockTraverser, this.scope, state); return state.closurify; }; +var loopTraverser = { + enter: function (node, parent, scope, context, state) { + var replace; + + if (t.isFunction(node) || t.isLoop(node)) { + return context.skip(); + } + + if (node && !node.label) { + if (t.isBreakStatement(node)) { + if (t.isSwitchCase(parent)) return; + + state.hasBreak = true; + replace = t.returnStatement(t.literal("break")); + } else if (t.isContinueStatement(node)) { + state.hasContinue = true; + replace = t.returnStatement(t.literal("continue")); + } + } + + if (t.isReturnStatement(node)) { + state.hasReturn = true; + replace = t.returnStatement(t.objectExpression([ + t.property("init", t.identifier("v"), node.argument || t.identifier("undefined")) + ])); + } + + if (replace) return t.inherits(replace, node); + } +}; + /** * If we're inside of a loop then traverse it and check if it has one of * the following node types `ReturnStatement`, `BreakStatement`, @@ -301,44 +339,32 @@ LetScoping.prototype.getLetReferences = function () { */ LetScoping.prototype.checkLoop = function () { - var has = { + var state = { hasContinue: false, hasReturn: false, hasBreak: false, }; - traverse(this.block, { - enter: function (node, parent, scope, context) { - var replace; + traverse(this.block, loopTraverser, this.scope, state); + return state; +}; - if (t.isFunction(node) || t.isLoop(node)) { - return context.skip(); +var hoistVarDeclarationsTraverser = { + enter: function (node, parent, scope, context, self) { + if (t.isForStatement(node)) { + if (isVar(node.init, node)) { + node.init = t.sequenceExpression(self.pushDeclar(node.init)); } - - if (node && !node.label) { - if (t.isBreakStatement(node)) { - if (t.isSwitchCase(parent)) return; - - has.hasBreak = true; - replace = t.returnStatement(t.literal("break")); - } else if (t.isContinueStatement(node)) { - has.hasContinue = true; - replace = t.returnStatement(t.literal("continue")); - } + } else if (t.isFor(node)) { + if (isVar(node.left, node)) { + node.left = node.left.declarations[0].id; } - - if (t.isReturnStatement(node)) { - has.hasReturn = true; - replace = t.returnStatement(t.objectExpression([ - t.property("init", t.identifier("v"), node.argument || t.identifier("undefined")) - ])); - } - - if (replace) return t.inherits(replace, node); + } else if (isVar(node, parent)) { + return self.pushDeclar(node).map(t.expressionStatement); + } else if (t.isFunction(node)) { + return context.skip(); } - }, this.scope, has); - - return has; + } }; /** @@ -347,23 +373,7 @@ LetScoping.prototype.checkLoop = function () { */ LetScoping.prototype.hoistVarDeclarations = function () { - traverse(this.block, { - enter: function (node, parent, scope, context, self) { - if (t.isForStatement(node)) { - if (isVar(node.init, node)) { - node.init = t.sequenceExpression(self.pushDeclar(node.init)); - } - } else if (t.isFor(node)) { - if (isVar(node.left, node)) { - node.left = node.left.declarations[0].id; - } - } else if (isVar(node, parent)) { - return self.pushDeclar(node).map(t.expressionStatement); - } else if (t.isFunction(node)) { - return context.skip(); - } - } - }, this.scope, this); + traverse(this.block, hoistVarDeclarationsTraverser, this.scope, this); }; /** diff --git a/lib/6to5/transformation/transformers/es6/constants.js b/lib/6to5/transformation/transformers/es6/constants.js index c70ceffeef..701339c3d4 100644 --- a/lib/6to5/transformation/transformers/es6/constants.js +++ b/lib/6to5/transformation/transformers/es6/constants.js @@ -4,6 +4,17 @@ var traverse = require("../../../traverse"); var t = require("../../../types"); var _ = require("lodash"); +var traverser = { + enter: function (child, parent, scope, context, state) { + if (child._ignoreConstant) return; + if (t.isVariableDeclaration(child)) return; + + if (t.isVariableDeclarator(child) || t.isDeclaration(child) || t.isAssignmentExpression(child)) { + state.check(parent, state.getIds(child), scope); + } + } +}; + exports.Program = exports.BlockStatement = exports.ForInStatement = @@ -71,19 +82,6 @@ exports.ForStatement = function (node, parent, scope, context, file) { if (!hasConstants) return; - var state = { - check: check, - getIds: getIds - }; - - traverse(node, { - enter: function (child, parent, scope, context, state) { - if (child._ignoreConstant) return; - if (t.isVariableDeclaration(child)) return; - - if (t.isVariableDeclarator(child) || t.isDeclaration(child) || t.isAssignmentExpression(child)) { - state.check(parent, state.getIds(child), scope); - } - } - }, null, state); + var state = { check: check, getIds: getIds }; + traverse(node, traverser, null, state); }; diff --git a/lib/6to5/transformation/transformers/es6/parameters.default.js b/lib/6to5/transformation/transformers/es6/parameters.default.js index e634696195..b17fc852f9 100644 --- a/lib/6to5/transformation/transformers/es6/parameters.default.js +++ b/lib/6to5/transformation/transformers/es6/parameters.default.js @@ -4,6 +4,22 @@ var traverse = require("../../../traverse"); var util = require("../../../util"); var t = require("../../../types"); +function checkTDZ(node, parent, scope, context, state) { + if (!t.isReferencedIdentifier(node, parent)) return; + + if (state.ids.indexOf(node.name) >= 0) { + throw state.file.errorWithNode(node, "Temporal dead zone - accessing a variable before it's initialized"); + } + + if (scope.has(node.name, true)) { + state.iife = true; + } +} + +var checkTDZTraverser = { + enter: checkTDZ +}; + exports.Function = function (node, parent, scope, context, file) { if (!node.defaults || !node.defaults.length) return; t.ensureBlock(node); @@ -12,27 +28,10 @@ exports.Function = function (node, parent, scope, context, file) { return t.getIds(param); }); - var iife = false; var def; - - var checkTDZ = function (param, def, ids) { - var check = function (node, parent) { - if (!t.isReferencedIdentifier(node, parent)) return; - - if (ids.indexOf(node.name) >= 0) { - throw file.errorWithNode(node, "Temporal dead zone - accessing a variable before it's initialized"); - } - - if (scope.has(node.name, true)) { - iife = true; - } - }; - - check(def, node); - - if (!t.isPattern(param)) { - traverse(def, { enter: check }); - } + var state = { + file: file, + iife: false }; for (var i = 0; i < node.defaults.length; i++) { @@ -44,14 +43,21 @@ exports.Function = function (node, parent, scope, context, file) { // temporal dead zone check - here we prevent accessing of params that // are to the right - ie. uninitialized parameters var rightIds = ids.slice(i); + for (var i2 = 0; i2 < rightIds.length; i2++) { - checkTDZ(param, def, rightIds[i2]); + state.ids = rightIds[i2]; + + checkTDZ(def, node, scope, context, state); + + if (!t.isPattern(param)) { + traverse(def, checkTDZTraverser, scope, state); + } } // we're accessing a variable that's already defined within this function var has = scope.get(param.name, true); if (has && node.params.indexOf(has) < 0) { - iife = true; + state.iife = true; } } @@ -82,7 +88,7 @@ exports.Function = function (node, parent, scope, context, file) { // we need to cut off all trailing default parameters node.params = node.params.slice(0, lastNonDefaultParam); - if (iife) { + if (state.iife) { var container = t.functionExpression(null, [], node.body, node.generator); container._aliasFunction = true; diff --git a/lib/6to5/transformation/transformers/optional/block-scoping-tdz.js b/lib/6to5/transformation/transformers/optional/block-scoping-tdz.js index 81f7dcf5c4..488cc52deb 100644 --- a/lib/6to5/transformation/transformers/optional/block-scoping-tdz.js +++ b/lib/6to5/transformation/transformers/optional/block-scoping-tdz.js @@ -1,6 +1,36 @@ var traverse = require("../../../traverse"); var t = require("../../../types"); +var traverser = { + enter: function (node, parent, scope, context, state) { + if (!t.isReferencedIdentifier(node, parent)) return; + + var declared = state.letRefs[node.name]; + if (!declared) return; + + // declared node is different in this scope + if (scope.get(node.name, true) !== declared) return; + + var declaredLoc = declared.loc; + var referenceLoc = node.loc; + + if (!declaredLoc || !referenceLoc) return; + + // does this reference appear on a line before the declaration? + var before = referenceLoc.start.line < declaredLoc.start.line; + + if (referenceLoc.start.line === declaredLoc.start.line) { + // this reference appears on the same line + // check it appears before the declaration + before = referenceLoc.start.col < declaredLoc.start.col; + } + + if (before) { + throw state.file.errorWithNode(node, "Temporal dead zone - accessing a variable before it's initialized"); + } + } +}; + exports.optional = true; exports.Loop = @@ -11,36 +41,8 @@ exports.BlockStatement = function (node, parent, scope, context, file) { var state = { letRefs: letRefs, - file: file + file: file }; - traverse(node, { - enter: function (node, parent, scope, context, state) { - if (!t.isReferencedIdentifier(node, parent)) return; - - var declared = state.letRefs[node.name]; - if (!declared) return; - - // declared node is different in this scope - if (scope.get(node.name, true) !== declared) return; - - var declaredLoc = declared.loc; - var referenceLoc = node.loc; - - if (!declaredLoc || !referenceLoc) return; - - // does this reference appear on a line before the declaration? - var before = referenceLoc.start.line < declaredLoc.start.line; - - if (referenceLoc.start.line === declaredLoc.start.line) { - // this reference appears on the same line - // check it appears before the declaration - before = referenceLoc.start.col < declaredLoc.start.col; - } - - if (before) { - throw state.file.errorWithNode(node, "Temporal dead zone - accessing a variable before it's initialized"); - } - } - }, scope, state); + traverse(node, traverser, scope, state); }; diff --git a/lib/6to5/transformation/transformers/optional/self-contained.js b/lib/6to5/transformation/transformers/optional/self-contained.js index 5ce54bf13d..5baffd16cb 100644 --- a/lib/6to5/transformation/transformers/optional/self-contained.js +++ b/lib/6to5/transformation/transformers/optional/self-contained.js @@ -19,6 +19,45 @@ var ALIASABLE_CONSTRUCTORS = [ "WeakSet" ]; +var astTraverser = { + enter: function (node, parent, scope, context, file) { + var prop; + + if (t.isMemberExpression(node) && t.isReferenced(node, parent)) { + // Array.from -> _core.Array.from + var obj = node.object; + prop = node.property; + + if (!t.isReferenced(obj, node)) return; + + if (!node.computed && coreHas(obj) && _.has(core[obj.name], prop.name)) { + context.skip(); + return t.prependToMemberExpression(node, file.get("coreIdentifier")); + } + } else if (t.isReferencedIdentifier(node, parent) && !t.isMemberExpression(parent) && _.contains(ALIASABLE_CONSTRUCTORS, node.name)) { + // Symbol() -> _core.Symbol(); new Promise -> new _core.Promise + return t.memberExpression(file.get("coreIdentifier"), node); + } else if (t.isCallExpression(node)) { + // arr[Symbol.iterator]() -> _core.$for.getIterator(arr) + + if (node.arguments.length) return; + + var callee = node.callee; + if (!t.isMemberExpression(callee)) return; + if (!callee.computed) return; + + prop = callee.property; + if (!t.isIdentifier(prop.object, { name: "Symbol" })) return; + if (!t.isIdentifier(prop.property, { name: "iterator" })) return; + + return util.template("corejs-iterator", { + CORE_ID: file.get("coreIdentifier"), + VALUE: callee.object + }); + } + } +}; + exports.optional = true; exports.ast = { @@ -29,44 +68,7 @@ exports.ast = { }, exit: function (ast, file) { - traverse(ast, { - enter: function (node, parent, scope, context) { - var prop; - - if (t.isMemberExpression(node) && t.isReferenced(node, parent)) { - // Array.from -> _core.Array.from - var obj = node.object; - prop = node.property; - - if (!t.isReferenced(obj, node)) return; - - if (!node.computed && coreHas(obj) && _.has(core[obj.name], prop.name)) { - context.skip(); - return t.prependToMemberExpression(node, file.get("coreIdentifier")); - } - } else if (t.isReferencedIdentifier(node, parent) && !t.isMemberExpression(parent) && _.contains(ALIASABLE_CONSTRUCTORS, node.name)) { - // Symbol() -> _core.Symbol(); new Promise -> new _core.Promise - return t.memberExpression(file.get("coreIdentifier"), node); - } else if (t.isCallExpression(node)) { - // arr[Symbol.iterator]() -> _core.$for.getIterator(arr) - - if (node.arguments.length) return; - - var callee = node.callee; - if (!t.isMemberExpression(callee)) return; - if (!callee.computed) return; - - prop = callee.property; - if (!t.isIdentifier(prop.object, { name: "Symbol" })) return; - if (!t.isIdentifier(prop.property, { name: "iterator" })) return; - - return util.template("corejs-iterator", { - CORE_ID: file.get("coreIdentifier"), - VALUE: callee.object - }); - } - } - }); + traverse(ast, astTraverser, null, file); } }; diff --git a/lib/6to5/transformation/transformers/playground/object-getter-memoization.js b/lib/6to5/transformation/transformers/playground/object-getter-memoization.js index d80cea8bf3..0287e12795 100644 --- a/lib/6to5/transformation/transformers/playground/object-getter-memoization.js +++ b/lib/6to5/transformation/transformers/playground/object-getter-memoization.js @@ -3,6 +3,20 @@ var traverse = require("../../../traverse"); var t = require("../../../types"); +var traverser = { + enter: function (node, parent, scope, context, state) { + if (t.isFunction(node)) return; + + if (t.isReturnStatement(node) && node.argument) { + node.argument = t.memberExpression(t.callExpression(state.file.addHelper("define-property"), [ + t.thisExpression(), + state.key, + node.argument + ]), state.key, true); + } + } +}; + exports.Property = exports.MethodDefinition = function (node, parent, scope, context, file) { if (node.kind !== "memo") return; @@ -17,17 +31,10 @@ exports.MethodDefinition = function (node, parent, scope, context, file) { key = t.literal(key.name); } - traverse(value, { - enter: function (node) { - if (t.isFunction(node)) return; + var state = { + key: key, + file: file + }; - if (t.isReturnStatement(node) && node.argument) { - node.argument = t.memberExpression(t.callExpression(file.addHelper("define-property"), [ - t.thisExpression(), - key, - node.argument - ]), key, true); - } - } - }); + traverse(value, traverser, null, state); }; diff --git a/lib/6to5/traverse/index.js b/lib/6to5/traverse/index.js index 3c88178c1b..ee412284be 100644 --- a/lib/6to5/traverse/index.js +++ b/lib/6to5/traverse/index.js @@ -8,8 +8,6 @@ var Scope = require("./scope"); var t = require("../types"); var _ = require("lodash"); -function noop() { } - function TraversalContext() { this.shouldFlatten = false; this.shouldRemove = false; @@ -167,8 +165,8 @@ function traverse(parent, opts, scope, state) { if (!parent) return; if (!opts) opts = {}; - if (!opts.enter) opts.enter = noop; - if (!opts.exit) opts.exit = noop; + if (!opts.enter) opts.enter = _.noop; + if (!opts.exit) opts.exit = _.noop; // array of nodes if (Array.isArray(parent)) { @@ -180,35 +178,46 @@ function traverse(parent, opts, scope, state) { } } -traverse.removeProperties = function (tree) { - var clear = function (node) { - node._declarations = null; - node.extendedRange = null; - node._scopeInfo = null; - node.tokens = null; - node.range = null; - node.start = null; - node.end = null; - node.loc = null; - node.raw = null; +function clearNode(node) { + node._declarations = null; + node.extendedRange = null; + node._scopeInfo = null; + node.tokens = null; + node.range = null; + node.start = null; + node.end = null; + node.loc = null; + node.raw = null; + if (Array.isArray(node.trailingComments)) clearComments(node.trailingComments); + + if (Array.isArray(node.leadingComments)) clearComments(node.leadingComments); - }; +} - var clearComments = function (comments) { - _.each(comments, clear); - }; +var clearTraverser = { enter: clearNode }; - clear(tree); - traverse(tree, { enter: clear }); +function clearComments(comments) { + for (var i = 0; i < comments.length; i++) + clearNode(comments[i]); +} + +traverse.removeProperties = function (tree) { + clearNode(tree); + traverse(tree, clearTraverser); return tree; }; -traverse.hasType = function (tree, type, blacklistTypes) { - blacklistTypes = [].concat(blacklistTypes || []); +function hasBlacklistedType(node, parent, scope, context, state) { + if (node.type === state.type) { + state.has = true; + context.skip(); + } +} +traverse.hasType = function (tree, type, blacklistTypes) { // the node we're searching in is blacklisted if (_.contains(blacklistTypes, tree.type)) return false; @@ -216,17 +225,13 @@ traverse.hasType = function (tree, type, blacklistTypes) { if (tree.type === type) return true; var state = { - has: false + has: false, + type: type }; traverse(tree, { blacklist: blacklistTypes, - enter: function (node, parent, scope, context, state) { - if (node.type === type) { - state.has = true; - context.skip(); - } - } + enter: hasBlacklistedType }, null, state); return state.has; diff --git a/lib/6to5/traverse/scope.js b/lib/6to5/traverse/scope.js index 55296c8c50..434f387690 100644 --- a/lib/6to5/traverse/scope.js +++ b/lib/6to5/traverse/scope.js @@ -119,6 +119,34 @@ Scope.prototype.generateTempBasedOnNode = function (node, file) { return id; }; +var functionVariableTraverser = { + enter: function (node, parent, scope, context, state) { + if (t.isFor(node)) { + _.each(FOR_KEYS, function (key) { + var declar = node[key]; + if (t.isVar(declar)) state.add(declar); + }); + } + + // this block is a function so we'll stop since none of the variables + // declared within are accessible + if (t.isFunction(node)) return context.skip(); + + // function identifier doesn't belong to this scope + if (state.blockId && node === state.blockId) return; + + if (t.isIdentifier(node) && t.isReferenced(node, parent) && !scope.has(node.name)) { + state.add(node, true); + } + + // we've ran into a declaration! + // we'll let the BlockStatement scope deal with `let` declarations unless + if (t.isDeclaration(node) && !t.isBlockScoped(node)) { + state.add(node); + } + } +}; + Scope.prototype.getInfo = function () { var parent = this.parent; var block = this.block; @@ -176,36 +204,10 @@ Scope.prototype.getInfo = function () { if (t.isProgram(block) || t.isFunction(block)) { var state = { blockId: block.id, - add: add + add: add }; - traverse(block, { - enter: function (node, parent, scope, context, state) { - if (t.isFor(node)) { - _.each(FOR_KEYS, function (key) { - var declar = node[key]; - if (t.isVar(declar)) add(declar); - }); - } - - // this block is a function so we'll stop since none of the variables - // declared within are accessible - if (t.isFunction(node)) return context.skip(); - - // function identifier doesn't belong to this scope - if (state.blockId && node === state.blockId) return; - - if (t.isIdentifier(node) && t.isReferenced(node, parent) && !scope.has(node.name)) { - state.add(node, true); - } - - // we've ran into a declaration! - // we'll let the BlockStatement scope deal with `let` declarations unless - if (t.isDeclaration(node) && !t.isBlockScoped(node)) { - state.add(node); - } - } - }, this, state); + traverse(block, functionVariableTraverser, this, state); } // Function - params, rest