| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259 |
- /**
- * 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);
- }
- });
|