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

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

Go to download

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.

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

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.annotations.Immutable;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.NominalTypeBuilder;
import com.google.javascript.rhino.QualifiedName;
import com.google.javascript.rhino.jstype.FunctionType;
import java.util.List;
import org.jspecify.nullness.Nullable;

/**
 * This describes the Closure-specific JavaScript coding conventions.
 */
@Immutable
public final class ClosureCodingConvention extends CodingConventions.Proxy {

  private static final long serialVersionUID = 1L;

  static final DiagnosticType OBJECTLIT_EXPECTED = DiagnosticType.warning(
      "JSC_REFLECT_OBJECTLIT_EXPECTED",
      "Object literal expected as second argument");

  private final ImmutableSet indirectlyDeclaredProperties;

  public ClosureCodingConvention() {
    this(CodingConventions.getDefault());
  }

  public ClosureCodingConvention(CodingConvention wrapped) {
    super(wrapped);

    ImmutableSet.Builder props = ImmutableSet.builder();
    props.add(
        "superClass_",
        "instance_",
        "getInstance");
    props.addAll(wrapped.getIndirectlyDeclaredProperties());
    indirectlyDeclaredProperties = props.build();
  }

  /**
   * Closure's goog.inherits adds a {@code superClass_} property to the
   * subclass, and a {@code constructor} property.
   */
  @Override
  public void applySubclassRelationship(
      final NominalTypeBuilder parent, final NominalTypeBuilder child, SubclassType type) {
    super.applySubclassRelationship(parent, child, type);
    if (type == SubclassType.INHERITS) {
      final FunctionType childCtor = child.constructor();
      child.declareConstructorProperty(
          "superClass_", parent.prototypeOrInstance(), childCtor.getSource());
      // Notice that constructor functions do not need to be covariant on the superclass.
      // So if G extends F, new G() and new F() can accept completely different argument
      // types, but G.prototype.constructor needs to be covariant on F.prototype.constructor.
      // To get around this, we just turn off type-checking on arguments and return types
      // of G.prototype.constructor.
      FunctionType qmarkCtor = childCtor.forgetParameterAndReturnTypes();
      child.declarePrototypeProperty("constructor", qmarkCtor, childCtor.getSource());
    }
  }

  /**
   * {@inheritDoc}
   *
   * 

Understands several different inheritance patterns that occur in Google code (various uses * of {@code inherits} and {@code mixin}). */ @Override public @Nullable SubclassRelationship getClassesDefinedByCall(Node callNode) { SubclassRelationship relationship = super.getClassesDefinedByCall(callNode); if (relationship != null) { return relationship; } Node callName = callNode.getFirstChild(); SubclassType type = typeofClassDefiningName(callName); if (type != null) { // Possible formats for a class-defining method call: // goog.inherits(SubClass, SuperClass) // goog$inherits(SubClass, SuperClass) // ValueType.mixin(SubClass, SuperClass, ...) // used by J2CL. // ValueType$mixin(SubClass, SuperClass, ...) if (callNode.getChildCount() < 3) { return null; } // goog.inherits(SubClass, SuperClass) Node subclass = callName.getNext(); Node superclass = subclass.getNext(); if (type == SubclassType.MIXIN) { // Strip off the prototype from the name. if (endsWithPrototype(superclass)) { superclass = superclass.getFirstChild(); } if (endsWithPrototype(subclass)) { subclass = subclass.getFirstChild(); } } // bail out if either of the side of the "inherits" // isn't a real class name. This prevents us from // doing something weird in cases like: // goog.inherits(MySubClass, cond ? SuperClass1 : BaseClass2) if (subclass != null && subclass.isUnscopedQualifiedName() && superclass.isUnscopedQualifiedName()) { return new SubclassRelationship(type, subclass, superclass); } } return null; } private static final QualifiedName GOOG_DEFINECLASS = QualifiedName.of("goog.defineClass"); @Override public boolean isClassFactoryCall(Node callNode) { return GOOG_DEFINECLASS.matches(callNode.getFirstChild()); } /** * Determines whether the given node is a class-defining name, like "inherits" or "mixin." * * @return The type of class-defining name, or null. */ private static @Nullable SubclassType typeofClassDefiningName(Node callName) { // Check if the method name matches one of the class-defining methods. String methodName = null; if (callName.isGetProp()) { methodName = callName.getString(); } else if (callName.isName()) { String name = callName.getString(); int dollarIndex = name.lastIndexOf('$'); if (dollarIndex != -1) { methodName = name.substring(dollarIndex + 1); } } if (methodName != null) { if (methodName.equals("inherits")) { return SubclassType.INHERITS; } else if (methodName.equals("mixin")) { return SubclassType.MIXIN; } } return null; } @Override public boolean isSuperClassReference(String propertyName) { return "superClass_".equals(propertyName) || super.isSuperClassReference(propertyName); } /** * Given a qualified name node, returns whether "prototype" is at the end. * For example: * a.b.c => false * a.b.c.prototype => true */ private static boolean endsWithPrototype(Node qualifiedName) { return qualifiedName.isGetProp() && qualifiedName.getString().equals("prototype"); } /** * @return Whether the node indicates that the file represents a "module", a file whose top level * declarations are not in global scope. */ @Override public boolean extractIsModuleFile(Node node, Node parent) { String namespace = extractClassNameIfGoog(node, parent, "goog.module"); return namespace != null; } /** * Extracts X from goog.provide('X'), if the applied Node is goog. * * @return The extracted class name, or null. */ @Override public String extractClassNameIfProvide(Node node, Node parent) { String namespace = extractClassNameIfGoog(node, parent, "goog.provide"); if (namespace == null) { namespace = extractClassNameIfGoog(node, parent, "goog.module"); } return namespace; } /** * Extracts X from goog.require('X'), if the applied Node is goog. * * @return The extracted class name, or null. */ @Override public String extractClassNameIfRequire(Node node, Node parent) { return extractClassNameIfGoog(node, parent, "goog.require"); } private static String extractClassNameIfGoog(Node node, Node parent, String functionName){ String className = null; if (NodeUtil.isExprCall(parent)) { Node callee = node.getFirstChild(); if (callee != null && callee.isGetProp() && callee.matchesQualifiedName(functionName)) { Node target = callee.getNext(); if (target != null && target.isStringLit()) { className = target.getString(); } } } return className; } /** * Use closure's implementation. * @return closure's function name for exporting properties. */ @Override public String getExportPropertyFunction() { return "goog.exportProperty"; } /** * Use closure's implementation. * @return closure's function name for exporting symbols. */ @Override public String getExportSymbolFunction() { return "goog.exportSymbol"; } private static final QualifiedName GOOG_FORWARDDECLARE = QualifiedName.of("goog.forwardDeclare"); @Override public List identifyTypeDeclarationCall(Node n) { Node callName = n.getFirstChild(); // Identify forward declaration of form goog.forwardDeclare('foo.bar') if (GOOG_FORWARDDECLARE.matches(callName) && n.hasTwoChildren()) { Node typeDeclaration = n.getSecondChild(); if (typeDeclaration.isStringLit()) { return ImmutableList.of(typeDeclaration.getString()); } } return super.identifyTypeDeclarationCall(n); } @Override public String getAbstractMethodName() { return "goog.abstractMethod"; } private static final QualifiedName GOOG_ADDSINGLETONGETTER = QualifiedName.of("goog.addSingletonGetter"); private static final QualifiedName GOOG_ADDSINGLETONGETTER_MANGLED = QualifiedName.of("goog$addSingletonGetter"); @Override public String getSingletonGetterClassName(Node callNode) { Node callArg = callNode.getFirstChild(); // Use both the original name and the post-CollapseProperties name. if (callNode.hasTwoChildren() && (GOOG_ADDSINGLETONGETTER.matches(callArg) || GOOG_ADDSINGLETONGETTER_MANGLED.matches(callArg))) { return callArg.getNext().getQualifiedName(); } return super.getSingletonGetterClassName(callNode); } @Override public void applySingletonGetter(NominalTypeBuilder classType, FunctionType getterType) { Node defSite = classType.constructor().getSource(); classType.declareConstructorProperty("getInstance", getterType, defSite); classType.declareConstructorProperty("instance_", classType.instance(), defSite); } @Override public boolean isPropertyTestFunction(Node call) { checkArgument(call.isCall()); // Avoid building the qualified name and check for // "goog.isArrayLike", "goog.isObject" Node target = call.getFirstChild(); if (target.isGetProp()) { Node src = target.getFirstChild(); String prop = target.getString(); if (src.isName() && src.getString().equals("goog") && (prop.equals("isArrayLike") || prop.equals("isObject"))) { return true; } } return super.isPropertyTestFunction(call); } private static final QualifiedName GOOG_REFLECT_OBJECTPROPERTY = QualifiedName.of("goog.reflect.objectProperty"); private static final QualifiedName GOOG_REFLECT_OBJECTPROPERTY_MANGLED = QualifiedName.of("goog$reflect$objectProperty"); @Override public boolean isPropertyRenameFunction(Node nameNode) { if (super.isPropertyRenameFunction(nameNode)) { return true; } return GOOG_REFLECT_OBJECTPROPERTY.matches(nameNode) || GOOG_REFLECT_OBJECTPROPERTY_MANGLED.matches(nameNode); } @Override public boolean isFunctionCallThatAlwaysThrows(Node n) { return super.isFunctionCallThatAlwaysThrows(n) || CodingConventions.defaultIsFunctionCallThatAlwaysThrows(n, "goog.asserts.fail"); } private static final QualifiedName GOOG_REFLECT_OBJECT = QualifiedName.of("goog.reflect.object"); private static final QualifiedName JSCOMP_REFLECTOBJECT = QualifiedName.of("$jscomp.reflectObject"); @Override public @Nullable ObjectLiteralCast getObjectLiteralCast(Node callNode) { Preconditions.checkArgument(callNode.isCall(), "Expected call node but found %s", callNode); ObjectLiteralCast proxyCast = super.getObjectLiteralCast(callNode); if (proxyCast != null) { return proxyCast; } Node callName = callNode.getFirstChild(); if (!(GOOG_REFLECT_OBJECT.matches(callName) || JSCOMP_REFLECTOBJECT.matches(callName)) || !callNode.hasXChildren(3)) { return null; } Node typeNode = callName.getNext(); if (!typeNode.isQualifiedName()) { return null; } Node objectNode = typeNode.getNext(); if (!objectNode.isObjectLit()) { return new ObjectLiteralCast(null, null, OBJECTLIT_EXPECTED); } return new ObjectLiteralCast(typeNode.getQualifiedName(), typeNode.getNext(), null); } @Override public ImmutableSet getAssertionFunctions() { return ImmutableSet.builder() .addAll(super.getAssertionFunctions()) .add( AssertionFunctionSpec.forTruthy().setFunctionName("goog.asserts.assert").build(), createGoogAssertOnReturn("Array"), createGoogAssertOnReturn("Boolean"), createGoogAssertOnReturn("Element"), createGoogAssertOnReturn("Function"), createGoogAssertOnReturn("Instanceof"), createGoogAssertOnReturn("Number"), createGoogAssertOnReturn("Object"), createGoogAssertOnReturn("String")) .build(); } /** Returns a new assertion function goog.asserts.assert[assertedTypeName] */ private static AssertionFunctionSpec createGoogAssertOnReturn(String assertedTypeName) { return AssertionFunctionSpec.forMatchesReturn() .setFunctionName("goog.asserts.assert" + assertedTypeName) .build(); } private static final QualifiedName GOOG_BIND = QualifiedName.of("goog.bind"); private static final QualifiedName GOOG_PARTIAL = QualifiedName.of("goog.partial"); @Override public @Nullable Bind describeFunctionBind( Node n, boolean callerChecksTypes, boolean iCheckTypes) { if (!n.isCall()) { return null; } Node callTarget = n.getFirstChild(); if (callTarget.isQualifiedName()) { if (GOOG_BIND.matches(callTarget) || callTarget.matchesName("goog$bind")) { // 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 (GOOG_PARTIAL.matches(callTarget) || callTarget.matchesName("goog$partial")) { // goog.partial(fn, args...); Node fn = callTarget.getNext(); if (fn == null) { return null; } Node thisValue = null; Node parameters = safeNext(fn); return new Bind(fn, thisValue, parameters); } } return super.describeFunctionBind(n, callerChecksTypes, iCheckTypes); } @Override public @Nullable Cache describeCachingCall(Node node) { if (!node.isCall()) { return null; } Node callTarget = node.getFirstChild(); if (matchesCacheMethodName(callTarget)) { int paramCount = node.getChildCount() - 1; if (3 <= paramCount && paramCount <= 4) { Node cacheObj = callTarget.getNext(); Node keyNode = cacheObj.getNext(); Node valueFn = keyNode.getNext(); Node keyFn = valueFn.getNext(); return new Cache(cacheObj, keyNode, valueFn, keyFn); } } return super.describeCachingCall(node); } static final Node googCacheReflect = IR.getprop(IR.name("goog"), "reflect", "cache"); private boolean matchesCacheMethodName(Node target) { if (target.isGetProp()) { return target.matchesQualifiedName(googCacheReflect); } else if (target.isName()) { return target.getString().equals("goog$reflect$cache"); } return false; } @Override public ImmutableSet getIndirectlyDeclaredProperties() { return indirectlyDeclaredProperties; } private static @Nullable Node safeNext(Node n) { if (n != null) { return n.getNext(); } return null; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy