com.google.javascript.jscomp.MethodCompilerPass Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of com.liferay.frontend.js.minifier
Show all versions of com.liferay.frontend.js.minifier
Liferay Frontend JS Minifier
/*
* 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 com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.rhino.Node;
import java.util.HashSet;
import java.util.Set;
import javax.annotation.Nullable;
/**
* Finds all method declarations and pulls them into data structures
* for use during cleanups such as arity checks or inlining.
*
*/
abstract class MethodCompilerPass implements CompilerPass {
/** List of methods defined in externs */
final Set externMethods = new HashSet<>();
/** List of extern methods without signatures that we can't warn about */
final Set externMethodsWithoutSignatures = new HashSet<>();
/** List of property names that may not be methods */
final Set nonMethodProperties = new HashSet<>();
// Use a linked map here to keep the output deterministic. Otherwise,
// the choice of method bodies is random when multiple identical definitions
// are found which causes problems in the source maps.
final Multimap methodDefinitions = LinkedHashMultimap.create();
final AbstractCompiler compiler;
/**
* The signature storage is provided by the implementing class.
*/
interface SignatureStore {
public void reset();
public void addSignature(
String functionName, Node functionNode, String sourceFile);
public void removeSignature(String functionName);
}
MethodCompilerPass(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void process(Node externs, Node root) {
externMethods.clear();
externMethodsWithoutSignatures.clear();
getSignatureStore().reset();
methodDefinitions.clear();
if (externs != null) {
NodeTraversal.traverse(compiler, externs, new GetExternMethods());
}
NodeTraversal.traverseRoots(compiler, new GatherSignatures(), externs, root);
NodeTraversal.traverseRoots(compiler, getActingCallback(), externs, root);
}
/**
* Subclasses should return a callback that does the actual work they want to perform given the
* computed list of method signatures
*/
abstract NodeTraversal.Callback getActingCallback();
/**
* Subclasses should return a SignatureStore for storing discovered
* signatures.
*/
abstract SignatureStore getSignatureStore();
/**
* Adds a node that may represent a function signature (if it's a function
* itself or the name of a function).
*/
private void addPossibleSignature(String name, Node node, NodeTraversal t) {
if (node != null && node.isFunction()) {
// The node we're looking at is a function, so we can add it directly
addSignature(name, node, t.getSourceName());
} else {
nonMethodProperties.add(name);
}
}
private void addSignature(String name, Node function, String fnSourceName) {
if (externMethodsWithoutSignatures.contains(name)) {
return;
}
getSignatureStore().addSignature(name, function, fnSourceName);
methodDefinitions.put(name, function);
}
/**
* Gathers methods from the externs file. Methods that are listed there but
* do not have a signature are flagged to be ignored when doing arity checks.
* Methods that do include signatures will be checked.
*/
private class GetExternMethods extends AbstractPostOrderCallback {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getToken()) {
case GETPROP:
case GETELEM:
{
String name = getPropName(n);
if (name == null) {
return;
}
// We have a signature. Parse tree of the form:
// assign <- parent
// getprop <- n
// name methods
// string setTimeout
// function
if (parent.isAssign() && n.isFirstChildOf(parent) && n.getNext().isFunction()) {
addSignature(name, n.getNext(), t.getSourceName());
} else {
getSignatureStore().removeSignature(name);
externMethodsWithoutSignatures.add(name);
}
externMethods.add(name);
}
break;
case CLASS_MEMBERS:
case OBJECTLIT: {
for (Node key = n.getFirstChild(); key != null; key = key.getNext()) {
Node value = key.getFirstChild();
String name = key.getString();
if (key.isStringKey() && value.isFunction()) {
addSignature(name, value, t.getSourceName());
} else {
getSignatureStore().removeSignature(name);
externMethodsWithoutSignatures.add(name);
}
externMethods.add(name);
}
} break;
default:
break;
}
}
}
/**
* Gather signatures from the source to be compiled.
*/
private class GatherSignatures extends AbstractPostOrderCallback {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getToken()) {
case GETPROP:
case GETELEM:
String name = getPropName(n);
if (name == null) {
return;
}
if (name.equals("prototype")) {
processPrototypeParent(t, parent);
} else {
// Static methods of the form Foo.bar = function() {} or
// Static methods of the form Foo.bar = baz (where baz is a
// function name). Parse tree looks like:
// assign <- parent
// getprop <- n
// name Foo
// string bar
// function or name <- n.getNext()
if (parent.isAssign() && n.isFirstChildOf(parent)) {
addPossibleSignature(name, n.getNext(), t);
}
}
break;
case OBJECTLIT:
case CLASS_MEMBERS:
for (Node key = n.getFirstChild(); key != null; key = key.getNext()) {
switch (key.getToken()) {
case MEMBER_FUNCTION_DEF:
case MEMBER_FIELD_DEF:
case STRING_KEY:
addPossibleSignature(key.getString(), key.getFirstChild(), t);
break;
case SETTER_DEF:
case GETTER_DEF:
nonMethodProperties.add(key.getString());
break;
case COMPUTED_PROP: // complicated
case OBJECT_SPREAD:
case COMPUTED_FIELD_DEF:
break;
default:
throw new IllegalStateException("Unexpected " + n.getToken() + " key: " + key);
}
}
break;
default:
break;
}
}
/**
* Processes the parent of a GETPROP prototype, which can either be
* another GETPROP (in the case of Foo.prototype.bar), or can be
* an assignment (in the case of Foo.prototype = ...).
*/
private void processPrototypeParent(NodeTraversal t, Node n) {
switch (n.getToken()) {
// Foo.prototype.getBar = function() { ... } or
// Foo.prototype.getBar = getBaz (where getBaz is a function)
// parse tree looks like:
// assign <- parent
// getprop <- n
// getprop
// name Foo
// string prototype
// string getBar
// function or name <- assignee
case GETPROP:
case GETELEM:
Node grandparent = n.getGrandparent();
String name = getPropName(n);
if (name != null && grandparent.isAssign()) {
Node assignee = grandparent.getSecondChild();
addPossibleSignature(name, assignee, t);
}
break;
default:
break;
}
}
}
@Nullable
private static String getPropName(Node getPropElem) {
if (getPropElem.isGetProp()) {
return getPropElem.getString();
} else if (getPropElem.getSecondChild().isStringLit()) {
return getPropElem.getSecondChild().getString();
} else {
return null;
}
}
}