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

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

There is a newer version: 9.0.8
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.jscomp.newtypes.DeclaredTypeRegistry;
import com.google.javascript.jscomp.newtypes.JSType;
import com.google.javascript.jscomp.newtypes.QualifiedName;
import com.google.javascript.rhino.FunctionTypeI;
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 com.google.javascript.rhino.jstype.JSTypeNative;
import com.google.javascript.rhino.jstype.JSTypeRegistry;
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 FunctionTypeI 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.
      FunctionTypeI qmarkCtor =
          childCtor.toBuilder().withUnknownReturnType().withNoParameters().build();
      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, FunctionTypeI 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 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.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 ImmutableCollection 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 ImmutableCollection 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