
com.google.javascript.jscomp.ChainCalls Maven / Gradle / Ivy
/*
* Copyright 2009 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 com.google.javascript.jscomp.ControlFlowGraph.Branch;
import com.google.javascript.jscomp.DefinitionsRemover.Definition;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.jscomp.NodeTraversal.ScopedCallback;
import com.google.javascript.jscomp.graph.DiGraph.DiGraphEdge;
import com.google.javascript.rhino.Node;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Chain calls to functions that return this.
*
*/
class ChainCalls implements CompilerPass {
private final AbstractCompiler compiler;
private final Set badFunctionNodes = new HashSet<>();
private final Set goodFunctionNodes = new HashSet<>();
private final List callSites = new ArrayList<>();
private SimpleDefinitionFinder defFinder;
private GatherFunctions gatherFunctions = new GatherFunctions();
ChainCalls(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void process(Node externs, Node root) {
defFinder = new SimpleDefinitionFinder(compiler);
defFinder.process(externs, root);
NodeTraversal.traverseEs6(compiler, root, new GatherCallSites());
for (CallSite callSite : callSites) {
callSite.parent.removeChild(callSite.n);
callSite.n.removeChild(callSite.callNode);
callSite.nextGetPropNode.replaceChild(callSite.nextGetPropFirstChildNode,
callSite.callNode);
compiler.reportCodeChange();
}
}
/**
* Determines whether a function always returns this.
*/
private class GatherFunctions implements ScopedCallback {
@Override
public void enterScope(NodeTraversal t) {
ControlFlowGraph cfg = t.getControlFlowGraph();
for (DiGraphEdge s : cfg.getImplicitReturn().getInEdges()) {
Node exitNode = s.getSource().getValue();
if (!exitNode.isReturn() ||
exitNode.getFirstChild() == null ||
!exitNode.getFirstChild().isThis()) {
badFunctionNodes.add(t.getScopeRoot());
return;
}
}
goodFunctionNodes.add(t.getScopeRoot());
}
@Override
public void exitScope(NodeTraversal t) {
}
@Override
public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
}
}
private class GatherCallSites extends AbstractPostOrderCallback {
/**
* If the function call returns this and the next statement has the same
* target expression, record the call site.
*/
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (!n.isExprResult()) {
return;
}
Node callNode = n.getFirstChild();
if (!callNode.isCall()) {
return;
}
Node getPropNode = callNode.getFirstChild();
if (!getPropNode.isGetProp()) {
return;
}
Node getPropFirstChildNode = getPropNode.getFirstChild();
Collection definitions =
defFinder.getDefinitionsReferencedAt(getPropNode);
if (definitions == null) {
return;
}
for (Definition definition : definitions) {
Node rValue = definition.getRValue();
if (rValue == null) {
return;
}
if (badFunctionNodes.contains(rValue)) {
return;
}
if (!goodFunctionNodes.contains(rValue)) {
new NodeTraversal(compiler, gatherFunctions, new Es6SyntacticScopeCreator(compiler))
.traverseInnerNode(
rValue, rValue.getParent(), t.getClosestHoistScope().getParent());
if (badFunctionNodes.contains(rValue)) {
return;
}
}
}
Node nextNode = n.getNext();
if (nextNode == null || !nextNode.isExprResult()) {
return;
}
Node nextCallNode = nextNode.getFirstChild();
if (!nextCallNode.isCall()) {
return;
}
Node nextGetPropNode = nextCallNode.getFirstChild();
if (!nextGetPropNode.isGetProp()) {
return;
}
Node nextGetPropFirstChildNode = nextGetPropNode.getFirstChild();
if (!compiler.areNodesEqualForInlining(
nextGetPropFirstChildNode, getPropFirstChildNode)) {
return;
}
if (NodeUtil.mayEffectMutableState(getPropFirstChildNode)) {
return;
}
// We can't chain immediately as it we wouldn't recognize further
// opportunities to chain.
callSites.add(new CallSite(parent, n, callNode, nextGetPropNode,
nextGetPropFirstChildNode));
}
}
/** Records a call site to chain. */
private static class CallSite {
final Node parent;
final Node n;
final Node callNode;
final Node nextGetPropNode;
final Node nextGetPropFirstChildNode;
CallSite(Node parent, Node n, Node callNode, Node nextGetPropNode,
Node nextGetPropFirstChildNode) {
this.parent = parent;
this.n = n;
this.callNode = callNode;
this.nextGetPropNode = nextGetPropNode;
this.nextGetPropFirstChildNode = nextGetPropFirstChildNode;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy