/** Add an @this annotation to all functions in the objLit. */
  private void addTypesToFunctions(Node objLit, String thisType) {
    Preconditions.checkState(objLit.isObjectLit());
    for (Node keyNode : objLit.children()) {
      Node value = keyNode.getLastChild();
      if (value != null && value.isFunction()) {
        JSDocInfoBuilder fnDoc = JSDocInfoBuilder.maybeCopyFrom(keyNode.getJSDocInfo());
        fnDoc.recordThisType(
            new JSTypeExpression(new Node(Token.BANG, IR.string(thisType)), VIRTUAL_FILE));
        keyNode.setJSDocInfo(fnDoc.build());
      }
    }

    // Add @this and @return to default property values.
    for (MemberDefinition property : extractProperties(objLit)) {
      if (!property.value.isObjectLit()) {
        continue;
      }
      if (hasShorthandAssignment(property.value)) {
        compiler.report(JSError.make(property.value, POLYMER_SHORTHAND_NOT_SUPPORTED));
        return;
      }

      Node defaultValue = NodeUtil.getFirstPropMatchingKey(property.value, "value");
      if (defaultValue == null || !defaultValue.isFunction()) {
        continue;
      }
      Node defaultValueKey = defaultValue.getParent();
      JSDocInfoBuilder fnDoc = JSDocInfoBuilder.maybeCopyFrom(defaultValueKey.getJSDocInfo());
      fnDoc.recordThisType(
          new JSTypeExpression(new Node(Token.BANG, IR.string(thisType)), VIRTUAL_FILE));
      fnDoc.recordReturnType(getTypeFromProperty(property));
      defaultValueKey.setJSDocInfo(fnDoc.build());
    }
  }
  /**
   * Adds an interface for the given ClassDefinition to externs. This allows generated setter
   * functions for read-only properties to avoid renaming altogether.
   *
   * @see https://www.polymer-project.org/0.8/docs/devguide/properties.html#read-only
   */
  private void addInterfaceExterns(
      final ClassDefinition cls, List<MemberDefinition> readOnlyProps) {
    Node block = IR.block();

    String interfaceName = getInterfaceName(cls);
    Node fnNode = IR.function(IR.name(""), IR.paramList(), IR.block());
    Node varNode = IR.var(NodeUtil.newQName(compiler, interfaceName), fnNode);

    JSDocInfoBuilder info = new JSDocInfoBuilder(true);
    info.recordInterface();
    varNode.setJSDocInfo(info.build());
    block.addChildToBack(varNode);

    appendPropertiesToBlock(cls, block, interfaceName + ".prototype.");
    for (MemberDefinition prop : readOnlyProps) {
      // Add all _set* functions to avoid renaming.
      String propName = prop.name.getString();
      String setterName = "_set" + propName.substring(0, 1).toUpperCase() + propName.substring(1);
      Node setterExprNode =
          IR.exprResult(NodeUtil.newQName(compiler, interfaceName + ".prototype." + setterName));

      JSDocInfoBuilder setterInfo = new JSDocInfoBuilder(true);
      JSTypeExpression propType = getTypeFromProperty(prop);
      setterInfo.recordParameter(propName, propType);
      setterExprNode.getFirstChild().setJSDocInfo(setterInfo.build());

      block.addChildToBack(setterExprNode);
    }

    Node parent = polymerElementExterns.getParent();
    Node stmts = block.removeChildren();
    parent.addChildrenToBack(stmts);

    compiler.reportCodeChange();
  }
  /**
   * Duplicates the PolymerElement externs with a different element base class if needed. For
   * example, if the base class is HTMLInputElement, then a class PolymerInputElement will be added.
   * If the element does not extend a native HTML element, this method is a no-op.
   */
  private void appendPolymerElementExterns(final ClassDefinition cls) {
    if (!nativeExternsAdded.add(cls.nativeBaseElement)) {
      return;
    }

    Node block = IR.block();

    Node baseExterns = polymerElementExterns.cloneTree();
    String polymerElementType = getPolymerElementType(cls);
    baseExterns.getFirstChild().setString(polymerElementType);

    String elementType = tagNameMap.get(cls.nativeBaseElement);
    JSTypeExpression elementBaseType =
        new JSTypeExpression(new Node(Token.BANG, IR.string(elementType)), VIRTUAL_FILE);
    JSDocInfoBuilder baseDocs = JSDocInfoBuilder.copyFrom(baseExterns.getJSDocInfo());
    baseDocs.changeBaseType(elementBaseType);
    baseExterns.setJSDocInfo(baseDocs.build());
    block.addChildToBack(baseExterns);

    for (Node baseProp : polymerElementProps) {
      Node newProp = baseProp.cloneTree();
      Node newPropRootName =
          NodeUtil.getRootOfQualifiedName(newProp.getFirstChild().getFirstChild());
      newPropRootName.setString(polymerElementType);
      block.addChildToBack(newProp);
    }

    Node parent = polymerElementExterns.getParent();
    Node stmts = block.removeChildren();
    parent.addChildrenAfter(stmts, polymerElementExterns);

    compiler.reportCodeChange();
  }
  private void addToDefinePropertiesObject(ClassDeclarationMetadata metadata, Node member) {
    Node obj =
        member.isStaticMember()
            ? metadata.definePropertiesObjForClass
            : metadata.definePropertiesObjForPrototype;
    Node prop = NodeUtil.getFirstPropMatchingKey(obj, member.getString());
    if (prop == null) {
      prop =
          IR.objectlit(
              IR.stringKey("configurable", IR.trueNode()),
              IR.stringKey("enumerable", IR.trueNode()));
      obj.addChildToBack(IR.stringKey(member.getString(), prop));
    }

    Node function = member.getLastChild();
    JSDocInfoBuilder info = JSDocInfoBuilder.maybeCopyFrom(NodeUtil.getBestJSDocInfo(function));

    info.recordThisType(
        new JSTypeExpression(
            new Node(Token.BANG, IR.string(metadata.fullClassName)), member.getSourceFileName()));
    Node stringKey =
        IR.stringKey(member.isGetterDef() ? "get" : "set", function.detachFromParent());
    stringKey.setJSDocInfo(info.build());
    prop.addChildToBack(stringKey);
    prop.useSourceInfoIfMissingFromForTree(member);
  }
Example #5
0
 private void addExtern() {
   Node name = IR.name(PROTECTOR_FN);
   name.putBooleanProp(Node.IS_CONSTANT_NAME, true);
   Node var = IR.var(name);
   // Add "@noalias" so we can strip the method when AliasExternals is enabled.
   JSDocInfoBuilder builder = new JSDocInfoBuilder(false);
   builder.recordNoAlias();
   var.setJSDocInfo(builder.build(var));
   CompilerInput input = compiler.getSynthesizedExternsInput();
   input.getAstRoot(compiler).addChildrenToBack(var);
   compiler.reportCodeChange();
 }
 private void addBehaviorSuppressions(Node behaviorValue) {
   for (Node keyNode : behaviorValue.children()) {
     if (keyNode.getFirstChild().isFunction()) {
       keyNode.removeProp(Node.JSDOC_INFO_PROP);
       JSDocInfoBuilder suppressDoc = new JSDocInfoBuilder(true);
       suppressDoc.addSuppression("checkTypes");
       suppressDoc.addSuppression("globalThis");
       keyNode.setJSDocInfo(suppressDoc.build());
     }
   }
   suppressDefaultValues(behaviorValue);
 }
 private void varify() {
   if (!blockScopedDeclarations.isEmpty()) {
     for (Node n : blockScopedDeclarations) {
       if (n.isConst()) {
         JSDocInfoBuilder builder = JSDocInfoBuilder.maybeCopyFrom(n.getJSDocInfo());
         builder.recordConstancy();
         JSDocInfo info = builder.build();
         n.setJSDocInfo(info);
       }
       n.setType(Token.VAR);
     }
     compiler.reportCodeChange();
   }
 }
    private void suppressDefaultValues(Node behaviorValue) {
      for (MemberDefinition property : extractProperties(behaviorValue)) {
        if (!property.value.isObjectLit()) {
          continue;
        }

        Node defaultValue = NodeUtil.getFirstPropMatchingKey(property.value, "value");
        if (defaultValue == null || !defaultValue.isFunction()) {
          continue;
        }
        Node defaultValueKey = defaultValue.getParent();
        JSDocInfoBuilder suppressDoc =
            JSDocInfoBuilder.maybeCopyFrom(defaultValueKey.getJSDocInfo());
        suppressDoc.addSuppression("checkTypes");
        suppressDoc.addSuppression("globalThis");
        defaultValueKey.setJSDocInfo(suppressDoc.build());
      }
    }
    @Override
    public void visit(NodeTraversal t, Node n, Node parent) {
      if (isBehavior(n)) {
        if (!n.isVar() && !n.isAssign()) {
          compiler.report(JSError.make(n, POLYMER_UNQUALIFIED_BEHAVIOR));
          return;
        }

        // Add @nocollapse.
        JSDocInfoBuilder newDocs = JSDocInfoBuilder.maybeCopyFrom(n.getJSDocInfo());
        newDocs.recordNoCollapse();
        n.setJSDocInfo(newDocs.build());

        Node behaviorValue = n.getChildAtIndex(1);
        if (n.isVar()) {
          behaviorValue = n.getFirstChild().getFirstChild();
        }
        suppressBehavior(behaviorValue);
      }
    }
  /**
   * Updates the first initialization (a.k.a "declaration") of a global name that occurs at an
   * ASSIGN node. See comment for {@link #updateObjLitOrFunctionDeclaration}.
   *
   * @param n An object representing a global name (e.g. "a", "a.b.c")
   * @param alias The flattened name for {@code n} (e.g. "a", "a$b$c")
   */
  private void updateObjLitOrFunctionDeclarationAtAssignNode(
      Name n, String alias, boolean canCollapseChildNames) {
    // NOTE: It's important that we don't add additional nodes
    // (e.g. a var node before the exprstmt) because the exprstmt might be
    // the child of an if statement that's not inside a block).

    Ref ref = n.getDeclaration();
    Node rvalue = ref.node.getNext();
    Node varNode = new Node(Token.VAR);
    Node varParent = ref.node.getAncestor(3);
    Node grandparent = ref.node.getAncestor(2);
    boolean isObjLit = rvalue.isObjectLit();
    boolean insertedVarNode = false;

    if (isObjLit && n.canEliminate()) {
      // Eliminate the object literal altogether.
      varParent.replaceChild(grandparent, varNode);
      ref.node = null;
      insertedVarNode = true;

    } else if (!n.isSimpleName()) {
      // Create a VAR node to declare the name.
      if (rvalue.isFunction()) {
        checkForHosedThisReferences(rvalue, n.docInfo, n);
      }

      ref.node.getParent().removeChild(rvalue);

      Node nameNode = NodeUtil.newName(compiler, alias, ref.node.getAncestor(2), n.getFullName());

      JSDocInfo info = NodeUtil.getBestJSDocInfo(ref.node.getParent());
      if (ref.node.getLastChild().getBooleanProp(Node.IS_CONSTANT_NAME)
          || (info != null && info.isConstant())) {
        nameNode.putBooleanProp(Node.IS_CONSTANT_NAME, true);
      }

      if (info != null) {
        varNode.setJSDocInfo(info);
      }
      varNode.addChildToBack(nameNode);
      nameNode.addChildToFront(rvalue);
      varParent.replaceChild(grandparent, varNode);

      // Update the node ancestry stored in the reference.
      ref.node = nameNode;
      insertedVarNode = true;
    }

    if (canCollapseChildNames) {
      if (isObjLit) {
        declareVarsForObjLitValues(
            n, alias, rvalue, varNode, varParent.getChildBefore(varNode), varParent);
      }

      addStubsForUndeclaredProperties(n, alias, varParent, varNode);
    }

    if (insertedVarNode) {
      if (!varNode.hasChildren()) {
        varParent.removeChild(varNode);
      }
      compiler.reportCodeChange();
    }
  }
  private void rewritePolymerClass(Node exprRoot, final ClassDefinition cls, NodeTraversal t) {
    // Add {@code @lends} to the object literal.
    Node call = exprRoot.getFirstChild();
    if (call.isAssign()) {
      call = call.getChildAtIndex(1);
    } else if (call.isName()) {
      call = call.getFirstChild();
    }

    Node objLit = cls.descriptor;
    if (hasShorthandAssignment(objLit)) {
      compiler.report(JSError.make(objLit, POLYMER_SHORTHAND_NOT_SUPPORTED));
      return;
    }

    JSDocInfoBuilder objLitDoc = new JSDocInfoBuilder(true);
    objLitDoc.recordLends(cls.target.getQualifiedName() + ".prototype");
    objLit.setJSDocInfo(objLitDoc.build());

    this.addTypesToFunctions(objLit, cls.target.getQualifiedName());
    this.switchDollarSignPropsToBrackets(objLit);
    this.quoteListenerAndHostAttributeKeys(objLit);

    // For simplicity add everything into a block, before adding it to the AST.
    Node block = IR.block();

    if (cls.nativeBaseElement != null) {
      this.appendPolymerElementExterns(cls);
    }
    JSDocInfoBuilder constructorDoc = this.getConstructorDoc(cls);

    // Remove the original constructor JS docs from the objlit.
    Node ctorKey = cls.constructor.value.getParent();
    if (ctorKey != null) {
      ctorKey.removeProp(Node.JSDOC_INFO_PROP);
    }

    if (cls.target.isGetProp()) {
      // foo.bar = Polymer({...});
      Node assign = IR.assign(cls.target.cloneTree(), cls.constructor.value.cloneTree());
      assign.setJSDocInfo(constructorDoc.build());
      Node exprResult = IR.exprResult(assign);
      block.addChildToBack(exprResult);
    } else {
      // var foo = Polymer({...}); OR Polymer({...});
      Node var = IR.var(cls.target.cloneTree(), cls.constructor.value.cloneTree());
      var.setJSDocInfo(constructorDoc.build());
      block.addChildToBack(var);
    }

    appendPropertiesToBlock(cls, block, cls.target.getQualifiedName() + ".prototype.");
    appendBehaviorMembersToBlock(cls, block);
    List<MemberDefinition> readOnlyProps = parseReadOnlyProperties(cls, block);
    addInterfaceExterns(cls, readOnlyProps);
    removePropertyDocs(objLit);

    block.useSourceInfoIfMissingFromForTree(exprRoot);
    Node stmts = block.removeChildren();
    Node parent = exprRoot.getParent();

    // If the call to Polymer() is not in the global scope and the assignment target is not
    // namespaced (which likely means it's exported to the global scope), put the type declaration
    // into the global scope at the start of the current script.
    //
    // This avoids unknown type warnings which are a result of the compiler's poor understanding of
    // types declared inside IIFEs or any non-global scope. We should revisit this decision after
    // moving to the new type inference system which should be able to infer these types better.
    if (!t.getScope().isGlobal() && !cls.target.isGetProp()) {
      Node scriptNode = NodeUtil.getEnclosingScript(exprRoot);
      scriptNode.addChildrenToFront(stmts);
    } else {
      Node beforeRoot = parent.getChildBefore(exprRoot);
      if (beforeRoot == null) {
        parent.addChildrenToFront(stmts);
      } else {
        parent.addChildrenAfter(stmts, beforeRoot);
      }
    }

    if (exprRoot.isVar()) {
      Node assignExpr = varToAssign(exprRoot);
      parent.replaceChild(exprRoot, assignExpr);
    }

    compiler.reportCodeChange();
  }
  /**
   * Classes are processed in 3 phases:
   *
   * <ol>
   *   <li>The class name is extracted.
   *   <li>Class members are processed and rewritten.
   *   <li>The constructor is built.
   * </ol>
   */
  private void visitClass(Node classNode, Node parent) {
    checkClassReassignment(classNode);
    // Collect Metadata
    ClassDeclarationMetadata metadata = ClassDeclarationMetadata.create(classNode, parent);

    if (metadata == null || metadata.fullClassName == null) {
      cannotConvert(
          parent,
          "Can only convert classes that are declarations or the right hand"
              + " side of a simple assignment.");
      return;
    }
    if (metadata.hasSuperClass() && !metadata.superClassNameNode.isQualifiedName()) {
      compiler.report(JSError.make(metadata.superClassNameNode, DYNAMIC_EXTENDS_TYPE));
      return;
    }

    boolean useUnique = NodeUtil.isStatement(classNode) && !NodeUtil.isInFunction(classNode);
    String uniqueFullClassName =
        useUnique ? getUniqueClassName(metadata.fullClassName) : metadata.fullClassName;
    Node classNameAccess = NodeUtil.newQName(compiler, uniqueFullClassName);
    Node prototypeAccess = NodeUtil.newPropertyAccess(compiler, classNameAccess, "prototype");

    Preconditions.checkState(
        NodeUtil.isStatement(metadata.insertionPoint),
        "insertion point must be a statement: %s",
        metadata.insertionPoint);

    Node constructor = null;
    JSDocInfo ctorJSDocInfo = null;
    // Process all members of the class
    Node classMembers = classNode.getLastChild();
    Map<String, JSDocInfo> prototypeMembersToDeclare = new LinkedHashMap<>();
    Map<String, JSDocInfo> classMembersToDeclare = new LinkedHashMap<>();
    for (Node member : classMembers.children()) {
      if (member.isEmpty()) {
        continue;
      }
      Preconditions.checkState(
          member.isMemberFunctionDef()
              || member.isGetterDef()
              || member.isSetterDef()
              || (member.isComputedProp() && !member.getBooleanProp(Node.COMPUTED_PROP_VARIABLE)),
          "Member variables should have been transpiled earlier: ",
          member);

      if (member.isGetterDef() || member.isSetterDef()) {
        JSTypeExpression typeExpr = getTypeFromGetterOrSetter(member).clone();
        addToDefinePropertiesObject(metadata, member);

        Map<String, JSDocInfo> membersToDeclare =
            member.isStaticMember() ? classMembersToDeclare : prototypeMembersToDeclare;
        JSDocInfo existingJSDoc = membersToDeclare.get(member.getString());
        JSTypeExpression existingType = existingJSDoc == null ? null : existingJSDoc.getType();
        if (existingType != null && !existingType.equals(typeExpr)) {
          compiler.report(JSError.make(member, CONFLICTING_GETTER_SETTER_TYPE, member.getString()));
        } else {
          JSDocInfoBuilder jsDoc = new JSDocInfoBuilder(false);
          jsDoc.recordType(typeExpr);
          if (member.getJSDocInfo() != null && member.getJSDocInfo().isExport()) {
            jsDoc.recordExport();
          }
          if (member.isStaticMember()) {
            jsDoc.recordNoCollapse();
          }
          membersToDeclare.put(member.getString(), jsDoc.build());
        }
      } else if (member.isMemberFunctionDef() && member.getString().equals("constructor")) {
        ctorJSDocInfo = member.getJSDocInfo();
        constructor = member.getFirstChild().detachFromParent();
        if (!metadata.anonymous) {
          // Turns class Foo { constructor: function() {} } into function Foo() {},
          // i.e. attaches the name the ctor function.
          constructor.replaceChild(constructor.getFirstChild(), metadata.classNameNode.cloneNode());
        }
      } else {
        Node qualifiedMemberAccess =
            getQualifiedMemberAccess(compiler, member, classNameAccess, prototypeAccess);
        Node method = member.getLastChild().detachFromParent();

        Node assign = IR.assign(qualifiedMemberAccess, method);
        assign.useSourceInfoIfMissingFromForTree(member);

        JSDocInfo info = member.getJSDocInfo();
        if (member.isStaticMember() && NodeUtil.referencesThis(assign.getLastChild())) {
          JSDocInfoBuilder memberDoc = JSDocInfoBuilder.maybeCopyFrom(info);
          memberDoc.recordThisType(
              new JSTypeExpression(
                  new Node(Token.BANG, new Node(Token.QMARK)), member.getSourceFileName()));
          info = memberDoc.build();
        }
        if (info != null) {
          assign.setJSDocInfo(info);
        }

        Node newNode = NodeUtil.newExpr(assign);
        metadata.insertNodeAndAdvance(newNode);
      }
    }

    // Add declarations for properties that were defined with a getter and/or setter,
    // so that the typechecker knows those properties exist on the class.
    // This is a temporary solution. Eventually, the type checker should understand
    // Object.defineProperties calls directly.
    for (Map.Entry<String, JSDocInfo> entry : prototypeMembersToDeclare.entrySet()) {
      String declaredMember = entry.getKey();
      Node declaration = IR.getprop(prototypeAccess.cloneTree(), IR.string(declaredMember));
      declaration.setJSDocInfo(entry.getValue());
      metadata.insertNodeAndAdvance(
          IR.exprResult(declaration).useSourceInfoIfMissingFromForTree(classNode));
    }
    for (Map.Entry<String, JSDocInfo> entry : classMembersToDeclare.entrySet()) {
      String declaredMember = entry.getKey();
      Node declaration = IR.getprop(classNameAccess.cloneTree(), IR.string(declaredMember));
      declaration.setJSDocInfo(entry.getValue());
      metadata.insertNodeAndAdvance(
          IR.exprResult(declaration).useSourceInfoIfMissingFromForTree(classNode));
    }

    if (metadata.definePropertiesObjForPrototype.hasChildren()) {
      Node definePropsCall =
          IR.exprResult(
              IR.call(
                  NodeUtil.newQName(compiler, "Object.defineProperties"),
                  prototypeAccess.cloneTree(),
                  metadata.definePropertiesObjForPrototype));
      definePropsCall.useSourceInfoIfMissingFromForTree(classNode);
      metadata.insertNodeAndAdvance(definePropsCall);
    }

    if (metadata.definePropertiesObjForClass.hasChildren()) {
      Node definePropsCall =
          IR.exprResult(
              IR.call(
                  NodeUtil.newQName(compiler, "Object.defineProperties"),
                  classNameAccess.cloneTree(),
                  metadata.definePropertiesObjForClass));
      definePropsCall.useSourceInfoIfMissingFromForTree(classNode);
      metadata.insertNodeAndAdvance(definePropsCall);
    }

    Preconditions.checkNotNull(constructor);

    JSDocInfo classJSDoc = NodeUtil.getBestJSDocInfo(classNode);
    JSDocInfoBuilder newInfo = JSDocInfoBuilder.maybeCopyFrom(classJSDoc);

    newInfo.recordConstructor();

    if (metadata.hasSuperClass()) {
      String superClassString = metadata.superClassNameNode.getQualifiedName();
      if (newInfo.isInterfaceRecorded()) {
        newInfo.recordExtendedInterface(
            new JSTypeExpression(
                new Node(Token.BANG, IR.string(superClassString)),
                metadata.superClassNameNode.getSourceFileName()));
      } else {
        Node inherits =
            IR.call(
                NodeUtil.newQName(compiler, INHERITS),
                NodeUtil.newQName(compiler, metadata.fullClassName),
                NodeUtil.newQName(compiler, superClassString));
        Node inheritsCall = IR.exprResult(inherits);
        compiler.needsEs6Runtime = true;

        inheritsCall.useSourceInfoIfMissingFromForTree(classNode);
        Node enclosingStatement = NodeUtil.getEnclosingStatement(classNode);
        enclosingStatement.getParent().addChildAfter(inheritsCall, enclosingStatement);
        newInfo.recordBaseType(
            new JSTypeExpression(
                new Node(Token.BANG, IR.string(superClassString)),
                metadata.superClassNameNode.getSourceFileName()));
      }
    }

    // Classes are @struct by default.
    if (!newInfo.isUnrestrictedRecorded()
        && !newInfo.isDictRecorded()
        && !newInfo.isStructRecorded()) {
      newInfo.recordStruct();
    }

    if (ctorJSDocInfo != null) {
      newInfo.recordSuppressions(ctorJSDocInfo.getSuppressions());
      for (String param : ctorJSDocInfo.getParameterNames()) {
        newInfo.recordParameter(param, ctorJSDocInfo.getParameterType(param));
      }
      newInfo.mergePropertyBitfieldFrom(ctorJSDocInfo);
    }

    if (NodeUtil.isStatement(classNode)) {
      constructor.getFirstChild().setString("");
      Node ctorVar = IR.let(metadata.classNameNode.cloneNode(), constructor);
      ctorVar.useSourceInfoIfMissingFromForTree(classNode);
      parent.replaceChild(classNode, ctorVar);
    } else {
      parent.replaceChild(classNode, constructor);
    }

    if (NodeUtil.isStatement(constructor)) {
      constructor.setJSDocInfo(newInfo.build());
    } else if (parent.isName()) {
      // The constructor function is the RHS of a var statement.
      // Add the JSDoc to the VAR node.
      Node var = parent.getParent();
      var.setJSDocInfo(newInfo.build());
    } else if (constructor.getParent().isName()) {
      // Is a newly created VAR node.
      Node var = constructor.getParent().getParent();
      var.setJSDocInfo(newInfo.build());
    } else if (parent.isAssign()) {
      // The constructor function is the RHS of an assignment.
      // Add the JSDoc to the ASSIGN node.
      parent.setJSDocInfo(newInfo.build());
    } else {
      throw new IllegalStateException("Unexpected parent node " + parent);
    }

    compiler.reportCodeChange();
  }
  /** Processes a rest parameter */
  private void visitRestParam(Node restParam, Node paramList) {
    Node functionBody = paramList.getLastSibling();

    restParam.setType(Token.NAME);
    restParam.setVarArgs(true);

    // Make sure rest parameters are typechecked
    JSTypeExpression type = null;
    JSDocInfo info = restParam.getJSDocInfo();
    String paramName = restParam.getString();
    if (info != null) {
      type = info.getType();
    } else {
      JSDocInfo functionInfo = paramList.getParent().getJSDocInfo();
      if (functionInfo != null) {
        type = functionInfo.getParameterType(paramName);
      }
    }
    if (type != null && type.getRoot().getType() != Token.ELLIPSIS) {
      compiler.report(JSError.make(restParam, BAD_REST_PARAMETER_ANNOTATION));
    }

    if (!functionBody.hasChildren()) {
      // If function has no body, we are done!
      compiler.reportCodeChange();
      return;
    }

    Node newBlock = IR.block().useSourceInfoFrom(functionBody);
    Node name = IR.name(paramName);
    Node let = IR.let(name, IR.name(REST_PARAMS)).useSourceInfoIfMissingFromForTree(functionBody);
    newBlock.addChildToFront(let);

    for (Node child : functionBody.children()) {
      newBlock.addChildToBack(child.detachFromParent());
    }

    if (type != null) {
      Node arrayType = IR.string("Array");
      Node typeNode = type.getRoot();
      Node memberType =
          typeNode.getType() == Token.ELLIPSIS
              ? typeNode.getFirstChild().cloneNode()
              : typeNode.cloneNode();
      arrayType.addChildToFront(
          new Node(Token.BLOCK, memberType).useSourceInfoIfMissingFrom(typeNode));
      JSDocInfoBuilder builder = new JSDocInfoBuilder(false);
      builder.recordType(
          new JSTypeExpression(new Node(Token.BANG, arrayType), restParam.getSourceFileName()));
      name.setJSDocInfo(builder.build());
    }

    int restIndex = paramList.getIndexOfChild(restParam);
    Node newArr = IR.var(IR.name(REST_PARAMS), IR.arraylit());
    functionBody.addChildToFront(newArr.useSourceInfoIfMissingFromForTree(restParam));
    Node init = IR.var(IR.name(REST_INDEX), IR.number(restIndex));
    Node cond = IR.lt(IR.name(REST_INDEX), IR.getprop(IR.name("arguments"), IR.string("length")));
    Node incr = IR.inc(IR.name(REST_INDEX), false);
    Node body =
        IR.block(
            IR.exprResult(
                IR.assign(
                    IR.getelem(
                        IR.name(REST_PARAMS), IR.sub(IR.name(REST_INDEX), IR.number(restIndex))),
                    IR.getelem(IR.name("arguments"), IR.name(REST_INDEX)))));
    functionBody.addChildAfter(
        IR.forNode(init, cond, incr, body).useSourceInfoIfMissingFromForTree(restParam), newArr);
    functionBody.addChildToBack(newBlock);
    compiler.reportCodeChange();

    // For now, we are running transpilation before type-checking, so we'll
    // need to make sure changes don't invalidate the JSDoc annotations.
    // Therefore we keep the parameter list the same length and only initialize
    // the values if they are set to undefined.
  }
Example #14
0
 /**
  * In the AST that Rhino gives us, it needs to make a distinction between JsDoc on the object
  * literal node and JsDoc on the object literal value. For example,
  *
  * <pre>
  * var x = {
  *   / JSDOC /
  *   a: 'b',
  *   c: / JSDOC / 'd'
  * };
  * </pre>
  *
  * But in few narrow cases (in particular, function literals), it's a lot easier for us if the
  * doc is attached to the value.
  */
 private static void normalizeObjectLiteralKeyAnnotations(Node objlit, Node key, Node value) {
   Preconditions.checkState(objlit.isObjectLit());
   if (key.getJSDocInfo() != null && value.isFunction()) {
     value.setJSDocInfo(key.getJSDocInfo());
   }
 }
Example #15
0
  private void visitClass(Node classNode, Node parent) {
    Node className = classNode.getFirstChild();
    Node superClassName = className.getNext();
    Node classMembers = classNode.getLastChild();

    // This is a statement node. We insert methods of the
    // transpiled class after this node.
    Node insertionPoint;

    // The fully qualified name of the class, which will be used in the output.
    // May come from the class itself or the LHS of an assignment.
    String fullClassName = null;

    // Whether the constructor function in the output should be anonymous.
    boolean anonymous;

    // If this is a class statement, or a class expression in a simple
    // assignment or var statement, convert it. In any other case, the
    // code is too dynamic, so just call cannotConvert.
    if (NodeUtil.isStatement(classNode)) {
      fullClassName = className.getString();
      anonymous = false;
      insertionPoint = classNode;
    } else if (parent.isAssign() && parent.getParent().isExprResult()) {
      // Add members after the EXPR_RESULT node:
      // example.C = class {}; example.C.prototype.foo = function() {};
      fullClassName = parent.getFirstChild().getQualifiedName();
      if (fullClassName == null) {
        cannotConvert(parent);
        return;
      }
      anonymous = true;
      insertionPoint = parent.getParent();
    } else if (parent.isName()) {
      // Add members after the 'var' statement.
      // var C = class {}; C.prototype.foo = function() {};
      fullClassName = parent.getString();
      anonymous = true;
      insertionPoint = parent.getParent();
    } else {
      cannotConvert(parent);
      return;
    }

    Verify.verify(NodeUtil.isStatement(insertionPoint));

    className.detachFromParent();
    Node constructor = null;
    JSDocInfo ctorJSDocInfo = null;
    for (Node member : classMembers.children()) {
      if (member.getString().equals("constructor")) {
        ctorJSDocInfo = member.getJSDocInfo();
        constructor = member.getFirstChild().detachFromParent();
        if (!anonymous) {
          constructor.replaceChild(constructor.getFirstChild(), className);
        }
      } else {
        String qualifiedMemberName;
        if (member.isStaticMember()) {
          if (NodeUtil.referencesThis(member.getFirstChild())) {
            compiler.report(JSError.make(member, STATIC_METHOD_REFERENCES_THIS));
          }
          qualifiedMemberName = Joiner.on(".").join(fullClassName, member.getString());
        } else {
          qualifiedMemberName = Joiner.on(".").join(fullClassName, "prototype", member.getString());
        }
        Node assign =
            IR.assign(
                NodeUtil.newQualifiedNameNode(
                    compiler.getCodingConvention(),
                    qualifiedMemberName,
                    /* basis node */ member,
                    /* original name */ member.getString()),
                member.getFirstChild().detachFromParent());
        assign.srcref(member);

        JSDocInfo info = member.getJSDocInfo();
        if (info != null) {
          info.setAssociatedNode(assign);
          assign.setJSDocInfo(info);
        }

        Node newNode = NodeUtil.newExpr(assign);
        insertionPoint.getParent().addChildAfter(newNode, insertionPoint);
        insertionPoint = newNode;
      }
    }

    if (constructor == null) {
      Node name = anonymous ? IR.name("").srcref(className) : className;
      constructor =
          IR.function(name, IR.paramList().srcref(classNode), IR.block().srcref(classNode));
    }
    JSDocInfo classJSDoc = classNode.getJSDocInfo();
    JSDocInfoBuilder newInfo =
        (classJSDoc != null) ? JSDocInfoBuilder.copyFrom(classJSDoc) : new JSDocInfoBuilder(true);

    newInfo.recordConstructor();
    if (!superClassName.isEmpty()) {
      if (!superClassName.isQualifiedName()) {
        compiler.report(JSError.make(superClassName, DYNAMIC_EXTENDS_TYPE));
        return;
      }

      Node superClassString = IR.string(superClassName.getQualifiedName());
      if (newInfo.isInterfaceRecorded()) {
        newInfo.recordExtendedInterface(
            new JSTypeExpression(
                new Node(Token.BANG, superClassString), superClassName.getSourceFileName()));
      } else {
        // TODO(mattloring) Remove dependency on Closure Library.
        Node inherits =
            NodeUtil.newQualifiedNameNode(compiler.getCodingConvention(), "goog.inherits");
        Node inheritsCall =
            IR.exprResult(IR.call(inherits, className.cloneTree(), superClassName.cloneTree()));
        inheritsCall.useSourceInfoIfMissingFromForTree(classNode);
        parent.addChildAfter(inheritsCall, classNode);
        newInfo.recordBaseType(
            new JSTypeExpression(
                new Node(Token.BANG, superClassString), superClassName.getSourceFileName()));
      }
    }

    // Classes are @struct by default.
    if (!newInfo.isUnrestrictedRecorded()
        && !newInfo.isDictRecorded()
        && !newInfo.isStructRecorded()) {
      newInfo.recordStruct();
    }

    if (ctorJSDocInfo != null) {
      newInfo.recordSuppressions(ctorJSDocInfo.getSuppressions());
      for (String param : ctorJSDocInfo.getParameterNames()) {
        newInfo.recordParameter(param, ctorJSDocInfo.getParameterType(param));
      }
    }
    parent.replaceChild(classNode, constructor);

    if (NodeUtil.isStatement(constructor)) {
      constructor.setJSDocInfo(newInfo.build(constructor));
    } else if (parent.isName()) {
      // The constructor function is the RHS of a var statement.
      // Add the JSDoc to the VAR node.
      Node var = parent.getParent();
      var.setJSDocInfo(newInfo.build(var));
    } else if (parent.isAssign()) {
      // The constructor function is the RHS of an assignment.
      // Add the JSDoc to the ASSIGN node.
      parent.setJSDocInfo(newInfo.build(parent));
    } else {
      throw new IllegalStateException("Unexpected parent node " + parent);
    }

    compiler.reportCodeChange();
  }
  private void rewriteGoogDefineClass(Node exprRoot, final ClassDefinition cls) {
    // For simplicity add everything into a block, before adding it to the AST.
    Node block = IR.block();

    // remove the original jsdoc info if it was attached to the value.
    cls.constructor.value.setJSDocInfo(null);
    if (exprRoot.isVar()) {
      // example: var ctr = function(){}
      Node var = IR.var(cls.name.cloneTree(), cls.constructor.value).srcref(exprRoot);
      JSDocInfo mergedClassInfo = mergeJsDocFor(cls, var);
      var.setJSDocInfo(mergedClassInfo);
      block.addChildToBack(var);
    } else {
      // example: ns.ctr = function(){}
      Node assign =
          IR.assign(cls.name.cloneTree(), cls.constructor.value)
              .srcref(exprRoot)
              .setJSDocInfo(cls.constructor.info);

      JSDocInfo mergedClassInfo = mergeJsDocFor(cls, assign);
      assign.setJSDocInfo(mergedClassInfo);

      Node expr = IR.exprResult(assign).srcref(exprRoot);
      block.addChildToBack(expr);
    }

    if (cls.superClass != null) {
      // example: goog.inherits(ctr, superClass)
      block.addChildToBack(
          fixupSrcref(
              IR.exprResult(
                  IR.call(
                          NodeUtil.newQName(compiler, "goog.inherits").srcrefTree(cls.superClass),
                          cls.name.cloneTree(),
                          cls.superClass.cloneTree())
                      .srcref(cls.superClass))));
    }

    for (MemberDefinition def : cls.staticProps) {
      // remove the original jsdoc info if it was attached to the value.
      def.value.setJSDocInfo(null);

      // example: ctr.prop = value
      block.addChildToBack(
          fixupSrcref(
              IR.exprResult(
                  fixupSrcref(
                          IR.assign(
                              IR.getprop(
                                      cls.name.cloneTree(),
                                      IR.string(def.name.getString()).srcref(def.name))
                                  .srcref(def.name),
                              def.value))
                      .setJSDocInfo(def.info))));
      // Handle inner class definitions.
      maybeRewriteClassDefinition(block.getLastChild());
    }

    for (MemberDefinition def : cls.props) {
      // remove the original jsdoc info if it was attached to the value.
      def.value.setJSDocInfo(null);

      // example: ctr.prototype.prop = value
      block.addChildToBack(
          fixupSrcref(
              IR.exprResult(
                  fixupSrcref(
                          IR.assign(
                              IR.getprop(
                                      fixupSrcref(
                                          IR.getprop(
                                              cls.name.cloneTree(),
                                              IR.string("prototype").srcref(def.name))),
                                      IR.string(def.name.getString()).srcref(def.name))
                                  .srcref(def.name),
                              def.value))
                      .setJSDocInfo(def.info))));
      // Handle inner class definitions.
      maybeRewriteClassDefinition(block.getLastChild());
    }

    if (cls.classModifier != null) {
      // Inside the modifier function, replace references to the argument
      // with the class name.
      //   function(cls) { cls.Foo = bar; }
      // becomes
      //   function(cls) { theClassName.Foo = bar; }
      // The cls parameter is unused, but leave it there so that it
      // matches the JsDoc.
      // TODO(tbreisacher): Add a warning if the param is shadowed or reassigned.
      Node argList = cls.classModifier.getFirstChild().getNext();
      Node arg = argList.getFirstChild();
      final String argName = arg.getString();
      NodeTraversal.traverse(
          compiler,
          cls.classModifier.getLastChild(),
          new AbstractPostOrderCallback() {
            @Override
            public void visit(NodeTraversal t, Node n, Node parent) {
              if (n.isName() && n.getString().equals(argName)) {
                parent.replaceChild(n, cls.name.cloneTree());
              }
            }
          });

      block.addChildToBack(
          IR.exprResult(
                  fixupFreeCall(
                      IR.call(cls.classModifier, cls.name.cloneTree()).srcref(cls.classModifier)))
              .srcref(cls.classModifier));
    }

    Node parent = exprRoot.getParent();
    Node stmts = block.removeChildren();
    parent.addChildrenAfter(stmts, exprRoot);
    parent.removeChild(exprRoot);

    compiler.reportCodeChange();
  }
  /**
   * Classes are processed in 3 phases: 1) The class name is extracted. 2) Class members are
   * processed and rewritten. 3) The constructor is built.
   */
  private void visitClass(Node classNode, Node parent) {
    checkClassReassignment(classNode);
    // Collect Metadata
    Node className = classNode.getFirstChild();
    Node superClassName = className.getNext();
    Node classMembers = classNode.getLastChild();

    // This is a statement node. We insert methods of the
    // transpiled class after this node.
    Node insertionPoint;

    if (!superClassName.isEmpty() && !superClassName.isQualifiedName()) {
      compiler.report(JSError.make(superClassName, DYNAMIC_EXTENDS_TYPE));
      return;
    }

    // The fully qualified name of the class, which will be used in the output.
    // May come from the class itself or the LHS of an assignment.
    String fullClassName = null;

    // Whether the constructor function in the output should be anonymous.
    boolean anonymous;

    // If this is a class statement, or a class expression in a simple
    // assignment or var statement, convert it. In any other case, the
    // code is too dynamic, so just call cannotConvert.
    if (NodeUtil.isStatement(classNode)) {
      fullClassName = className.getString();
      anonymous = false;
      insertionPoint = classNode;
    } else if (parent.isAssign() && parent.getParent().isExprResult()) {
      // Add members after the EXPR_RESULT node:
      // example.C = class {}; example.C.prototype.foo = function() {};
      fullClassName = parent.getFirstChild().getQualifiedName();
      if (fullClassName == null) {
        cannotConvert(
            parent,
            "Can only convert classes that are declarations or the right hand"
                + " side of a simple assignment.");
        return;
      }
      anonymous = true;
      insertionPoint = parent.getParent();
    } else if (parent.isName()) {
      // Add members after the 'var' statement.
      // var C = class {}; C.prototype.foo = function() {};
      fullClassName = parent.getString();
      anonymous = true;
      insertionPoint = parent.getParent();
    } else {
      cannotConvert(
          parent,
          "Can only convert classes that are declarations or the right hand"
              + " side of a simple assignment.");
      return;
    }

    if (!className.isEmpty() && !className.getString().equals(fullClassName)) {
      // cannot bind two class names in the case of: var Foo = class Bar {};
      cannotConvertYet(classNode, "named class in an assignment");
      return;
    }

    boolean useUnique = NodeUtil.isStatement(classNode) && !isInFunction(classNode);
    String uniqueFullClassName = useUnique ? getUniqueClassName(fullClassName) : fullClassName;
    String superClassString = superClassName.getQualifiedName();

    Verify.verify(NodeUtil.isStatement(insertionPoint));

    Node constructor = null;
    JSDocInfo ctorJSDocInfo = null;
    // Process all members of the class
    for (Node member : classMembers.children()) {
      if (member.isEmpty()) {
        continue;
      }

      if (member.isMemberDef() && member.getString().equals("constructor")) {
        ctorJSDocInfo = member.getJSDocInfo();
        constructor = member.getFirstChild().detachFromParent();
        if (!anonymous) {
          constructor.replaceChild(constructor.getFirstChild(), className.cloneNode());
        }
      } else {
        Node qualifiedMemberName;
        Node method;
        if (member.isMemberDef()) {
          if (member.isStaticMember()) {
            qualifiedMemberName =
                NodeUtil.newQualifiedNameNode(
                    compiler.getCodingConvention(),
                    Joiner.on(".").join(uniqueFullClassName, member.getString()));
          } else {
            qualifiedMemberName =
                NodeUtil.newQualifiedNameNode(
                    compiler.getCodingConvention(),
                    Joiner.on(".").join(uniqueFullClassName, "prototype", member.getString()));
          }
          method = member.getFirstChild().detachFromParent();
        } else if (member.isComputedProp()) {
          if (member.isStaticMember()) {
            qualifiedMemberName =
                IR.getelem(
                    NodeUtil.newQualifiedNameNode(
                        compiler.getCodingConvention(), uniqueFullClassName),
                    member.removeFirstChild());
          } else {
            qualifiedMemberName =
                IR.getelem(
                    NodeUtil.newQualifiedNameNode(
                        compiler.getCodingConvention(),
                        Joiner.on('.').join(uniqueFullClassName, "prototype")),
                    member.removeFirstChild());
          }
          method = member.getLastChild().detachFromParent();
        } else {
          throw new IllegalStateException("Unexpected class member: " + member);
        }
        Node assign = IR.assign(qualifiedMemberName, method);
        assign.useSourceInfoIfMissingFromForTree(member);

        JSDocInfo info = member.getJSDocInfo();
        if (member.isStaticMember() && NodeUtil.referencesThis(assign.getLastChild())) {
          JSDocInfoBuilder memberDoc;
          if (info == null) {
            memberDoc = new JSDocInfoBuilder(true);
          } else {
            memberDoc = JSDocInfoBuilder.copyFrom(info);
          }
          memberDoc.recordThisType(
              new JSTypeExpression(
                  new Node(Token.BANG, new Node(Token.QMARK)), member.getSourceFileName()));
          info = memberDoc.build(assign);
        }
        if (info != null) {
          info.setAssociatedNode(assign);
          assign.setJSDocInfo(info);
        }

        Node newNode = NodeUtil.newExpr(assign);
        insertionPoint.getParent().addChildAfter(newNode, insertionPoint);
        insertionPoint = newNode;
      }
    }

    // Rewrite constructor
    if (constructor == null) {
      Node body = IR.block();
      if (!superClassName.isEmpty()) {
        Node superCall = baseCall(classNode, "constructor", null);
        body.addChildToBack(IR.exprResult(superCall));
      }
      Node name = anonymous ? IR.name("").srcref(className) : className.detachFromParent();
      constructor =
          IR.function(name, IR.paramList(), body).useSourceInfoIfMissingFromForTree(classNode);
    }
    JSDocInfo classJSDoc = classNode.getJSDocInfo();
    JSDocInfoBuilder newInfo =
        (classJSDoc != null) ? JSDocInfoBuilder.copyFrom(classJSDoc) : new JSDocInfoBuilder(true);

    newInfo.recordConstructor();
    if (!superClassName.isEmpty()) {

      if (newInfo.isInterfaceRecorded()) {
        newInfo.recordExtendedInterface(
            new JSTypeExpression(
                new Node(Token.BANG, IR.string(superClassString)),
                superClassName.getSourceFileName()));
      } else {
        Node inherits =
            IR.call(
                NodeUtil.newQualifiedNameNode(compiler.getCodingConvention(), INHERITS),
                NodeUtil.newQualifiedNameNode(compiler.getCodingConvention(), fullClassName),
                NodeUtil.newQualifiedNameNode(compiler.getCodingConvention(), superClassString));
        Node inheritsCall = IR.exprResult(inherits);
        inheritsCall.useSourceInfoIfMissingFromForTree(classNode);
        Node enclosingStatement = NodeUtil.getEnclosingStatement(classNode);
        enclosingStatement.getParent().addChildAfter(inheritsCall, enclosingStatement);
        newInfo.recordBaseType(
            new JSTypeExpression(
                new Node(Token.BANG, IR.string(superClassString)),
                superClassName.getSourceFileName()));

        Node copyProps =
            IR.call(
                NodeUtil.newQualifiedNameNode(compiler.getCodingConvention(), COPY_PROP),
                NodeUtil.newQualifiedNameNode(compiler.getCodingConvention(), fullClassName),
                NodeUtil.newQualifiedNameNode(compiler.getCodingConvention(), superClassString));
        copyProps.useSourceInfoIfMissingFromForTree(classNode);
        enclosingStatement
            .getParent()
            .addChildAfter(IR.exprResult(copyProps).srcref(classNode), enclosingStatement);
      }
    }

    // Classes are @struct by default.
    if (!newInfo.isUnrestrictedRecorded()
        && !newInfo.isDictRecorded()
        && !newInfo.isStructRecorded()) {
      newInfo.recordStruct();
    }

    if (ctorJSDocInfo != null) {
      newInfo.recordSuppressions(ctorJSDocInfo.getSuppressions());
      for (String param : ctorJSDocInfo.getParameterNames()) {
        newInfo.recordParameter(param, ctorJSDocInfo.getParameterType(param));
      }
    }
    insertionPoint = constructor;

    if (NodeUtil.isStatement(classNode)) {
      constructor.getFirstChild().setString("");
      Node ctorVar = IR.var(IR.name(fullClassName), constructor);
      ctorVar.useSourceInfoIfMissingFromForTree(classNode);
      parent.replaceChild(classNode, ctorVar);
    } else {
      parent.replaceChild(classNode, constructor);
    }

    if (NodeUtil.isStatement(constructor)) {
      insertionPoint.setJSDocInfo(newInfo.build(insertionPoint));
    } else if (parent.isName()) {
      // The constructor function is the RHS of a var statement.
      // Add the JSDoc to the VAR node.
      Node var = parent.getParent();
      var.setJSDocInfo(newInfo.build(var));
    } else if (constructor.getParent().isName()) {
      // Is a newly created VAR node.
      Node var = constructor.getParent().getParent();
      var.setJSDocInfo(newInfo.build(var));
    } else if (parent.isAssign()) {
      // The constructor function is the RHS of an assignment.
      // Add the JSDoc to the ASSIGN node.
      parent.setJSDocInfo(newInfo.build(parent));
    } else {
      throw new IllegalStateException("Unexpected parent node " + parent);
    }

    compiler.reportCodeChange();
  }