@Test
  public void canUseModuleInternalTypedefsInJsDoc() {
    CompilerUtil compiler = createCompiler(path("foo.js"));

    compiler.compile(
        createSourceFile(
            path("foo.js"),
            "/** @typedef {{x: number}} */",
            "var Variable;",
            "",
            "/**",
            " * @param {Variable} a .",
            " * @param {Variable} b .",
            " * @return {Variable} .",
            " */",
            "exports.add = function(a, b) {",
            "  return {x: a.x + b.x};",
            "};"));

    Scope scope = compiler.getCompiler().getTopScope();
    Var var = scope.getVar("module$foo");
    JSType type = var.getInitialValue().getJSType().toObjectType().getPropertyType("add");
    assertTrue(type.isFunctionType());

    JSDocInfo info = type.getJSDocInfo();
    Node node = info.getTypeNodes().iterator().next();
    assertTrue(node.isString());
    assertEquals("$jscomp.scope.Variable", node.getString());
    assertEquals("Variable", node.getProp(Node.ORIGINALNAME_PROP));
  }
  @Test
  public void exportedInternalVarInheritsJsDocInfo() {
    CompilerUtil compiler = createCompiler(path("foo.js"));

    compiler.compile(
        createSourceFile(
            path("foo.js"),
            "/**",
            " * @constructor",
            " */",
            "var Greeter = function(){};",
            "/**",
            " * @param {string} name .",
            " * @return {string} .",
            " */",
            "Greeter.prototype.sayHi = function(name) {",
            "  return 'Hello, ' + name;",
            "};",
            "",
            "exports.Greeter = Greeter"));

    JSType exportedGreeter =
        compiler
            .getCompiler()
            .getTopScope()
            .getVar("module$foo")
            .getType()
            .findPropertyType("Greeter");
    assertTrue(exportedGreeter.isConstructor());
  }
  @Test
  public void savesOriginalTypeNameInJsDoc() {
    CompilerUtil compiler = createCompiler(path("foo.js"));

    compiler.compile(
        createSourceFile(
            path("foo.js"),
            "/** @constructor */",
            "var Builder = function(){};",
            "/** @return {!Builder} . */",
            "Builder.prototype.returnThis = function() { return this; };",
            "exports.Builder = Builder"));

    Scope scope = compiler.getCompiler().getTopScope();
    Var var = scope.getVar("module$foo");
    JSType type = var.getInitialValue().getJSType().findPropertyType("Builder");
    assertTrue(type.isConstructor());

    type = type.toObjectType().getTypeOfThis();
    assertEquals("$jscomp.scope.Builder", type.toString());

    type = type.toObjectType().getPropertyType("returnThis");
    assertTrue(type.toString(), type.isFunctionType());

    JSDocInfo info = type.getJSDocInfo();
    assertNotNull(info);

    Node node = getOnlyElement(info.getTypeNodes());
    assertEquals(Token.BANG, node.getType());

    node = node.getFirstChild();
    assertTrue(node.isString());
    assertEquals("$jscomp.scope.Builder", node.getString());
    assertEquals("Builder", node.getProp(Node.ORIGINALNAME_PROP));
  }
Пример #4
0
 @Override
 public boolean apply(JSType type) {
   // TODO(user): Doing an instanceof check here is too
   // restrictive as (Date,Error) is, for instance, an object type
   // even though its implementation is a UnionType. Would need to
   // create interfaces JSType, ObjectType, FunctionType etc and have
   // separate implementation instead of the class hierarchy, so that
   // union types can also be object types, etc.
   if (!type.restrictByNotNullOrUndefined().isSubtype(typeRegistry.getNativeType(OBJECT_TYPE))) {
     reportWarning(THIS_TYPE_NON_OBJECT, type.toString());
     return false;
   }
   return true;
 }
Пример #5
0
  private String getTypeAnnotation(Node node) {
    // Only add annotations for things with JSDoc, or function literals.
    JSDocInfo jsdoc = NodeUtil.getBestJSDocInfo(node);
    if (jsdoc == null && !node.isFunction()) {
      return "";
    }

    JSType type = node.getJSType();
    if (type == null) {
      return "";
    } else if (type.isFunctionType()) {
      return getFunctionAnnotation(node);
    } else if (type.isEnumType()) {
      return "/** @enum {"
          + type.toMaybeEnumType().getElementsType().toAnnotationString()
          + "} */\n";
    } else if (!type.isUnknownType()
        && !type.isEmptyType()
        && !type.isVoidType()
        && !type.isFunctionPrototypeType()) {
      return "/** @type {" + node.getJSType().toAnnotationString() + "} */\n";
    } else {
      return "";
    }
  }
Пример #6
0
  /** Gets the type of {@code this} in the current scope. */
  @Override
  public JSType getTypeOfThis() {
    if (isGlobal()) {
      return ObjectType.cast(rootNode.getJSType());
    }

    Preconditions.checkState(rootNode.isFunction());
    JSType nodeType = rootNode.getJSType();
    if (nodeType != null && nodeType.isFunctionType()) {
      return nodeType.toMaybeFunctionType().getTypeOfThis();
    } else {
      // Executed when the current scope has not been typechecked.
      return null;
    }
  }
    private Set<JSType> getTypesToSkipForTypeNonUnion(JSType type) {
      Set<JSType> types = Sets.newHashSet();
      JSType skipType = type;
      while (skipType != null) {
        types.add(skipType);

        ObjectType objSkipType = skipType.toObjectType();
        if (objSkipType != null) {
          skipType = objSkipType.getImplicitPrototype();
        } else {
          break;
        }
      }
      return types;
    }
 /**
  * @return True if n is a function node which is explicitly annotated as returning a nullable
  *     type, other than {?}.
  */
 private static boolean isReturnTypeNullable(Node n) {
   if (n == null || !n.isFunction()) {
     return false;
   }
   FunctionType functionType = n.getJSType().toMaybeFunctionType();
   if (functionType == null) {
     // If the JSDoc declares a non-function type on a function node, we still shouldn't crash.
     return false;
   }
   JSType returnType = functionType.getReturnType();
   if (returnType == null || returnType.isUnknownType() || !returnType.isNullable()) {
     return false;
   }
   JSDocInfo info = NodeUtil.getBestJSDocInfo(n);
   return info != null && info.hasReturnType();
 }
    /**
     * Add concrete types for autoboxing types if necessary. The concrete type system does not track
     * native types, like string, so add them if they are present in the JSType for the node.
     */
    private ConcreteType maybeAddAutoboxes(ConcreteType cType, Node node, String prop) {
      JSType jsType = node.getJSType();
      if (jsType == null) {
        return cType;
      } else if (jsType.isUnknownType()) {
        for (JSTypeNative nativeType : nativeTypes) {
          ConcreteType concrete =
              tt.getConcreteInstance(tt.getTypeRegistry().getNativeObjectType(nativeType));
          if (concrete != null && !concrete.getPropertyType(prop).isNone()) {
            cType = cType.unionWith(concrete);
          }
        }
        return cType;
      }

      return maybeAddAutoboxes(cType, jsType, prop);
    }
    private ConcreteType maybeAddAutoboxes(ConcreteType cType, JSType jsType, String prop) {
      jsType = jsType.restrictByNotNullOrUndefined();
      if (jsType instanceof UnionType) {
        for (JSType alt : ((UnionType) jsType).getAlternates()) {
          return maybeAddAutoboxes(cType, alt, prop);
        }
      }

      if (jsType.autoboxesTo() != null) {
        JSType autoboxed = jsType.autoboxesTo();
        return cType.unionWith(tt.getConcreteInstance((ObjectType) autoboxed));
      } else if (jsType.unboxesTo() != null) {
        return cType.unionWith(tt.getConcreteInstance((ObjectType) jsType));
      }

      return cType;
    }
  @Test
  public void canReferenceConstructorDefinedInTheGlobalScope() {
    CompilerUtil compiler = createCompiler(path("x/bar.js"));

    compiler.compile(
        createSourceFile(path("x/foo.js"), "/** @constructor */", "function Foo() {}"),
        createSourceFile(
            path("x/bar.js"), "/** @type {function(new: Foo)} */", "exports.Foo = Foo;"));

    Scope scope = compiler.getCompiler().getTopScope();
    Var var = scope.getVar("module$x$bar");

    JSType type = var.getInitialValue().getJSType().findPropertyType("Foo");
    assertTrue(type.isConstructor());

    type = type.toObjectType().getTypeOfThis();
    assertEquals("Foo", type.toString());
  }
 @Override
 public Iterable<JSType> getTypeAlternatives(JSType type) {
   if (type.isUnionType()) {
     return ((UnionType) type).getAlternates();
   } else {
     ObjectType objType = type.toObjectType();
     if (objType != null
         && objType.getConstructor() != null
         && objType.getConstructor().isInterface()) {
       List<JSType> list = Lists.newArrayList();
       for (FunctionType impl : registry.getDirectImplementors(objType)) {
         list.add(impl.getInstanceType());
       }
       return list;
     } else {
       return null;
     }
   }
 }
    @Override
    public boolean isInvalidatingType(JSType type) {
      if (type == null
          || invalidatingTypes.contains(type)
          || type.isUnknownType() /* unresolved types */) {
        return true;
      }

      ObjectType objType = ObjectType.cast(type);
      return objType != null && !objType.hasReferenceName();
    }
 @Override
 public JSType getInstanceFromPrototype(JSType type) {
   if (type.isFunctionPrototypeType()) {
     FunctionPrototypeType prototype = (FunctionPrototypeType) type;
     FunctionType owner = prototype.getOwnerFunction();
     if (owner.isConstructor() || owner.isInterface()) {
       return ((FunctionPrototypeType) type).getOwnerFunction().getInstanceType();
     }
   }
   return null;
 }
Пример #15
0
  /**
   * Infer the role of the function (whether it's a constructor or interface) and what it inherits
   * from in JSDocInfo.
   */
  FunctionTypeBuilder inferInheritance(@Nullable JSDocInfo info) {
    if (info != null) {
      isConstructor = info.isConstructor();
      isInterface = info.isInterface();

      // base type
      if (info.hasBaseType()) {
        if (isConstructor || isInterface) {
          JSType maybeBaseType = info.getBaseType().evaluate(scope, typeRegistry);
          if (maybeBaseType != null && maybeBaseType.setValidator(new ExtendedTypeValidator())) {
            baseType = (ObjectType) maybeBaseType;
          }
        } else {
          reportWarning(EXTENDS_WITHOUT_TYPEDEF, fnName);
        }
      }

      // implemented interfaces
      if (isConstructor || isInterface) {
        implementedInterfaces = Lists.newArrayList();
        for (JSTypeExpression t : info.getImplementedInterfaces()) {
          JSType maybeInterType = t.evaluate(scope, typeRegistry);
          if (maybeInterType != null
              && maybeInterType.setValidator(new ImplementedTypeValidator())) {
            implementedInterfaces.add((ObjectType) maybeInterType);
          }
        }
        if (baseType != null) {
          JSType maybeFunctionType = baseType.getConstructor();
          if (maybeFunctionType instanceof FunctionType) {
            FunctionType functionType = baseType.getConstructor();
            Iterables.addAll(implementedInterfaces, functionType.getImplementedInterfaces());
          }
        }
      } else if (info.getImplementedInterfaceCount() > 0) {
        reportWarning(IMPLEMENTS_WITHOUT_CONSTRUCTOR, fnName);
      }
    }

    return this;
  }
 @Override
 public ImmutableSet<JSType> getTypesToSkipForType(JSType type) {
   type = type.restrictByNotNullOrUndefined();
   if (type instanceof UnionType) {
     Set<JSType> types = Sets.newHashSet(type);
     for (JSType alt : ((UnionType) type).getAlternates()) {
       types.addAll(getTypesToSkipForTypeNonUnion(type));
     }
     return ImmutableSet.copyOf(types);
   }
   return ImmutableSet.copyOf(getTypesToSkipForTypeNonUnion(type));
 }
  @Test
  public void canReferenceConstructorExportedByAnotherModule() {
    CompilerUtil compiler = createCompiler(path("x/foo.js"), path("x/bar.js"));

    compiler.compile(
        createSourceFile(path("x/foo.js"), "/** @constructor */", "exports.Foo = function(){};"),
        createSourceFile(
            path("x/bar.js"),
            "var foo = require('./foo');",
            "/** @type {function(new: foo.Foo)} */",
            "exports.Foo = foo.Foo;"));

    Scope scope = compiler.getCompiler().getTopScope();
    Var var = scope.getVar("module$x$bar");

    JSType type = var.getInitialValue().getJSType().findPropertyType("Foo");
    assertTrue(type.isConstructor());

    type = type.toObjectType().getTypeOfThis();
    assertEquals("module$x$foo.Foo", type.toString());
  }
  @Override
  public JSType caseTemplateType(TemplateType type) {
    if (replacements.hasTemplateKey(type)) {
      if (hasVisitedType(type) || !replacements.hasTemplateType(type)) {
        // If we have already encountered this TemplateType during replacement
        // (i.e. there is a reference loop), or there is no JSType substitution
        // for the TemplateType, return the TemplateType type itself.
        return type;
      } else {
        JSType replacement = replacements.getTemplateType(type);

        visitedTypes.push(type);
        JSType visitedReplacement = replacement.visit(this);
        visitedTypes.pop();

        return visitedReplacement;
      }
    } else {
      return type;
    }
  }
Пример #19
0
  /**
   * Creates a JSDoc-suitable String representation the type of a parameter.
   *
   * @param parameterNode The parameter node.
   */
  private String getParameterNodeJSDocType(Node parameterNode) {
    JSType parameterType = parameterNode.getJSType();
    String typeString;

    // Emit unknown types as '*' (AllType) since '?' (UnknownType) is not
    // a valid JSDoc type.
    if (parameterType.isUnknownType()) {
      typeString = "*";
    } else {
      // Fix-up optional and vararg parameters to match JSDoc type language
      if (parameterNode.isOptionalArg()) {
        typeString = parameterType.restrictByNotNullOrUndefined().toAnnotationString() + "=";
      } else if (parameterNode.isVarArgs()) {
        typeString = "..." + parameterType.restrictByNotNullOrUndefined().toAnnotationString();
      } else {
        typeString = parameterType.toAnnotationString();
      }
    }

    return typeString;
  }
    @Override
    public ObjectType getTypeWithProperty(String field, JSType type) {
      if (!(type instanceof ObjectType)) {
        if (type.autoboxesTo() != null) {
          type = type.autoboxesTo();
        } else {
          return null;
        }
      }

      // Ignore the prototype itself at all times.
      if ("prototype".equals(field)) {
        return null;
      }

      // We look up the prototype chain to find the highest place (if any) that
      // this appears.  This will make references to overriden properties look
      // like references to the initial property, so they are renamed alike.
      ObjectType foundType = null;
      ObjectType objType = ObjectType.cast(type);
      while (objType != null && objType.getImplicitPrototype() != objType) {
        if (objType.hasOwnProperty(field)) {
          foundType = objType;
        }
        objType = objType.getImplicitPrototype();
      }
      // If the property does not exist on the referenced type but the original
      // type is an object type, see if any subtype has the property.
      if (foundType == null) {
        ObjectType maybeType =
            ObjectType.cast(registry.getGreatestSubtypeWithProperty(type, field));
        // getGreatestSubtypeWithProperty does not guarantee that the property
        // is defined on the returned type, it just indicates that it might be,
        // so we have to double check.
        if (maybeType != null && maybeType.hasOwnProperty(field)) {
          foundType = maybeType;
        }
      }
      return foundType;
    }
Пример #21
0
  /** Infer the return type from JSDocInfo. */
  FunctionTypeBuilder inferReturnType(@Nullable JSDocInfo info) {
    if (info != null && info.hasReturnType()) {
      returnType = info.getReturnType().evaluate(scope, typeRegistry);
      returnTypeInferred = false;
    }

    if (templateTypeName != null
        && returnType != null
        && returnType.restrictByNotNullOrUndefined().isTemplateType()) {
      reportError(TEMPLATE_TYPE_EXPECTED, fnName);
    }
    return this;
  }
  /** Invalidates the given type, so that no properties on it will be renamed. */
  private void addInvalidatingType(JSType type) {
    type = type.restrictByNotNullOrUndefined();
    if (type instanceof UnionType) {
      for (JSType alt : ((UnionType) type).getAlternates()) {
        addInvalidatingType(alt);
      }
      return;
    }

    typeSystem.addInvalidatingType(type);
    ObjectType objType = ObjectType.cast(type);
    if (objType != null && objType.getImplicitPrototype() != null) {
      typeSystem.addInvalidatingType(objType.getImplicitPrototype());
    }
  }
Пример #23
0
 @Override
 public boolean apply(JSType type) {
   ObjectType objectType = ObjectType.cast(type);
   if (objectType == null) {
     reportWarning(EXTENDS_NON_OBJECT, fnName, type.toString());
   } else if (objectType.isUnknownType()
       &&
       // If this has a supertype that hasn't been resolved yet,
       // then we can assume this type will be ok once the super
       // type resolves.
       (objectType.getImplicitPrototype() == null
           || objectType.getImplicitPrototype().isResolved())) {
     reportWarning(RESOLVED_TAG_EMPTY, "@extends", fnName);
   } else {
     return true;
   }
   return false;
 }
 @Override
 public JSType caseObjectType(ObjectType type) {
   JSType arrayType = getNativeType(ARRAY_TYPE);
   return arrayType.isSubtype(type) ? arrayType : null;
 }
 @Override
 protected JSType caseTopType(JSType topType) {
   return topType.isAllType() ? getNativeType(ARRAY_TYPE) : topType;
 }
  /**
   * Returns whether two nodes are equivalent, taking into account the template parameters that were
   * provided to this matcher. If the template comparison node is a parameter node, then only the
   * types of the node must match. Otherwise, the node must be equal and the child nodes must be
   * equivalent according to the same function. This differs from the built in Node equivalence
   * function with the special comparison.
   */
  private boolean matchesNode(Node template, Node ast) {
    if (isTemplateParameterNode(template)) {
      int paramIndex = (int) (template.getDouble());
      Node previousMatch = paramNodeMatches.get(paramIndex);
      if (previousMatch != null) {
        // If this named node has already been matched against, make sure all
        // subsequent usages of the same named node are equivalent.
        return ast.isEquivalentTo(previousMatch);
      }

      // Only the types need to match for the template parameters, which allows
      // the template function to express arbitrary expressions.
      JSType templateType = template.getJSType();

      Preconditions.checkNotNull(templateType, "null template parameter type.");

      // TODO(johnlenz): We shouldn't spend time checking template whose
      // types whose definitions aren't included (NoResolvedType). Alternately
      // we should treat them as "unknown" and perform loose matches.
      if (templateType.isNoResolvedType()) {
        return false;
      }

      MatchResult matchResult = typeMatchingStrategy.match(templateType, ast.getJSType());
      isLooseMatch = matchResult.isLooseMatch();
      boolean isMatch = matchResult.isMatch();
      if (isMatch && previousMatch == null) {
        paramNodeMatches.set(paramIndex, ast);
      }
      return isMatch;
    } else if (isTemplateLocalNameNode(template)) {
      // If this template name node was already matched against, then make sure
      // all subsequent usages of the same template name node are equivalent in
      // the matched code.
      // For example, this code will handle the case:
      // function template() {
      //   var a = 'str';
      //   fn(a);
      // }
      //
      // will only match test code:
      //   var b = 'str';
      //   fn(b);
      //
      // but it will not match:
      //   var b = 'str';
      //   fn('str');
      int paramIndex = (int) (template.getDouble());
      boolean previouslyMatched = this.localVarMatches.get(paramIndex) != null;
      if (previouslyMatched) {
        // If this named node has already been matched against, make sure all
        // subsequent usages of the same named node are equivalent.
        return ast.getString().equals(this.localVarMatches.get(paramIndex));
      } else {
        this.localVarMatches.set(paramIndex, ast.getString());
      }
    }

    // Template and AST shape has already been checked, but continue look for
    // other template variables (parameters and locals) that must be checked.
    Node templateChild = template.getFirstChild();
    Node astChild = ast.getFirstChild();
    while (templateChild != null) {
      if (!matchesNode(templateChild, astChild)) {
        return false;
      }
      templateChild = templateChild.getNext();
      astChild = astChild.getNext();
    }

    return true;
  }
