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. 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.

There is a newer version: v20200830
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.ImmutableCollection;
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.jstype.FunctionType;
import java.util.ArrayList;
import java.util.List;

/**
 * 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 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) { Node subclass = null; Node superclass = callNode.getLastChild(); // There are four possible syntaxes for a class-defining method: // goog.inherits(SubClass, SuperClass) // goog$inherits(SubClass, SuperClass) // goog.mixin(SubClass.prototype, SuperClass.prototype) // goog$mixin(SubClass.prototype, SuperClass.prototype) if (callNode.hasXChildren(3)) { // goog.inherits(SubClass, SuperClass) subclass = callName.getNext(); } else { return null; } if (type == SubclassType.MIXIN) { // Only consider mixins that mix two prototypes as related to // inheritance. if (!endsWithPrototype(superclass)) { return null; } if (!endsWithPrototype(subclass)) { return null; } // Strip off the prototype from the name. subclass = subclass.getFirstChild(); superclass = superclass.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; } @Override public boolean isClassFactoryCall(Node callNode) { return callNode.getFirstChild().matchesQualifiedName("goog.defineClass"); } /** * 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 SubclassType typeofClassDefiningName(Node callName) { // Check if the method name matches one of the class-defining methods. String methodName = null; if (callName.isGetProp()) { methodName = callName.getLastChild().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.getLastChild().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.isString()) { 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"; } @Override public List identifyTypeDeclarationCall(Node n) { Node callName = n.getFirstChild(); if (callName.matchesQualifiedName("goog.addDependency") && n.getChildCount() >= 3) { Node typeArray = callName.getNext().getNext(); if (typeArray.isArrayLit()) { List typeNames = new ArrayList<>(); for (Node name = typeArray.getFirstChild(); name != null; name = name.getNext()) { if (name.isString()) { typeNames.add(name.getString()); } } return typeNames; } } // Identify forward declaration of form goog.forwardDeclare('foo.bar') if (callName.matchesQualifiedName("goog.forwardDeclare") && n.hasTwoChildren()) { Node typeDeclaration = n.getSecondChild(); if (typeDeclaration.isString()) { return ImmutableList.of(typeDeclaration.getString()); } } return super.identifyTypeDeclarationCall(n); } @Override public String getAbstractMethodName() { return "goog.abstractMethod"; } @Override public String getSingletonGetterClassName(Node callNode) { Node callArg = callNode.getFirstChild(); // Use both the original name and the post-CollapseProperties name. if (callNode.hasTwoChildren() && (callArg.matchesQualifiedName("goog.addSingletonGetter") || callArg.matchesQualifiedName("goog$addSingletonGetter"))) { 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 String getGlobalObject() { return "goog.global"; } @Override public boolean isAliasingGlobalThis(Node n) { return CodingConventions.isAliasingGlobalThis(this, n); } private final ImmutableSet propertyTestFunctions = ImmutableSet.of( "goog.isDef", "goog.isNull", "goog.isDefAndNotNull", "goog.isString", "goog.isNumber", "goog.isBoolean", "goog.isFunction", "goog.isArray", "goog.isArrayLike", "goog.isObject"); @Override public boolean isPropertyTestFunction(Node call) { checkArgument(call.isCall()); return propertyTestFunctions.contains( call.getFirstChild().getQualifiedName()) || super.isPropertyTestFunction(call); } @Override public boolean isPropertyRenameFunction(String name) { return super.isPropertyRenameFunction(name) || "goog.reflect.objectProperty".equals(name); } @Override public boolean isFunctionCallThatAlwaysThrows(Node n) { return super.isFunctionCallThatAlwaysThrows(n) || CodingConventions.defaultIsFunctionCallThatAlwaysThrows(n, "goog.asserts.fail"); } @Override public 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 (!(callName.matchesQualifiedName("goog.reflect.object") || callName.matchesQualifiedName("$jscomp.reflectObject")) || !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 boolean isPrivate(String name) { return false; } @Override public ImmutableCollection 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(); } @Override public Bind describeFunctionBind( Node n, boolean callerChecksTypes, boolean iCheckTypes) { if (!n.isCall()) { return null; } Node callTarget = n.getFirstChild(); if (callTarget.isQualifiedName()) { if (callTarget.matchesQualifiedName("goog.bind") || callTarget.matchesQualifiedName("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 (callTarget.matchesQualifiedName("goog.partial") || callTarget.matchesQualifiedName("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 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"), IR.string("reflect"), IR.string("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 ImmutableCollection getIndirectlyDeclaredProperties() { return indirectlyDeclaredProperties; } private static Node safeNext(Node n) { if (n != null) { return n.getNext(); } return null; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy