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 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.jscomp.newtypes.DeclaredTypeRegistry;
import com.google.javascript.jscomp.newtypes.JSType;
import com.google.javascript.jscomp.newtypes.QualifiedName;
import com.google.javascript.jscomp.newtypes.RawNominalType;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.jstype.FunctionType;
import com.google.javascript.rhino.jstype.JSTypeNative;
import com.google.javascript.rhino.jstype.JSTypeRegistry;
import com.google.javascript.rhino.jstype.ObjectType;
import java.util.ArrayList;
import java.util.Collection;
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(FunctionType parentCtor,
      FunctionType childCtor, SubclassType type) {
    super.applySubclassRelationship(parentCtor, childCtor, type);
    if (type == SubclassType.INHERITS) {
      childCtor.defineDeclaredProperty("superClass_",
          parentCtor.getPrototype(), childCtor.getSource());
      childCtor.getPrototype().defineDeclaredProperty("constructor",
          // 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.
          childCtor.forgetParameterAndReturnTypes(),
          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 six possible syntaxes for a class-defining method: // SubClass.inherits(SuperClass) // goog.inherits(SubClass, SuperClass) // goog$inherits(SubClass, SuperClass) // SubClass.mixin(SuperClass.prototype) // goog.mixin(SubClass.prototype, SuperClass.prototype) // goog$mixin(SubClass.prototype, SuperClass.prototype) boolean isDeprecatedCall = callNode.hasTwoChildren() && callName.isGetProp(); if (isDeprecatedCall) { // SubClass.inherits(SuperClass) subclass = callName.getFirstChild(); } else if (callNode.getChildCount() == 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 (!isDeprecatedCall) { 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 applySingletonGetterOld(FunctionType functionType, FunctionType getterType, ObjectType objectType) { functionType.defineDeclaredProperty("getInstance", getterType, functionType.getSource()); functionType.defineDeclaredProperty("instance_", objectType, functionType.getSource()); } @Override public void applySingletonGetterNew( RawNominalType rawType, JSType getInstanceType, JSType instanceType) { rawType.addCtorProperty("getInstance", null, getInstanceType, true); rawType.addCtorProperty("instance_", null, instanceType, true); } @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) { Preconditions.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 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") || callNode.getChildCount() != 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 Collection getAssertionFunctions() { return ImmutableList.of( new AssertionFunctionSpec("goog.asserts.assert", JSTypeNative.TRUTHY), new AssertionFunctionSpec("goog.asserts.assertNumber", JSTypeNative.NUMBER_TYPE), new AssertionFunctionSpec("goog.asserts.assertString", JSTypeNative.STRING_TYPE), new AssertionFunctionSpec("goog.asserts.assertObject", JSTypeNative.OBJECT_TYPE), new AssertFunctionByTypeName("goog.asserts.assertFunction", "Function"), new AssertFunctionByTypeName("goog.asserts.assertArray", "Array"), new AssertFunctionByTypeName("goog.asserts.assertElement", "Element"), new AssertInstanceofSpec("goog.asserts.assertInstanceof") ); } @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 Collection getIndirectlyDeclaredProperties() { return indirectlyDeclaredProperties; } private static Node safeNext(Node n) { if (n != null) { return n.getNext(); } return null; } /** * A function that will throw an exception when if the value is not * an instanceof a specific type. */ public static class AssertInstanceofSpec extends AssertionFunctionSpec { public AssertInstanceofSpec(String functionName) { super(functionName, JSTypeNative.OBJECT_TYPE); } /** * Returns the type for a type assertion, or null if the function asserts * that the node must not be null or undefined. */ @Override public com.google.javascript.rhino.jstype.JSType getAssertedOldType(Node call, JSTypeRegistry registry) { if (call.getChildCount() > 2) { Node constructor = call.getSecondChild().getNext(); if (constructor != null) { com.google.javascript.rhino.jstype.JSType ownerType = constructor.getJSType(); if (ownerType != null && ownerType.isFunctionType() && ownerType.isConstructor()) { FunctionType functionType = ((FunctionType) ownerType); return functionType.getInstanceType(); } } } return registry.getNativeType(JSTypeNative.UNKNOWN_TYPE); } @Override public JSType getAssertedNewType(Node call, DeclaredTypeRegistry scope) { if (call.getChildCount() > 2) { Node constructor = call.getSecondChild().getNext(); if (constructor != null && constructor.isQualifiedName()) { QualifiedName qname = QualifiedName.fromNode(constructor); JSType functionType = scope.getDeclaredTypeOf(qname.getLeftmostName()); if (functionType != null) { if (!qname.isIdentifier()) { functionType = functionType.getProp(qname.getAllButLeftmost()); } com.google.javascript.jscomp.newtypes.FunctionType ctorType = functionType == null ? null : functionType.getFunTypeIfSingletonObj(); if (ctorType != null && ctorType.isUniqueConstructor()) { return ctorType.getInstanceTypeOfCtor(); } } } } return scope.getCommonTypes().UNKNOWN; } } /** * A function that will throw an exception when the value is not an * instanceof the given type name, for instance "Element". */ public static class AssertFunctionByTypeName extends AssertionFunctionSpec { private final String typeName; public AssertFunctionByTypeName(String functionName, String typeName) { super(functionName, null); this.typeName = typeName; } @Override public com.google.javascript.rhino.jstype.JSType getAssertedOldType(Node call, JSTypeRegistry registry) { return registry.getType(typeName); } @Override public JSType getAssertedNewType(Node call, DeclaredTypeRegistry scope) { JSType result = scope.getDeclaredTypeOf(typeName) .getFunTypeIfSingletonObj().getInstanceTypeOfCtor(); return result; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy