All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.javascript.jscomp.InlineSimpleMethods Maven / Gradle / Ivy

Go to download

Closure Compiler is a JavaScript optimizing compiler. It parses your JavaScript, analyzes it, removes dead code and rewrites and minimizes what's left. It also checks syntax, variable references, and types, and warns about common JavaScript pitfalls. It is used in many of Google's JavaScript apps, including Gmail, Google Web Search, Google Maps, and Google Docs. This binary checks for style issues such as incorrect or missing JSDoc usage, and missing goog.require() statements. It does not do more advanced checks such as typechecking.

There is a newer version: v20200830
Show newest version
/*
 * Copyright 2007 The Closure Compiler Authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.javascript.jscomp;

import static com.google.common.base.Preconditions.checkState;

import com.google.javascript.jscomp.NodeTraversal.Callback;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.Node;
import java.util.Collection;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Inlines methods that take no arguments and have only a return statement
 * returning a property. Because it works on method names rather than type
 * inference, a method with multiple definitions will be inlined if each
 * definition is identical.
 *
 * 
 * A.prototype.foo = function() { return this.b; }
 * B.prototype.foo = function() { return this.b; }
 * 
* * will inline foo, but * *
 * A.prototype.foo = function() { return this.b; }
 * B.prototype.foo = function() { return this.c; }
 * 
* * will not. * * Declarations are not removed because we do not find all possible * call sites. For examples, calls of the form foo["bar"] are not * detected. * * This pass is not on by default because it is not safe in simple mode. * If the prototype method is mutated and we don't detect that, inlining it is * unsafe. * We enable it whenever function inlining is enabled. * */ class InlineSimpleMethods extends MethodCompilerPass { private static final Logger logger = Logger.getLogger(InlineSimpleMethods.class.getName()); private final AstAnalyzer astAnalyzer; InlineSimpleMethods(AbstractCompiler compiler) { super(compiler); astAnalyzer = compiler.getAstAnalyzer(); } @Override public void process(Node externs, Node root) { checkState(compiler.getLifeCycleStage().isNormalized(), compiler.getLifeCycleStage()); super.process(externs, root); } /** * For each method call, see if it is a candidate for inlining. * TODO(kushal): Cache the results of the checks */ private class InlineTrivialAccessors extends InvocationsCallback { @Override void visit(NodeTraversal t, Node callNode, Node parent, String callName) { if (externMethods.contains(callName) || nonMethodProperties.contains(callName)) { return; } Collection definitions = methodDefinitions.get(callName); if (definitions == null || definitions.isEmpty()) { return; } // Do check of arity, complexity, and consistency in what we think is // the order from least to most complex Node firstDefinition = definitions.iterator().next(); // Check any multiple definitions if (definitions.size() == 1 || allDefinitionsEquivalent(definitions)) { if (!argsMayHaveSideEffects(callNode)) { // Verify this is a trivial return Node returned = returnedExpression(firstDefinition); if (returned != null) { if (isPropertyTree(returned)) { if (logger.isLoggable(Level.FINE)) { logger.fine("Inlining property accessor: " + callName); } inlinePropertyReturn(parent, callNode, returned); } else if (NodeUtil.isLiteralValue(returned, false) && !astAnalyzer.mayHaveSideEffects(callNode.getFirstChild())) { if (logger.isLoggable(Level.FINE)) { logger.fine("Inlining constant accessor: " + callName); } inlineConstReturn(parent, callNode, returned); } } else if (isEmptyMethod(firstDefinition) && !astAnalyzer.mayHaveSideEffects(callNode.getFirstChild())) { if (logger.isLoggable(Level.FINE)) { logger.fine("Inlining empty method: " + callName); } inlineEmptyMethod(t, parent, callNode); } } } else { if (logger.isLoggable(Level.FINE)) { logger.fine("Method '" + callName + "' has conflicting definitions."); } } } } @Override Callback getActingCallback() { return new InlineTrivialAccessors(); } /** * Returns true if the provided node is a getprop for * which the left child is this or a valid property tree * and for which the right side is a string. */ private static boolean isPropertyTree(Node expectedGetprop) { if (!expectedGetprop.isGetProp()) { return false; } Node getpropLhs = expectedGetprop.getFirstChild(); return getpropLhs.isThis() || isPropertyTree(getpropLhs); } /** * Finds the occurrence of "this" in the provided property tree and replaces * it with replacement */ private static void replaceThis(Node expectedGetprop, Node replacement) { Node leftChild = expectedGetprop.getFirstChild(); if (leftChild.isThis()) { expectedGetprop.replaceChild(leftChild, replacement); } else { replaceThis(leftChild, replacement); } } /** * Return the node that represents the expression returned * by the method, given a FUNCTION node. */ private static Node returnedExpression(Node fn) { Node expectedBlock = NodeUtil.getFunctionBody(fn); if (!expectedBlock.hasOneChild()) { return null; } Node expectedReturn = expectedBlock.getFirstChild(); if (!expectedReturn.isReturn()) { return null; } if (!expectedReturn.hasOneChild()) { return null; } return expectedReturn.getOnlyChild(); } /** * Return whether the given FUNCTION node is an empty method definition. * * Must be private, or moved to NodeUtil. */ private static boolean isEmptyMethod(Node fn) { return NodeUtil.isEmptyBlock(NodeUtil.getFunctionBody(fn)); } /** Given a set of method definitions, verify they are the same. */ private boolean allDefinitionsEquivalent(Collection definitions) { Node first = null; for (Node n : definitions) { if (first == null) { first = n; } else if (!compiler.areNodesEqualForInlining(first, n)) { return false; } // else continue } return true; } /** * Replace the provided method call with the tree specified in returnedValue * * Parse tree of a call is * name * call * getprop * obj * string */ private void inlinePropertyReturn(Node parent, Node call, Node returnedValue) { Node getProp = returnedValue.cloneTree(); replaceThis(getProp, call.getFirstChild().removeFirstChild()); parent.replaceChild(call, getProp); compiler.reportChangeToEnclosingScope(getProp); } /** * Replace the provided object and its method call with the tree specified * in returnedValue. Should be called only if the object reference has * no side effects. */ private void inlineConstReturn(Node parent, Node call, Node returnedValue) { Node retValue = returnedValue.cloneTree(); parent.replaceChild(call, retValue); compiler.reportChangeToEnclosingScope(retValue); } /** * Remove the provided object and its method call. */ private void inlineEmptyMethod(NodeTraversal t, Node parent, Node call) { // If the return value of the method call is read, // replace it with "void 0". Otherwise, remove the call entirely. if (NodeUtil.isExprCall(parent)) { parent.replaceWith(IR.empty()); NodeUtil.markFunctionsDeleted(parent, compiler); } else { Node srcLocation = call; parent.replaceChild(call, NodeUtil.newUndefinedNode(srcLocation)); NodeUtil.markFunctionsDeleted(call, compiler); } t.reportCodeChange(); } /** * Check whether the given method call's arguments have side effects. * @param call The call node of a method invocation. */ private boolean argsMayHaveSideEffects(Node call) { for (Node currentChild = call.getSecondChild(); currentChild != null; currentChild = currentChild.getNext()) { if (astAnalyzer.mayHaveSideEffects(currentChild)) { return true; } } return false; } /** * A do-nothing signature store. */ static final MethodCompilerPass.SignatureStore DUMMY_SIGNATURE_STORE = new MethodCompilerPass.SignatureStore() { @Override public void addSignature( String functionName, Node functionNode, String sourceFile) { } @Override public void removeSignature(String functionName) { } @Override public void reset() { } }; @Override SignatureStore getSignatureStore() { return DUMMY_SIGNATURE_STORE; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy