"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ClosureRefVisitor = exports.ClosureRefReplacer = undefined; var _realm = require("../realm.js"); var _index = require("../values/index.js"); var _babelTypes = require("babel-types"); var t = _interopRequireWildcard(_babelTypes); var _jsx = require("../react/jsx"); var _internalizer = require("../utils/internalizer.js"); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } function markVisited(node, data) { node._renamedOnce = data; } /** * Copyright (c) 2017-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ function shouldVisit(node, data) { return node._renamedOnce !== data; } // replaceWith causes the node to be re-analyzed, so to prevent double replacement // we add this property on the node to mark it such that it does not get replaced // again on this pass // TODO: Make this work when replacing with arbitrary BabelNodeExpressions. Currently // if the node that we're substituting contains identifiers as children, // they will be visited again and possibly transformed. // If necessary we could implement this by following node.parentPath and checking // if any parent nodes are marked visited, but that seem unnecessary right now.let closureRefReplacer = { function replaceName(path, residualFunctionBinding, name, data) { if (path.scope.hasBinding(name, /*noGlobals*/true)) return; if (residualFunctionBinding && shouldVisit(path.node, data)) { markVisited(residualFunctionBinding.serializedValue, data); var serializedValue = residualFunctionBinding.serializedValue; if (path.node.type === "JSXIdentifier" || path.node.type === "JSXMemberIdentifier") { path.replaceWith((0, _jsx.convertExpressionToJSXIdentifier)(serializedValue, true)); } else { path.replaceWith(serializedValue); } } } function getLiteralTruthiness(node) { // In the return value, 'known' is true only if this is a literal of known truthiness and with no side effects; if 'known' is true, 'value' is its truthiness. if (t.isBooleanLiteral(node) || t.isNumericLiteral(node) || t.isStringLiteral(node)) { return { known: true, value: !!node.value }; } if (t.isFunctionExpression(node) || t.isArrowFunctionExpression(node) || t.isRegExpLiteral(node) || t.isClassExpression(node) && node.superClass === null && node.body.body.length === 0 || t.isObjectExpression(node) && node.properties.length === 0 || t.isArrayExpression(node) && node.elements.length === 0) { return { known: true, value: true }; } if (t.isNullLiteral(node)) { return { known: true, value: false }; } return { known: false }; } function canShareFunctionBody(duplicateFunctionInfo) { // Only share function when: // 1. it does not access any free variables. // 2. it does not use "this". var _duplicateFunctionInf = duplicateFunctionInfo.functionInfo, unbound = _duplicateFunctionInf.unbound, modified = _duplicateFunctionInf.modified, usesThis = _duplicateFunctionInf.usesThis; return unbound.size === 0 && modified.size === 0 && !usesThis; } var ClosureRefReplacer = exports.ClosureRefReplacer = { ReferencedIdentifier: function ReferencedIdentifier(path, state) { if (ignorePath(path)) return; var residualFunctionBindings = state.residualFunctionBindings; var name = path.node.name; var residualFunctionBinding = residualFunctionBindings.get(name); if (residualFunctionBinding) replaceName(path, residualFunctionBinding, name, residualFunctionBindings); }, CallExpression: function CallExpression(path, state) { // Here we apply the require optimization by replacing require calls with their // corresponding initialized modules. var requireReturns = state.requireReturns; if (!state.isRequire || !state.isRequire(path.scope, path.node)) return; state.requireStatistics.count++; if (state.modified.has(path.node.callee.name)) return; var moduleId = "" + path.node.arguments[0].value; var new_node = requireReturns.get(moduleId); if (new_node !== undefined) { markVisited(new_node, state.residualFunctionBindings); path.replaceWith(new_node); state.requireStatistics.replaced++; } }, "AssignmentExpression|UpdateExpression": function AssignmentExpressionUpdateExpression(path, state) { var residualFunctionBindings = state.residualFunctionBindings; var ids = path.getBindingIdentifierPaths(); for (var name in ids) { var residualFunctionBinding = residualFunctionBindings.get(name); if (residualFunctionBinding) { var nestedPath = ids[name]; replaceName(nestedPath, residualFunctionBinding, name, residualFunctionBindings); } } }, // TODO: handle FunctionDeclaration. // Replace "function () {}" ==> "factory_id.bind(null)". FunctionExpression: function FunctionExpression(path, state) { if (t.isProgram(path.parentPath.parentPath.node)) { // Our goal is replacing duplicate nested function so skip root residual function itself. // This assumes the root function is wrapped with: t.file(t.program([t.expressionStatement(rootFunction). return; } var functionExpression = path.node; var functionTag = functionExpression.body.uniqueOrderedTag; if (!functionTag) { // Un-interpreted nested function. return; } var duplicateFunctionInfo = state.factoryFunctionInfos.get(functionTag); if (duplicateFunctionInfo && canShareFunctionBody(duplicateFunctionInfo)) { var factoryId = duplicateFunctionInfo.factoryId; path.replaceWith(t.callExpression(t.memberExpression(factoryId, t.identifier("bind")), [_internalizer.nullExpression])); } }, // A few very simple dead code elimination helpers. Eventually these should be subsumed by the partial evaluators. IfStatement: { exit: function exit(path, state) { var node = path.node; var testTruthiness = getLiteralTruthiness(node.test); if (testTruthiness.known) { if (testTruthiness.value) { // Strictly speaking this is not safe: Annex B.3.4 allows FunctionDeclarations as the body of IfStatements in sloppy mode, // which have weird hoisting behavior: `console.log(typeof f); if (true) function f(){} console.log(typeof f)` will print 'undefined', 'function', but // `console.log(typeof f); function f(){} console.log(typeof f)` will print 'function', 'function'. // However, Babylon can't parse these, so it doesn't come up. path.replaceWith(node.consequent); } else { if (node.alternate !== null) { path.replaceWith(node.alternate); } else { path.remove(); } } } } }, ConditionalExpression: { exit: function exit(path, state) { var node = path.node; var testTruthiness = getLiteralTruthiness(node.test); if (testTruthiness.known) { path.replaceWith(testTruthiness.value ? node.consequent : node.alternate); } } }, LogicalExpression: { exit: function exit(path, state) { var node = path.node; var leftTruthiness = getLiteralTruthiness(node.left); if (node.operator === "&&" && leftTruthiness.known) { path.replaceWith(leftTruthiness.value ? node.right : node.left); } else if (node.operator === "||" && leftTruthiness.known) { path.replaceWith(leftTruthiness.value ? node.left : node.right); } } }, WhileStatement: { exit: function exit(path, state) { var node = path.node; var testTruthiness = getLiteralTruthiness(node.test); if (testTruthiness.known && !testTruthiness.value) { path.remove(); } } } }; function visitName(path, state, name, modified) { // Is the name bound to some local identifier? If so, we don't need to do anything if (path.scope.hasBinding(name, /*noGlobals*/true)) return; // Otherwise, let's record that there's an unbound identifier state.functionInfo.unbound.add(name); if (modified) state.functionInfo.modified.add(name); } function ignorePath(path) { var parent = path.parent; return t.isLabeledStatement(parent) || t.isBreakStatement(parent) || t.isContinueStatement(parent); } // TODO #886: doesn't check that `arguments` and `this` is in top function var ClosureRefVisitor = exports.ClosureRefVisitor = { ReferencedIdentifier: function ReferencedIdentifier(path, state) { if (ignorePath(path)) return; var innerName = path.node.name; if (innerName === "arguments") { state.functionInfo.usesArguments = true; return; } visitName(path, state, innerName, false); }, ThisExpression: function ThisExpression(path, state) { state.functionInfo.usesThis = true; }, "AssignmentExpression|UpdateExpression": function AssignmentExpressionUpdateExpression(path, state) { for (var name in path.getBindingIdentifiers()) { visitName(path, state, name, true); } } }; //# sourceMappingURL=visitors.js.map