239 lines
9.3 KiB
JavaScript
239 lines
9.3 KiB
JavaScript
"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
|