com.google.javascript.jscomp.AnonymousFunctionNamingCallback Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of closure-compiler-linter Show documentation
Show all versions of closure-compiler-linter Show documentation
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.
/*
* Copyright 2004 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.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
/**
* Visitor that performs naming operations on anonymous function by
* means of the FunctionNamer interface. Anonymous functions are
* named based on context. For example, the anonymous function below
* would be given a name generated from goog.string.htmlEscape by the FunctionNamer.
*
* goog.string.htmlEscape = function(str) {
* }
*
* This pass does not try to name FUNCTIONs with empty NAME nodes if doing so would violate AST
* validity. Currently, we can never name arrow functions, which must stay anonymous, or getters,
* setters, and member function definitions, which have a name elsewhere in the AST.
*/
class AnonymousFunctionNamingCallback
extends AbstractPostOrderCallback {
private final FunctionNamer namer;
/**
* Interface used by AnonymousFunctionNamingCallback to set the name
* of anonymous functions.
*/
interface FunctionNamer {
/**
* Generates a string representation of a node for use by
* setFunctionName.
*/
String getName(Node node);
/**
* Sets the name of an anonymous function. Will only ever be called if the fnNode can be named
* without making the AST invalid.
* @param fnNode The function node to update
* @param name The name
*/
void setFunctionName(String name, Node fnNode);
/**
* Generate a name by "concatenating" the output of multiple calls
* to getName.
*/
String getCombinedName(String lhs, String rhs);
}
AnonymousFunctionNamingCallback(FunctionNamer namer) {
this.namer = namer;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getToken()) {
case FUNCTION:
// this handles functions that are assigned to variables or
// properties
// e.g. goog.string.htmlEscape = function(str) {
// }
// get the function name and see if it's empty
Node functionNameNode = n.getFirstChild();
String functionName = functionNameNode.getString();
if (functionName.isEmpty() && !n.isArrowFunction()) {
if (parent.isAssign() || parent.isDefaultValue()) {
// this is an assignment to a property, generally either a
// static function or a prototype function, or a potential assignment of
// a default value
// e.g. goog.string.htmlEscape = function() { }
// goog.structs.Map.prototype.getCount = function() { } or
// function f(g = function() {}) { }
Node lhs = parent.getFirstChild();
String name = namer.getName(lhs);
namer.setFunctionName(name, n);
} else if (parent.isName()) {
// this is an assignment to a variable
// e.g. var handler = function() {}
String name = namer.getName(parent);
namer.setFunctionName(name, n);
}
}
break;
case ASSIGN:
// this handles functions that are assigned to a prototype through
// an object literal
// e.g. BuzzApp.prototype = {
// Start : function() { }
// }
Node lhs = n.getFirstChild();
Node rhs = lhs.getNext();
if (rhs.isObjectLit()) {
nameObjectLiteralMethods(rhs, namer.getName(lhs));
}
break;
default:
break;
}
}
private void nameObjectLiteralMethods(Node objectLiteral, String context) {
for (Node keyNode = objectLiteral.getFirstChild();
keyNode != null;
keyNode = keyNode.getNext()) {
// Object literal keys may be STRING_KEY, GETTER_DEF, SETTER_DEF,
// MEMBER_FUNCTION_DEF (Shorthand function definition) or COMPUTED_PROP.
// Getters, setters, and member function defs are skipped because their FUNCTION nodes must
// have empty NAME nodes (currently enforced by CodeGenerator).
if (keyNode.isStringKey() || keyNode.isComputedProp()) {
// concatenate the context and key name to get a new qualified name.
String name = namer.getCombinedName(context, namer.getName(keyNode));
// computed property has 2 children -- index expression and value expression
Node valueNode = keyNode.isStringKey() ? keyNode.getOnlyChild() : keyNode.getLastChild();
Token type = valueNode.getToken();
if (type == Token.FUNCTION && !valueNode.isArrowFunction()) {
// set name if function is anonymous
Node functionNameNode = valueNode.getFirstChild();
String functionName = functionNameNode.getString();
if (functionName.isEmpty()) {
namer.setFunctionName(name, valueNode);
}
} else if (type == Token.OBJECTLIT) {
// process nested object literal
nameObjectLiteralMethods(valueNode, name);
}
}
}
}
}