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

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

/*
 * 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);
        }
      }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy