UNPKG

kitchensink

Version:

Dispatch's awesome components and style guide

404 lines (340 loc) 10.9 kB
/** * 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 recast = require("recast"); var types = recast.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 util = require("./util"); var runtimeProperty = util.runtimeProperty; var getMarkInfo = require("private").makeAccessor(); exports.transform = function transform(node, options) { options = options || {}; var path = node instanceof NodePath ? node : new NodePath(node); visitor.visit(path, options); node = path.value; if (options.includeRuntime === true || (options.includeRuntime === 'if used' && visitor.wasChangeReported())) { injectRuntime(n.File.check(node) ? node.program : node); } options.madeChanges = visitor.wasChangeReported(); return node; }; function injectRuntime(program) { n.Program.assert(program); // Include the runtime by modifying the AST rather than by concatenating // strings. This technique will allow for more accurate source mapping. var runtimePath = require("..").runtime.path; var runtime = fs.readFileSync(runtimePath, "utf8"); var runtimeBody = recast.parse(runtime, { sourceFileName: runtimePath }).program.body; var body = program.body; body.unshift.apply(body, runtimeBody); } 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(); 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 outerBody = []; var innerBody = []; var bodyPath = path.get("body", "body"); bodyPath.each(function(childPath) { var node = childPath.value; if (node && node._blockHoist != null) { outerBody.push(node); } else { innerBody.push(node); } }); if (outerBody.length > 0) { // Only replace the inner body if we actually hoisted any statements // to the outer body. bodyPath.replace(innerBody); } var outerFnExpr = getOuterFnExpr(path); // Note that getOuterFnExpr has the side-effect of ensuring that the // function has a name (so node.id will always be an Identifier), even // if a temporary name has to be synthesized. n.Identifier.assert(node.id); var innerFnId = b.identifier(node.id.name + "$"); var contextId = path.scope.declareTemporary("context$"); var argsId = path.scope.declareTemporary("args$"); // Turn all declarations into vars, and replace the original // declarations with equivalent assignment expressions. var vars = hoist(path); var didRenameArguments = renameArguments(path, argsId); if (didRenameArguments) { vars = vars || b.variableDeclaration("var", []); vars.declarations.push(b.variableDeclarator( argsId, b.identifier("arguments") )); } 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 that are not generators don't care about the // outer function because they don't need it to be marked and don't // inherit from its .prototype. node.generator ? outerFnExpr : b.literal(null), b.thisExpression() ]; var tryLocsList = emitter.getTryLocsList(); if (tryLocsList) { wrapArgs.push(tryLocsList); } var wrapCall = b.callExpression( runtimeProperty(shouldTransformAsync ? "async" : "wrap"), wrapArgs ); outerBody.push(b.returnStatement(wrapCall)); node.body = b.blockStatement(outerBody); var wasGeneratorFunction = node.generator; if (wasGeneratorFunction) { node.generator = false; } if (shouldTransformAsync) { node.async = false; } if (wasGeneratorFunction && n.Expression.check(node)) { return b.callExpression(runtimeProperty("mark"), [node]); } }, visitForOfStatement: function(path) { this.traverse(path); var node = path.value; var tempIterId = path.scope.declareTemporary("t$"); var tempIterDecl = b.variableDeclarator( tempIterId, b.callExpression( runtimeProperty("values"), [node.right] ) ); var tempInfoId = path.scope.declareTemporary("t$"); var tempInfoDecl = b.variableDeclarator(tempInfoId, null); var init = node.left; var loopId; if (n.VariableDeclaration.check(init)) { loopId = init.declarations[0].id; init.declarations.push(tempIterDecl, tempInfoDecl); } else { loopId = init; init = b.variableDeclaration("var", [ tempIterDecl, tempInfoDecl ]); } n.Identifier.assert(loopId); var loopIdAssignExprStmt = b.expressionStatement( b.assignmentExpression( "=", loopId, b.memberExpression( tempInfoId, b.identifier("value"), false ) ) ); if (n.BlockStatement.check(node.body)) { node.body.body.unshift(loopIdAssignExprStmt); } else { node.body = b.blockStatement([ loopIdAssignExprStmt, node.body ]); } return b.forStatement( init, b.unaryExpression( "!", b.memberExpression( b.assignmentExpression( "=", tempInfoId, b.callExpression( b.memberExpression( tempIterId, b.identifier("next"), false ), [] ) ), b.identifier("done"), false ) ), null, node.body ); } }); // Given a NodePath for a Function, return an Expression node that can be // used to refer reliably to the function object from inside the function. // This expression is essentially a replacement for arguments.callee, with // the key advantage that it works in strict mode. function getOuterFnExpr(funPath) { var node = funPath.value; n.Function.assert(node); if (node.generator && // Non-generator functions don't need to be marked. n.FunctionDeclaration.check(node)) { var pp = funPath.parent; while (pp && !(n.BlockStatement.check(pp.value) || n.Program.check(pp.value))) { pp = pp.parent; } if (!pp) { return node.id; } var markDecl = getRuntimeMarkDecl(pp); var markedArray = markDecl.declarations[0].id; var funDeclIdArray = markDecl.declarations[0].init.callee.object; n.ArrayExpression.assert(funDeclIdArray); var index = funDeclIdArray.elements.length; funDeclIdArray.elements.push(node.id); return b.memberExpression( markedArray, b.literal(index), true ); } return node.id || ( node.id = funPath.scope.parent.declareTemporary("callee$") ); } function getRuntimeMarkDecl(blockPath) { assert.ok(blockPath instanceof NodePath); var block = blockPath.node; isArray.assert(block.body); var info = getMarkInfo(block); if (info.decl) { return info.decl; } info.decl = b.variableDeclaration("var", [ b.variableDeclarator( blockPath.scope.declareTemporary("marked"), b.callExpression( b.memberExpression( b.arrayExpression([]), b.identifier("map"), false ), [runtimeProperty("mark")] ) ) ]); for (var i = 0; i < block.body.length; ++i) { if (!shouldNotHoistAbove(blockPath.get("body", i))) { break; } } blockPath.get("body").insertAt(i, info.decl); return info.decl; } 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. return n.ExpressionStatement.check(value) && n.Literal.check(value.expression) && value.expression.value === "use strict"; } function renameArguments(funcPath, argsId) { assert.ok(funcPath instanceof types.NodePath); var func = funcPath.value; var didRenameArguments = false; recast.visit(funcPath, { visitFunction: function(path) { if (path.value === func) { this.traverse(path); } else { return false; } }, visitIdentifier: function(path) { if (path.value.name === "arguments" && util.isReference(path)) { path.replace(argsId); didRenameArguments = true; return false; } this.traverse(path); } }); // If the traversal replaced any arguments references, then we need to // alias the outer function's arguments binding (be it the implicit // arguments object or some other parameter or variable) to the variable // named by argsId. return didRenameArguments; } 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] ); } // Transforming `await x` to `yield regeneratorRuntime.awrap(x)` // causes the argument to be wrapped in such a way that the runtime // can distinguish between awaited and merely yielded values. return b.yieldExpression( b.callExpression( runtimeProperty("awrap"), [argument] ), false ); } });