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

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

There is a newer version: 9.0.8
Show newest version
/*
 * Copyright 2011 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 static com.google.common.base.Preconditions.checkState;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.annotations.Immutable;
import com.google.javascript.rhino.ClosurePrimitive;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.NominalTypeBuilder;
import com.google.javascript.rhino.QualifiedName;
import com.google.javascript.rhino.StaticSourceFile;
import com.google.javascript.rhino.jstype.FunctionType;
import com.google.javascript.rhino.jstype.JSTypeRegistry;
import com.google.javascript.rhino.jstype.ObjectType;
import java.util.Collection;
import java.util.List;
import java.util.Map;

/**
 * Helper classes for dealing with coding conventions.
 */
public final class CodingConventions {

  private CodingConventions() {}

  /** Gets the default coding convention. */
  public static CodingConvention getDefault() {
    return new DefaultCodingConvention();
  }

  /**
   * @param n The last statement of a block to check for an always throws
   *     function call. Used by CheckMissingReturn.
   * @param alwaysThrowsFunctionName The name of a function that always throws.
   * @return {@code true} if n is call to alwaysThrowsFunctionName, otherwise
   *     {@code false}.
   */
  public static boolean defaultIsFunctionCallThatAlwaysThrows(
      Node n, String alwaysThrowsFunctionName) {
    if (n.isExprResult()) {
      if (!n.getFirstChild().isCall()) {
        return false;
      }
    } else if (!n.isCall()) {
      return false;
    }
    if (n.isExprResult()) {
      n = n.getFirstChild();
    }
    // n is a call
    return n.getFirstChild().matchesQualifiedName(alwaysThrowsFunctionName);
  }

  /**
   * A convention that wraps another.
   *
   * When you want to support a new library, you should subclass this
   * delegate, and override the methods that you want to customize.
   *
   * This way, a person using jQuery and Closure Library can create a new
   * coding convention by creating a jQueryCodingConvention that delegates
   * to a ClosureCodingConvention that delegates to a DefaultCodingConvention.
   */
  @Immutable
  public static class Proxy implements CodingConvention {

    protected final CodingConvention nextConvention;

    protected Proxy(CodingConvention convention) {
      this.nextConvention = convention;
    }

    @Override
    public boolean isConstant(String variableName) {
      return nextConvention.isConstant(variableName);
    }

    @Override public boolean isConstantKey(String keyName) {
      return nextConvention.isConstantKey(keyName);
    }

    @Override
    public boolean isValidEnumKey(String key) {
      return nextConvention.isValidEnumKey(key);
    }

    @Override
    public boolean isOptionalParameter(Node parameter) {
      return nextConvention.isOptionalParameter(parameter);
    }

    @Override
    public boolean isVarArgsParameter(Node parameter) {
      return nextConvention.isVarArgsParameter(parameter);
    }

    @Override
    public boolean isFunctionCallThatAlwaysThrows(Node n) {
      return nextConvention.isFunctionCallThatAlwaysThrows(n);
    }

    @Override
    public boolean isExported(String name, boolean local) {
      return nextConvention.isExported(name, local);
    }

    @Override
    public final boolean isExported(String name) {
      return CodingConvention.super.isExported(name);
    }

    @Override
    public String getPackageName(StaticSourceFile source) {
      return nextConvention.getPackageName(source);
    }

    @Override
    public SubclassRelationship getClassesDefinedByCall(Node callNode) {
      return nextConvention.getClassesDefinedByCall(callNode);
    }

    @Override
    public boolean isClassFactoryCall(Node callNode) {
      return nextConvention.isClassFactoryCall(callNode);
    }

    @Override
    public boolean isSuperClassReference(String propertyName) {
      return nextConvention.isSuperClassReference(propertyName);
    }

    @Override
    public boolean extractIsModuleFile(Node node, Node parent) {
      return nextConvention.extractIsModuleFile(node, parent);
    }

    @Override
    public String extractClassNameIfProvide(Node node, Node parent) {
      return nextConvention.extractClassNameIfProvide(node, parent);
    }

    @Override
    public String extractClassNameIfRequire(Node node, Node parent) {
      return nextConvention.extractClassNameIfRequire(node, parent);
    }

    @Override
    public String getExportPropertyFunction() {
      return nextConvention.getExportPropertyFunction();
    }

    @Override
    public String getExportSymbolFunction() {
      return nextConvention.getExportSymbolFunction();
    }

    @Override
    public List identifyTypeDeclarationCall(Node n) {
      return nextConvention.identifyTypeDeclarationCall(n);
    }

    @Override
    public void applySubclassRelationship(
        NominalTypeBuilder parent, NominalTypeBuilder child, SubclassType type) {
      nextConvention.applySubclassRelationship(parent, child, type);
    }

    @Override
    public String getAbstractMethodName() {
      return nextConvention.getAbstractMethodName();
    }

    @Override
    public String getSingletonGetterClassName(Node callNode) {
      return nextConvention.getSingletonGetterClassName(callNode);
    }

    @Override
    public void applySingletonGetter(
        NominalTypeBuilder classType, FunctionType getterType) {
      nextConvention.applySingletonGetter(classType, getterType);
    }

    @Override
    public boolean isInlinableFunction(Node n) {
      return nextConvention.isInlinableFunction(n);
    }

    @Override
    public DelegateRelationship getDelegateRelationship(Node callNode) {
      return nextConvention.getDelegateRelationship(callNode);
    }

    @Override
    public void applyDelegateRelationship(
        NominalTypeBuilder delegateSuperclass,
        NominalTypeBuilder delegateBase,
        NominalTypeBuilder delegator,
        ObjectType delegateProxy,
        FunctionType findDelegate) {
      nextConvention.applyDelegateRelationship(
          delegateSuperclass, delegateBase, delegator, delegateProxy, findDelegate);
    }

    @Override
    public String getDelegateSuperclassName() {
      return nextConvention.getDelegateSuperclassName();
    }

    @Override
    public void checkForCallingConventionDefinitions(
        Node n, Map delegateCallingConventions) {
      nextConvention.checkForCallingConventionDefinitions(
          n, delegateCallingConventions);
    }

    @Override
    public void defineDelegateProxyPrototypeProperties(
        JSTypeRegistry registry,
        List delegateProxies,
        Map delegateCallingConventions) {
      nextConvention.defineDelegateProxyPrototypeProperties(
          registry, delegateProxies, delegateCallingConventions);
    }

    @Override
    public Collection getAssertionFunctions() {
      return nextConvention.getAssertionFunctions();
    }

    @Override
    public Bind describeFunctionBind(Node n) {
      return describeFunctionBind(n, false, false);
    }

    @Override
    public Bind describeFunctionBind(
        Node n, boolean callerChecksTypes, boolean iCheckTypes) {
      return nextConvention
          .describeFunctionBind(n, callerChecksTypes, iCheckTypes);
    }

    @Override
    public Cache describeCachingCall(Node node) {
      return nextConvention.describeCachingCall(node);
    }

    @Override
    public boolean isPropertyTestFunction(Node call) {
      return nextConvention.isPropertyTestFunction(call);
    }

    @Override
    public boolean isPropertyRenameFunction(Node nameNode) {
      return nextConvention.isPropertyRenameFunction(nameNode);
    }

    @Override
    public boolean isPrototypeAlias(Node getProp) {
      return false;
    }

    @Override
    public ObjectLiteralCast getObjectLiteralCast(Node callNode) {
      return nextConvention.getObjectLiteralCast(callNode);
    }

    @Override
    public Collection getIndirectlyDeclaredProperties() {
      return nextConvention.getIndirectlyDeclaredProperties();
    }
  }


  /**
   * The default coding convention.
   * Should be at the bottom of all proxy chains.
   */
  @Immutable
  private static class DefaultCodingConvention implements CodingConvention {

    private static final QualifiedName JSCOMP_REFLECT_PROPERTY =
        QualifiedName.of("$jscomp.reflectProperty");

    private static final long serialVersionUID = 1L;

    @Override
    public boolean isConstant(String variableName) {
      return false;
    }

    @Override
    public boolean isConstantKey(String variableName) {
      return false;
    }

    @Override
    public boolean isValidEnumKey(String key) {
      return key != null && key.length() > 0;
    }

    @Override
    public boolean isOptionalParameter(Node parameter) {
      return false;
    }

    @Override
    public boolean isVarArgsParameter(Node parameter) {
      // be as lax as possible
      return parameter.isRest();
    }

    @Override
    public boolean isFunctionCallThatAlwaysThrows(Node n) {
      if (NodeUtil.isExprCall(n)) {
        FunctionType fnType = FunctionType.toMaybeFunctionType(n.getFirstFirstChild().getJSType());
        return fnType != null && ClosurePrimitive.ASSERTS_FAIL == fnType.getClosurePrimitive();
      }
      return false;
    }

    @Override
    public String getPackageName(StaticSourceFile source) {
      // The package name of a source file is its file path.
      String name = source.getName();
      int lastSlash = name.lastIndexOf('/');
      return lastSlash == -1 ? "" : name.substring(0, lastSlash);
    }

    @Override
    public boolean isExported(String name, boolean local) {
      return local && name.startsWith("$super");
    }

    @Override
    public final boolean isExported(String name) {
      return CodingConvention.super.isExported(name);
    }

    private static final QualifiedName JSCOMP_INHERITS = QualifiedName.of("$jscomp.inherits");

    @Override
    public SubclassRelationship getClassesDefinedByCall(Node callNode) {
      Node callName = callNode.getFirstChild();
      if ((JSCOMP_INHERITS.matches(callName) || callName.matchesName("$jscomp$inherits"))
          && callNode.hasXChildren(3)) {
        Node subclass = callName.getNext();
        Node superclass = subclass.getNext();
        // The StripCode pass may create $jscomp.inherits calls with NULL arguments.
        if (subclass.isQualifiedName() && superclass.isQualifiedName()) {
          return new SubclassRelationship(SubclassType.INHERITS, subclass, superclass);
        }
      }
      return null;
    }

    @Override
    public boolean isClassFactoryCall(Node callNode) {
      return false;
    }

    @Override
    public boolean isSuperClassReference(String propertyName) {
      return false;
    }

    @Override
    public boolean extractIsModuleFile(Node node, Node parent) {
      String message = "only implemented in ClosureCodingConvention";
      throw new UnsupportedOperationException(message);
    }

    @Override
    public String extractClassNameIfProvide(Node node, Node parent) {
      String message = "only implemented in ClosureCodingConvention";
      throw new UnsupportedOperationException(message);
    }

    @Override
    public String extractClassNameIfRequire(Node node, Node parent) {
      String message = "only implemented in ClosureCodingConvention";
      throw new UnsupportedOperationException(message);
    }

    @Override
    public String getExportPropertyFunction() {
      return null;
    }

    @Override
    public String getExportSymbolFunction() {
      return null;
    }

    @Override
    public List identifyTypeDeclarationCall(Node n) {
      return null;
    }

    @Override
    public void applySubclassRelationship(
        NominalTypeBuilder parent, NominalTypeBuilder child, SubclassType type) {
      // do nothing
    }

    @Override
    public String getAbstractMethodName() {
      return null;
    }

    @Override
    public String getSingletonGetterClassName(Node callNode) {
      return null;
    }

    @Override
    public void applySingletonGetter(
        NominalTypeBuilder classType, FunctionType getterType) {
      // do nothing.
    }

    @Override
    public boolean isInlinableFunction(Node n) {
      checkState(n.isFunction(), n);
      return true;
    }

    @Override
    public DelegateRelationship getDelegateRelationship(Node callNode) {
      return null;
    }

    @Override
    public void applyDelegateRelationship(
        NominalTypeBuilder delegateSuperclass,
        NominalTypeBuilder delegateBase,
        NominalTypeBuilder delegator,
        ObjectType delegateProxy,
        FunctionType findDelegate) {
      // do nothing.
    }

    @Override
    public String getDelegateSuperclassName() {
      return null;
    }

    @Override
    public void checkForCallingConventionDefinitions(Node n,
        Map delegateCallingConventions) {
      // do nothing.
    }

    @Override
    public void defineDelegateProxyPrototypeProperties(
        JSTypeRegistry registry,
        List delegateProxies,
        Map delegateCallingConventions) {
      // do nothing.
    }

    @Override
    public boolean isPropertyTestFunction(Node call) {
      // Avoid building the qualified name and check for
      // "goog.isArray"
      Node target = call.getFirstChild();
      if (target.isGetProp()) {
        Node src = target.getFirstChild();
        String prop = target.getString();
        if (src.isName() && src.getString().equals("Array") && prop.equals("isArray")) {
          return true;
        }
      }
      return false;
    }

    @Override
    public boolean isPropertyRenameFunction(Node nameNode) {
      return nameNode.matchesName(NodeUtil.JSC_PROPERTY_NAME_FN)
          || JSCOMP_REFLECT_PROPERTY.matches(nameNode)
          || nameNode.matchesName("$jscomp$reflectProperty");
    }

    @Override
    public boolean isPrototypeAlias(Node getProp) {
      return false;
    }

    @Override
    public ObjectLiteralCast getObjectLiteralCast(Node callNode) {
      return null;
    }

    @Override
    public ImmutableSet getAssertionFunctions() {
      return ImmutableSet.of(
          AssertionFunctionSpec.forTruthy()
              .setClosurePrimitive(ClosurePrimitive.ASSERTS_TRUTHY)
              .build(),
          AssertionFunctionSpec.forMatchesReturn()
              .setClosurePrimitive(ClosurePrimitive.ASSERTS_MATCHES_RETURN)
              .build());
    }

    @Override
    public Bind describeFunctionBind(Node n) {
      return describeFunctionBind(n, false, false);
    }

    private static final QualifiedName FUNCTION_PROTOTYPE_BIND_CALL =
        QualifiedName.of("Function.prototype.bind.call");

    @Override
    public Bind describeFunctionBind(
        Node n, boolean callerChecksTypes, boolean iCheckTypes) {
      if (!n.isCall()) {
        return null;
      }

      Node callTarget = n.getFirstChild();
      if (callTarget.isQualifiedName()) {
        if (FUNCTION_PROTOTYPE_BIND_CALL.matches(callTarget)) {
          // goog.bind(fn, self, args...);
          Node fn = callTarget.getNext();
          if (fn == null) {
            return null;
          }
          Node thisValue = safeNext(fn);
          Node parameters = safeNext(thisValue);
          return new Bind(fn, thisValue, parameters);
        }
      }

      if (callTarget.isGetProp() && callTarget.getString().equals("bind")) {
        Node maybeFn = callTarget.getFirstChild();
        com.google.javascript.rhino.jstype.JSType maybeFnType =
            maybeFn.getJSType();
        FunctionType fnType = null;
        if (iCheckTypes && maybeFnType != null) {
          fnType = maybeFnType.restrictByNotNullOrUndefined()
              .toMaybeFunctionType();
        }

        if (fnType != null || callerChecksTypes || maybeFn.isFunction()) {
          // (function(){}).bind(self, args...);
          Node thisValue = callTarget.getNext();
          Node parameters = safeNext(thisValue);
          return new Bind(maybeFn, thisValue, parameters);
        }
      }

      return null;
    }

    @Override
    public Cache describeCachingCall(Node node) {
      return null;
    }

    @Override
    public Collection getIndirectlyDeclaredProperties() {
      return ImmutableList.of();
    }

    private static Node safeNext(Node n) {
      if (n != null) {
        return n.getNext();
      }
      return null;
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy