diff --git a/lib/6to5/file.js b/lib/6to5/file.js index c7222c989f..ed343d9298 100644 --- a/lib/6to5/file.js +++ b/lib/6to5/file.js @@ -147,7 +147,7 @@ File.prototype.parse = function (code) { File.prototype.transform = function (ast) { this.ast = ast; - this.scope = new Scope(null, ast.program); + this.scope = new Scope(ast.program); var self = this; @@ -196,6 +196,10 @@ File.prototype.generateUid = function (name, scope) { return uid; }; +File.prototype.generateUidIdentifier = function (name, scope) { + return t.identifier(this.generateUid(name, scope)); +}; + File.prototype._generateUid = function (name) { var uids = this.uids; var i = uids[name] || 1; diff --git a/lib/6to5/transformation/transformers/_alias-functions.js b/lib/6to5/transformation/transformers/_alias-functions.js index 2c66d4d721..d509143296 100644 --- a/lib/6to5/transformation/transformers/_alias-functions.js +++ b/lib/6to5/transformation/transformers/_alias-functions.js @@ -6,11 +6,11 @@ var go = function (getBody, node, file, scope) { var thisId; var getArgumentsId = function () { - return argumentsId = argumentsId || t.identifier(file.generateUid("arguments", scope)); + return argumentsId = argumentsId || file.generateUidIdentifier("arguments", scope); }; var getThisId = function () { - return thisId = thisId || t.identifier(file.generateUid("this", scope)); + return thisId = thisId || file.generateUidIdentifier("this", scope); }; // traverse the function and find all alias functions so we can alias diff --git a/lib/6to5/transformation/transformers/classes.js b/lib/6to5/transformation/transformers/classes.js index f1837b803d..93365aae24 100644 --- a/lib/6to5/transformation/transformers/classes.js +++ b/lib/6to5/transformation/transformers/classes.js @@ -36,7 +36,7 @@ function Class(node, file, scope) { this.instanceMutatorMap = {}; this.staticMutatorMap = {}; this.hasConstructor = false; - this.className = node.id || t.identifier(file.generateUid("class", scope)); + this.className = node.id || file.generateUidIdentifier("class", scope); this.superName = node.superClass; } @@ -58,7 +58,7 @@ Class.prototype.run = function () { superClassArgument = superClassCallee = getMemberExpressionObject(superName); } else if (!t.isIdentifier(superName)) { superClassArgument = superName; - superClassCallee = superName = t.identifier(file.generateUid("ref", this.scope)); + superClassCallee = superName = file.generateUidIdentifier("ref", this.scope); } } diff --git a/lib/6to5/transformation/transformers/destructuring.js b/lib/6to5/transformation/transformers/destructuring.js index 18f1d4efc3..3389295cc4 100644 --- a/lib/6to5/transformation/transformers/destructuring.js +++ b/lib/6to5/transformation/transformers/destructuring.js @@ -77,7 +77,7 @@ var pushPattern = function (opts) { var scope = opts.scope; if (!t.isMemberExpression(parentId) && !t.isIdentifier(parentId)) { - var key = t.identifier(file.generateUid("ref", scope)); + var key = file.generateUidIdentifier("ref", scope); nodes.push(t.variableDeclaration("var", [ t.variableDeclarator(key, parentId) @@ -97,7 +97,7 @@ exports.ForOfStatement = function (node, parent, file, scope) { var pattern = declar.declarations[0].id; if (!t.isPattern(pattern)) return; - var key = t.identifier(file.generateUid("ref", scope)); + var key = file.generateUidIdentifier("ref", scope); node.left = t.variableDeclaration(declar.kind, [ t.variableDeclarator(key, null) ]); @@ -125,7 +125,7 @@ exports.Function = function (node, parent, file, scope) { if (!t.isPattern(pattern)) return pattern; hasDestructuring = true; - var parentId = t.identifier(file.generateUid("ref", scope)); + var parentId = file.generateUidIdentifier("ref", scope); pushPattern({ kind: "var", @@ -155,7 +155,7 @@ exports.ExpressionStatement = function (node, parent, file, scope) { var nodes = []; - var ref = t.identifier(file.generateUid("ref", scope)); + var ref = file.generateUidIdentifier("ref", scope); nodes.push(t.variableDeclaration("var", [ t.variableDeclarator(ref, expr.right) ])); diff --git a/lib/6to5/transformation/transformers/for-of.js b/lib/6to5/transformation/transformers/for-of.js index 4c8d52da85..3355d9407a 100644 --- a/lib/6to5/transformation/transformers/for-of.js +++ b/lib/6to5/transformation/transformers/for-of.js @@ -5,7 +5,7 @@ exports.ForOfStatement = function (node, parent, file, scope) { var left = node.left; var declar; - var stepKey = t.identifier(file.generateUid("step", scope)); + var stepKey = file.generateUidIdentifier("step", scope); var stepValue = t.memberExpression(stepKey, t.identifier("value")); if (t.isIdentifier(left)) { diff --git a/lib/6to5/transformation/transformers/generators/visit.js b/lib/6to5/transformation/transformers/generators/visit.js index 49c13f18ef..093abaed34 100644 --- a/lib/6to5/transformation/transformers/generators/visit.js +++ b/lib/6to5/transformation/transformers/generators/visit.js @@ -11,6 +11,7 @@ var runtimeProperty = require("./util").runtimeProperty; var Emitter = require("./emit").Emitter; var hoist = require("./hoist").hoist; +var Scope = require("../../../traverse/scope"); var types = require("ast-types"); var t = require("../../../types"); @@ -18,169 +19,172 @@ var runtimeAsyncMethod = runtimeProperty("async"); var runtimeWrapMethod = runtimeProperty("wrap"); var runtimeMarkMethod = runtimeProperty("mark"); -exports.transform = function transform(node) { - return types.visit(node, visitor); +exports.transform = function transform(node, file) { + return types.visit(node, { + visitFunction: function (path) { + return visitor.call(this, path, file); + } + }); }; -var visitor = types.PathVisitor.fromMethodsObject({ - visitFunction: function (path) { - // Calling this.traverse(path) first makes for a post-order traversal. - this.traverse(path); +var visitor = function (path, file) { + // Calling this.traverse(path) first makes for a post-order traversal. + this.traverse(path); - var node = path.value; + var node = path.value; + var scope = new Scope(node); - if (!node.generator && !node.async) { + if (!node.generator && !node.async) { + return; + } + + node.generator = false; + + if (node.expression) { + // Transform expression lambdas into normal functions. + node.expression = false; + node.body = t.blockStatement([ + t.returnStatement(node.body) + ]); + } + + if (node.async) { + awaitVisitor.visit(path.get("body")); + } + + var outerFnId = node.id || ( + node.id = file.generateUidIdentifier("callee", scope) + ); + + var innerFnId = t.identifier(node.id.name + "$"); + var contextId = file.generateUidIdentifier("context$", scope); + var vars = hoist(path); + + var emitter = new Emitter(contextId); + emitter.explode(path.get("body")); + + var outerBody = []; + + if (vars && vars.declarations.length > 0) { + outerBody.push(vars); + } + + var wrapArgs = [ + emitter.getContextFunction(innerFnId), + // Async functions don't care about the outer function because they + // don't need it to be marked and don't inherit from its .prototype. + node.async ? t.literal(null) : outerFnId, + t.thisExpression() + ]; + + var tryEntryList = emitter.getTryEntryList(); + if (tryEntryList) { + wrapArgs.push(tryEntryList); + } + + var wrapCall = t.callExpression( + node.async ? runtimeAsyncMethod : runtimeWrapMethod, + wrapArgs + ); + + outerBody.push(t.returnStatement(wrapCall)); + node.body = t.blockStatement(outerBody); + + if (node.async) { + node.async = false; + return; + } + + if (t.isFunctionDeclaration(node)) { + var pp = path.parent; + + while (pp && !(t.isBlockStatement(pp.value) || t.isProgram(pp.value))) { + pp = pp.parent; + } + + if (!pp) { return; } - node.generator = false; + // Here we turn the FunctionDeclaration into a named + // FunctionExpression that will be assigned to a variable of the + // same name at the top of the enclosing block. This is important + // for a very subtle reason: named function expressions can refer to + // themselves by name without fear that the binding may change due + // to code executing outside the function, whereas function + // declarations are vulnerable to the following rebinding: + // + // function f() { return f } + // var g = f; + // f = "asdf"; + // g(); // "asdf" + // + // One way to prevent the problem illustrated above is to transform + // the function declaration thus: + // + // var f = function f() { return f }; + // var g = f; + // f = "asdf"; + // g(); // f + // g()()()()(); // f + // + // In the code below, we transform generator function declarations + // in the following way: + // + // gen().next(); // { value: gen, done: true } + // function *gen() { + // return gen; + // } + // + // becomes something like + // + // var gen = runtime.mark(function *gen() { + // return gen; + // }); + // gen().next(); // { value: gen, done: true } + // + // which ensures that the generator body can always reliably refer + // to gen by name. - if (node.expression) { - // Transform expression lambdas into normal functions. - node.expression = false; - node.body = t.blockStatement([ - t.returnStatement(node.body) - ]); - } + // Remove the FunctionDeclaration so that we can add it back as a + // FunctionExpression passed to runtime.mark. + path.replace(); - if (node.async) { - awaitVisitor.visit(path.get("body")); - } + // Change the type of the function to be an expression instead of a + // declaration. Note that all the other fields are the same. + node.type = "FunctionExpression"; - var outerFnId = node.id || ( - node.id = path.scope.parent.declareTemporary("callee$") - ); + var varDecl = t.variableDeclaration("var", [ + t.variableDeclarator( + node.id, + t.callExpression(runtimeMarkMethod, [node]) + ) + ]); - var innerFnId = t.identifier(node.id.name + "$"); - var contextId = path.scope.declareTemporary("context$"); - var vars = hoist(path); + // Copy any comments preceding the function declaration to the + // variable declaration, to avoid weird formatting consequences. + t.inheritsComments(varDecl, node); + t.removeComments(node); - var emitter = new Emitter(contextId); - emitter.explode(path.get("body")); + varDecl._blockHoist = true; - var outerBody = []; + var bodyPath = pp.get("body"); + var bodyLen = bodyPath.value.length; - if (vars && vars.declarations.length > 0) { - outerBody.push(vars); - } - - var wrapArgs = [ - emitter.getContextFunction(innerFnId), - // Async functions don't care about the outer function because they - // don't need it to be marked and don't inherit from its .prototype. - node.async ? t.literal(null) : outerFnId, - t.thisExpression() - ]; - - var tryEntryList = emitter.getTryEntryList(); - if (tryEntryList) { - wrapArgs.push(tryEntryList); - } - - var wrapCall = t.callExpression( - node.async ? runtimeAsyncMethod : runtimeWrapMethod, - wrapArgs - ); - - outerBody.push(t.returnStatement(wrapCall)); - node.body = t.blockStatement(outerBody); - - if (node.async) { - node.async = false; - return; - } - - if (t.isFunctionDeclaration(node)) { - var pp = path.parent; - - while (pp && !(t.isBlockStatement(pp.value) || t.isProgram(pp.value))) { - pp = pp.parent; - } - - if (!pp) { + for (var i = 0; i < bodyLen; ++i) { + var firstStmtPath = bodyPath.get(i); + if (!shouldNotHoistAbove(firstStmtPath)) { + firstStmtPath.insertBefore(varDecl); return; } - - // Here we turn the FunctionDeclaration into a named - // FunctionExpression that will be assigned to a variable of the - // same name at the top of the enclosing block. This is important - // for a very subtle reason: named function expressions can refer to - // themselves by name without fear that the binding may change due - // to code executing outside the function, whereas function - // declarations are vulnerable to the following rebinding: - // - // function f() { return f } - // var g = f; - // f = "asdf"; - // g(); // "asdf" - // - // One way to prevent the problem illustrated above is to transform - // the function declaration thus: - // - // var f = function f() { return f }; - // var g = f; - // f = "asdf"; - // g(); // f - // g()()()()(); // f - // - // In the code below, we transform generator function declarations - // in the following way: - // - // gen().next(); // { value: gen, done: true } - // function *gen() { - // return gen; - // } - // - // becomes something like - // - // var gen = runtime.mark(function *gen() { - // return gen; - // }); - // gen().next(); // { value: gen, done: true } - // - // which ensures that the generator body can always reliably refer - // to gen by name. - - // Remove the FunctionDeclaration so that we can add it back as a - // FunctionExpression passed to runtime.mark. - path.replace(); - - // Change the type of the function to be an expression instead of a - // declaration. Note that all the other fields are the same. - node.type = "FunctionExpression"; - - var varDecl = t.variableDeclaration("var", [ - t.variableDeclarator( - node.id, - t.callExpression(runtimeMarkMethod, [node]) - ) - ]); - - // Copy any comments preceding the function declaration to the - // variable declaration, to avoid weird formatting consequences. - t.inheritsComments(varDecl, node); - t.removeComments(node); - - varDecl._blockHoist = true; - - var bodyPath = pp.get("body"); - var bodyLen = bodyPath.value.length; - - for (var i = 0; i < bodyLen; ++i) { - var firstStmtPath = bodyPath.get(i); - if (!shouldNotHoistAbove(firstStmtPath)) { - firstStmtPath.insertBefore(varDecl); - return; - } - } - - bodyPath.push(varDecl); - } else { - t.assertFunctionExpression(node); - return t.callExpression(runtimeMarkMethod, [node]); } + + bodyPath.push(varDecl); + } else { + t.assertFunctionExpression(node); + return t.callExpression(runtimeMarkMethod, [node]); } -}); +}; function shouldNotHoistAbove(stmtPath) { var value = stmtPath.value;