Пример #27
0
  /** Infer the parameter types from the list of argument names and the doc info. */
  FunctionTypeBuilder inferParameterTypes(@Nullable Node argsParent, @Nullable JSDocInfo info) {
    if (argsParent == null) {
      if (info == null) {
        return this;
      } else {
        return inferParameterTypes(info);
      }
    }

    // arguments
    Node oldParameterType = null;
    if (parametersNode != null) {
      oldParameterType = parametersNode.getFirstChild();
    }

    FunctionParamBuilder builder = new FunctionParamBuilder(typeRegistry);
    boolean warnedAboutArgList = false;
    Set<String> allJsDocParams =
        (info == null) ? Sets.<String>newHashSet() : Sets.newHashSet(info.getParameterNames());
    boolean foundTemplateType = false;
    for (Node arg : argsParent.children()) {
      String argumentName = arg.getString();
      allJsDocParams.remove(argumentName);

      // type from JSDocInfo
      JSType parameterType = null;
      boolean isOptionalParam = isOptionalParameter(arg, info);
      boolean isVarArgs = isVarArgsParameter(arg, info);
      if (info != null && info.hasParameterType(argumentName)) {
        parameterType = info.getParameterType(argumentName).evaluate(scope, typeRegistry);
      } else if (oldParameterType != null && oldParameterType.getJSType() != null) {
        parameterType = oldParameterType.getJSType();
        isOptionalParam = oldParameterType.isOptionalArg();
        isVarArgs = oldParameterType.isVarArgs();
      } else {
        parameterType = typeRegistry.getNativeType(UNKNOWN_TYPE);
      }

      if (templateTypeName != null
          && parameterType.restrictByNotNullOrUndefined().isTemplateType()) {
        if (foundTemplateType) {
          reportError(TEMPLATE_TYPE_DUPLICATED, fnName);
        }
        foundTemplateType = true;
      }
      warnedAboutArgList |=
          addParameter(builder, parameterType, warnedAboutArgList, isOptionalParam, isVarArgs);

      if (oldParameterType != null) {
        oldParameterType = oldParameterType.getNext();
      }
    }

    if (templateTypeName != null && !foundTemplateType) {
      reportError(TEMPLATE_TYPE_EXPECTED, fnName);
    }

    for (String inexistentName : allJsDocParams) {
      reportWarning(INEXISTANT_PARAM, inexistentName, fnName);
    }

    parametersNode = builder.build();
    return this;
  }
 @Override
 public void addInvalidatingType(JSType type) {
   checkState(!type.isUnionType());
   invalidatingTypes.add(type);
 }
 @Override
 public boolean isTypeToSkip(JSType type) {
   return type.isEnumType() || (type.autoboxesTo() != null);
 }
 @Override
 public JSType restrictByNotNullOrUndefined(JSType type) {
   return type.restrictByNotNullOrUndefined();
 }