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

com.google.javascript.jscomp.ijs.ClassUtil Maven / Gradle / Ivy

/*
 * Copyright 2017 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.ijs;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Strings.isNullOrEmpty;

import com.google.javascript.jscomp.NodeUtil;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.QualifiedName;
import org.jspecify.nullness.Nullable;

/**
 * Static utility methods for dealing with classes. The primary benefit is for papering over the
 * differences between ES6 class and goog.defineClass syntax.
 */
final class ClassUtil {
  private ClassUtil() {}

  private static final QualifiedName GOOG_DEFINECLASS = QualifiedName.of("goog.defineClass");

  /**
   * Return whether the given node represents a GETPROP with a first child THIS inside a named
   * class.
   */
  static boolean isThisPropInsideClassWithName(Node maybeGetProp) {
    return getClassNameOfThisProp(maybeGetProp) != null;
  }

  /**
   * Return the fully qualified name of a this.property inside a constructor. This method called
   * should only be called if `isThisPropInsideClassWithName` returns true.
   */
  static String getFullyQualifiedNameOfThisProp(Node getProp) {
    checkArgument(isThisPropInsideClassWithName(getProp));
    String className = getClassNameOfThisProp(getProp);
    return className + ".prototype." + getProp.getString();
  }

  private static @Nullable String getClassNameOfThisProp(Node getprop) {
    if (!getprop.isGetProp() || !getprop.getFirstChild().isThis()) {
      return null;
    }
    Node function = NodeUtil.getEnclosingFunction(getprop);
    if (function == null) {
      return null;
    }
    String className = getMemberFunctionClassName(function);
    if (isNullOrEmpty(className)) {
      return null;
    }
    return className;
  }

  /**
   * Return whether the given node represents a MEMBER_FIELD_DEF that is inside a class with a name.
   */
  static boolean isMemberFieldDefInsideClassWithName(Node fieldNode) {
    return getMemberFieldDefClassName(fieldNode) != null;
  }

  /**
   * Return the fully qualified name of a MEMBER_FIELD_DEF. It is invalid to call this method for a
   * field that belongs to a nameless class.
   */
  static String getFullyQualifiedNameOfMemberFieldDef(Node fieldNode) {
    checkArgument(isMemberFieldDefInsideClassWithName(fieldNode));
    String className = getMemberFieldDefClassName(fieldNode);
    return fieldNode.isStaticMember()
        ? className + "." + fieldNode.getString()
        : className + ".prototype." + fieldNode.getString();
  }

  private static @Nullable String getMemberFieldDefClassName(Node fieldNode) {
    checkArgument(fieldNode.isMemberFieldDef());
    Node classNode = fieldNode.getGrandparent();
    checkState(classNode.isClass());
    return NodeUtil.getName(classNode);
  }

  static String getFullyQualifiedNameOfMethod(Node function) {
    checkArgument(isClassMethod(function));
    String className = getMemberFunctionClassName(function);
    checkState(className != null && !className.isEmpty());
    Node memberFunctionDef = function.getParent();
    String methodName = memberFunctionDef.getString();
    return memberFunctionDef.isStaticMember()
        ? className + "." + methodName
        : className + ".prototype." + methodName;
  }

  static boolean isClassMethod(Node functionNode) {
    checkArgument(functionNode.isFunction());
    Node parent = functionNode.getParent();
    if (parent.isMemberFunctionDef() && parent.getParent().isClassMembers()) {
      // ES6 class
      return true;
    }
    // goog.defineClass
    return parent.isStringKey()
        && parent.getParent().isObjectLit()
        && parent.getGrandparent().isCall()
        && GOOG_DEFINECLASS.matches(parent.getGrandparent().getFirstChild());
  }

  /**
   * Checks whether the given constructor/member function belongs to a named class, as opposed to an
   * anonymous class.
   */
  static boolean hasNamedClass(Node functionNode) {
    checkArgument(functionNode.isFunction());
    return getMemberFunctionClassName(functionNode) != null;
  }

  private static String getMemberFunctionClassName(Node functionNode) {
    checkArgument(functionNode.isFunction());
    if (isClassMethod(functionNode)) {
      Node parent = functionNode.getParent();
      if (parent.isMemberFunctionDef()) {
        // ES6 class
        Node classNode = functionNode.getGrandparent().getParent();
        checkState(classNode.isClass());
        return NodeUtil.getName(classNode);
      }
      // goog.defineClass
      checkState(parent.isStringKey());
      Node defineClassCall = parent.getGrandparent();
      checkState(defineClassCall.isCall());
      return NodeUtil.getBestLValue(defineClassCall).getQualifiedName();
    }
    return NodeUtil.getName(functionNode);
  }

  static boolean isConstructor(Node functionNode) {
    if (isClassMethod(functionNode)) {
      return NodeUtil.isEs6Constructor(functionNode)
          ||
          // TODO(b/124020008): Delete this case when `goog.defineClass` is dropped.
          "constructor".equals(functionNode.getParent().getString());
    }
    JSDocInfo jsdoc = NodeUtil.getBestJSDocInfo(functionNode);
    return jsdoc != null && jsdoc.isConstructorOrInterface();
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy