/** * Copyright (c) 2014, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * https://raw.github.com/facebook/regenerator/master/LICENSE file. An * additional grant of patent rights can be found in the PATENTS file in * the same directory. */ var assert = require("assert"); var fs = require("fs"); var types = require("ast-types"); var n = types.namedTypes; var b = types.builders; var isArray = types.builtInTypes.array; var isObject = types.builtInTypes.object; var NodePath = types.NodePath; var hoist = require("./hoist").hoist; var Emitter = require("./emit").Emitter; var runtimeProperty = require("./util").runtimeProperty; exports.transform = function transform(node, options) { options = options || {}; var path = node instanceof NodePath ? node : new NodePath(node); visitor.visit(path, options); node = path.value; options.madeChanges = visitor.wasChangeReported(); return node; }; var visitor = types.PathVisitor.fromMethodsObject({ reset: function(node, options) { this.options = options; }, visitFunction: function(path) { // Calling this.traverse(path) first makes for a post-order traversal. this.traverse(path); var node = path.value; var shouldTransformAsync = node.async && !this.options.disableAsync; if (!node.generator && !shouldTransformAsync) { return; } this.reportChanged(); node.generator = false; if (node.expression) { // Transform expression lambdas into normal functions. node.expression = false; node.body = b.blockStatement([ b.returnStatement(node.body) ]); } if (shouldTransformAsync) { awaitVisitor.visit(path.get("body")); } var outerFnId = node.id || ( node.id = path.scope.parent.declareTemporary("callee$") ); var outerBody = []; var bodyBlock = path.value.body; bodyBlock.body = bodyBlock.body.filter(function (node) { if (node && node._blockHoist != null) { outerBody.push(node); return false; } else { return true; } }); var innerFnId = b.identifier(node.id.name + "$"); var contextId = path.scope.declareTemporary("context$"); var vars = hoist(path); var emitter = new Emitter(contextId); emitter.explode(path.get("body")); 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. shouldTransformAsync ? b.literal(null) : outerFnId, b.thisExpression() ]; var tryLocsList = emitter.getTryLocsList(); if (tryLocsList) { wrapArgs.push(tryLocsList); } var wrapCall = b.callExpression( shouldTransformAsync ? runtimeProperty("async") : runtimeProperty("wrap"), wrapArgs ); outerBody.push(b.returnStatement(wrapCall)); node.body = b.blockStatement(outerBody); if (shouldTransformAsync) { node.async = false; return; } if (n.FunctionDeclaration.check(node)) { var pp = path.parent; while (pp && !(n.BlockStatement.check(pp.value) || n.Program.check(pp.value))) { pp = pp.parent; } if (!pp) { 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 = b.variableDeclaration("var", [ b.variableDeclarator( node.id, b.callExpression(runtimeProperty("mark"), [node]) ) ]); if (node.comments) { // Copy any comments preceding the function declaration to the // variable declaration, to avoid weird formatting consequences. varDecl.leadingComments = node.leadingComments; varDecl.trailingComments = node.trailingComments; node.leadingComments = null; node.trailingComments = null; } varDecl._blockHoist = 3; var bodyPath = pp.get("body"); var bodyLen = bodyPath.value.length; bodyPath.push(varDecl); } else { n.FunctionExpression.assert(node); return b.callExpression(runtimeProperty("mark"), [node]); } } }); function shouldNotHoistAbove(stmtPath) { var value = stmtPath.value; n.Statement.assert(value); // If the first statement is a "use strict" declaration, make sure to // insert hoisted declarations afterwards. if (n.ExpressionStatement.check(value) && n.Literal.check(value.expression) && value.expression.value === "use strict") { return true; } if (n.VariableDeclaration.check(value)) { for (var i = 0; i < value.declarations.length; ++i) { var decl = value.declarations[i]; if (n.CallExpression.check(decl.init) && types.astNodesAreEquivalent(decl.init.callee, runtimeProperty("mark"))) { return true; } } } return false; } var awaitVisitor = types.PathVisitor.fromMethodsObject({ visitFunction: function(path) { return false; // Don't descend into nested function scopes. }, visitAwaitExpression: function(path) { // Convert await and await* expressions to yield expressions. var argument = path.value.argument; // If the parser supports await* syntax using a boolean .all property // (#171), desugar that syntax to yield Promise.all(argument). if (path.value.all) { argument = b.callExpression( b.memberExpression( b.identifier("Promise"), b.identifier("all"), false ), [argument] ); } return b.yieldExpression(argument, false); } });