/**
   * Replaces a GETPROP a.b.c with a NAME a$b$c.
   *
   * @param alias A flattened prefix name (e.g. "a$b")
   * @param n The GETPROP node corresponding to the original name (e.g. "a.b")
   * @param parent {@code n}'s parent
   * @param originalName String version of the property name.
   */
  private void flattenNameRef(String alias, Node n, Node parent, String originalName) {
    Preconditions.checkArgument(
        n.isGetProp(), "Expected GETPROP, found %s. Node: %s", Token.name(n.getType()), n);

    // BEFORE:
    //   getprop
    //     getprop
    //       name a
    //       string b
    //     string c
    // AFTER:
    //   name a$b$c
    Node ref = NodeUtil.newName(compiler, alias, n, originalName);
    NodeUtil.copyNameAnnotations(n.getLastChild(), ref);
    if (parent.isCall() && n == parent.getFirstChild()) {
      // The node was a call target, we are deliberately flatten these as
      // we node the "this" isn't provided by the namespace. Mark it as such:
      parent.putBooleanProp(Node.FREE_CALL, true);
    }

    TypeI type = n.getTypeI();
    if (type != null) {
      ref.setTypeI(type);
    }

    parent.replaceChild(n, ref);
    compiler.reportCodeChange();
  }
예제 #2
0
 private void validateObjectLiteralKeyName(Node n) {
   if (n.isQuotedString()) {
     try {
       // Validate that getString doesn't throw
       n.getString();
     } catch (UnsupportedOperationException e) {
       violation("getString failed for" + Token.name(n.getType()), n);
     }
   } else {
     validateNonEmptyString(n);
   }
 }
예제 #3
0
 private void validateSwitchMember(Node n) {
   switch (n.getType()) {
     case Token.CASE:
       validateCase(n);
       return;
     case Token.DEFAULT_CASE:
       validateDefaultCase(n);
       return;
     default:
       violation("Expected switch member but was " + Token.name(n.getType()), n);
   }
 }
예제 #4
0
 private void validateClassMember(Node n) {
   if (n.getType() == Token.MEMBER_FUNCTION_DEF
       || n.getType() == Token.GETTER_DEF
       || n.getType() == Token.SETTER_DEF) {
     validateChildCount(n);
     validateFunctionExpression(n.getFirstChild());
   } else if (n.getType() == Token.MEMBER_VARIABLE_DEF) {
     validateChildless(n);
   } else if (n.isComputedProp()) {
     validateComputedPropClassMethod(n);
   } else if (n.isEmpty()) {
     // Empty is allowed too.
   } else {
     violation("Class contained member of invalid type " + Token.name(n.getType()), n);
   }
 }
예제 #5
0
 private void validateSpread(Node n) {
   validateNodeType(Token.SPREAD, n);
   validateChildCount(n);
   Node parent = n.getParent();
   switch (parent.getType()) {
     case Token.CALL:
     case Token.NEW:
       if (n == parent.getFirstChild()) {
         violation("SPREAD node is not callable.", n);
       }
       break;
     case Token.ARRAYLIT:
       break;
     default:
       violation(
           "SPREAD node should not be the child of a " + Token.name(parent.getType()) + " node.",
           n);
   }
 }
예제 #6
0
 /**
  * Arrow functions must be visited pre-order in order to rewrite the references to {@code this}
  * correctly. Everything else is translated post-order in {@link #visit}.
  */
 @Override
 public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
   switch (n.getType()) {
     case Token.FUNCTION:
       if (n.isArrowFunction()) {
         visitArrowFunction(t, n);
       }
       break;
     case Token.ARRAY_COMP:
     case Token.ARRAY_PATTERN:
     case Token.FOR_OF:
     case Token.OBJECT_PATTERN:
     case Token.SUPER:
       cannotConvertYet(n, Token.name(n.getType()));
       // Don't bother visiting the children of a node if we
       // already know we can't convert the node itself.
       return false;
   }
   return true;
 }
예제 #7
0
 private void validateNameDeclarationChild(int type, Node n) {
   if (n.isName()) {
     // Don't use validateName here since this NAME node may have
     // a child.
     validateNonEmptyString(n);
     validateMaximumChildCount(n, 1);
     if (n.hasChildren()) {
       validateExpression(n.getFirstChild());
     }
   } else if (n.isArrayPattern()) {
     validateArrayPattern(type, n);
   } else if (n.isObjectPattern()) {
     validateObjectPattern(type, n);
   } else if (n.isDefaultValue()) {
     validateDefaultValue(type, n);
   } else if (n.isComputedProp()) {
     validateObjectPatternComputedPropKey(type, n);
   } else {
     violation("Invalid child for " + Token.name(type) + " node", n);
   }
 }
예제 #8
0
 private void validateObjectLitKey(Node n) {
   switch (n.getType()) {
     case Token.GETTER_DEF:
       validateObjectLitGetKey(n);
       return;
     case Token.SETTER_DEF:
       validateObjectLitSetKey(n);
       return;
     case Token.STRING_KEY:
       validateObjectLitStringKey(n);
       return;
     case Token.MEMBER_FUNCTION_DEF:
       validateClassMember(n);
       if (n.isStaticMember()) {
         violation("Keys in an object literal should not be static.", n);
       }
       return;
     case Token.COMPUTED_PROP:
       validateObjectLitComputedPropKey(n);
       return;
     default:
       violation("Expected object literal key expression but was " + Token.name(n.getType()), n);
   }
 }
예제 #9
0
  @Override
  public void visit(NodeTraversal t, Node n, Node parent) {
    // VOID nodes appear when there are extra semicolons at the BLOCK level.
    // I've been unable to think of any cases where this indicates a bug,
    // and apparently some people like keeping these semicolons around,
    // so we'll allow it.
    if (n.isEmpty() || n.isComma()) {
      return;
    }

    if (parent == null) {
      return;
    }

    int pt = parent.getType();
    if (pt == Token.COMMA) {
      Node gramps = parent.getParent();
      if (gramps.isCall() && parent == gramps.getFirstChild()) {
        // Semantically, a direct call to eval is different from an indirect
        // call to an eval. See Ecma-262 S15.1.2.1. So it's ok for the first
        // expression to a comma to be a no-op if it's used to indirect
        // an eval.
        if (n == parent.getFirstChild()
            && parent.getChildCount() == 2
            && n.getNext().isName()
            && "eval".equals(n.getNext().getString())) {
          return;
        }
      }

      if (n == parent.getLastChild()) {
        for (Node an : parent.getAncestors()) {
          int ancestorType = an.getType();
          if (ancestorType == Token.COMMA) continue;
          if (ancestorType != Token.EXPR_RESULT && ancestorType != Token.BLOCK) return;
          else break;
        }
      }
    } else if (pt != Token.EXPR_RESULT && pt != Token.BLOCK) {
      if (pt == Token.FOR
          && parent.getChildCount() == 4
          && (n == parent.getFirstChild() || n == parent.getFirstChild().getNext().getNext())) {
        // Fall through and look for warnings for the 1st and 3rd child
        // of a for.
      } else {
        return; // it might be ok to not have a side-effect
      }
    }

    boolean isSimpleOp = NodeUtil.isSimpleOperatorType(n.getType());
    if (isSimpleOp || !NodeUtil.mayHaveSideEffects(n, t.getCompiler())) {
      if (n.isQualifiedName() && n.getJSDocInfo() != null) {
        // This no-op statement was there so that JSDoc information could
        // be attached to the name. This check should not complain about it.
        return;
      } else if (n.isExprResult()) {
        // we already reported the problem when we visited the child.
        return;
      }

      String msg = "This code lacks side-effects. Is there a bug?";
      if (n.isString()) {
        msg = "Is there a missing '+' on the previous line?";
      } else if (isSimpleOp) {
        msg =
            "The result of the '"
                + Token.name(n.getType()).toLowerCase()
                + "' operator is not being used.";
      }

      t.getCompiler().report(t.makeError(n, level, USELESS_CODE_ERROR, msg));
      // TODO(johnlenz): determine if it is necessary to
      // try to protect side-effect free statements as well.
      if (!NodeUtil.isStatement(n)) {
        problemNodes.add(n);
      }
    }
  }
 private JSType getTypeFromCommentHelper(
     Node n, DeclaredTypeRegistry registry, ImmutableList<String> typeParameters)
     throws UnknownTypeException {
   Preconditions.checkNotNull(n);
   if (typeParameters == null) {
     typeParameters = ImmutableList.of();
   }
   switch (n.getType()) {
     case Token.LC:
       return getRecordTypeHelper(n, registry, typeParameters);
     case Token.EMPTY: // for function types that don't declare a return type
       return JSType.UNKNOWN;
     case Token.VOID:
       // TODO(dimvar): void can be represented in 2 ways: Token.VOID and a
       // Token.STRING whose getString() is "void".
       // Change jsdoc parsing to only have one representation.
       return JSType.UNDEFINED;
     case Token.LB:
       warnings.add(JSError.make(n, BAD_ARRAY_TYPE_SYNTAX));
       return JSType.UNKNOWN;
     case Token.STRING:
       return getNamedTypeHelper(n, registry, typeParameters);
     case Token.PIPE:
       {
         // The way JSType.join works, Subtype|Supertype is equal to Supertype,
         // so when programmers write un-normalized unions, we normalize them
         // silently. We may also want to warn.
         JSType union = JSType.BOTTOM;
         for (Node child = n.getFirstChild(); child != null; child = child.getNext()) {
           // TODO(dimvar): When the union has many things, we join and throw
           // away types, except the result of the last join. Very inefficient.
           // Consider optimizing.
           JSType nextType = getTypeFromCommentHelper(child, registry, typeParameters);
           if (nextType.isUnknown()) {
             return JSType.UNKNOWN;
           }
           JSType nextUnion = JSType.join(union, nextType);
           if (nextUnion.isBottom()) {
             warnings.add(
                 JSError.make(n, UNION_IS_UNINHABITABLE, nextType.toString(), union.toString()));
             return JSType.UNKNOWN;
           }
           union = nextUnion;
         }
         return union;
       }
     case Token.BANG:
       {
         JSType nullableType =
             getTypeFromCommentHelper(n.getFirstChild(), registry, typeParameters);
         if (nullableType.isTypeVariable()) {
           warnings.add(JSError.make(n, CANNOT_MAKE_TYPEVAR_NON_NULL));
         }
         return nullableType.removeType(JSType.NULL);
       }
     case Token.QMARK:
       {
         Node child = n.getFirstChild();
         if (child == null) {
           return JSType.UNKNOWN;
         } else {
           return JSType.join(
               JSType.NULL, getTypeFromCommentHelper(child, registry, typeParameters));
         }
       }
     case Token.STAR:
       return JSType.TOP;
     case Token.FUNCTION:
       return getFunTypeHelper(n, registry, typeParameters);
     default:
       throw new IllegalArgumentException(
           "Unsupported type exp: " + Token.name(n.getType()) + " " + n.toStringTree());
   }
 }
예제 #11
0
 private void validateAssignmentTarget(Node n) {
   if (!n.isValidAssignmentTarget()) {
     violation("Expected assignment target expression but was " + Token.name(n.getType()), n);
   }
 }
예제 #12
0
 public void validateStatement(Node n) {
   switch (n.getType()) {
     case Token.LABEL:
       validateLabel(n);
       return;
     case Token.BLOCK:
       validateBlock(n);
       return;
     case Token.FUNCTION:
       validateFunctionStatement(n);
       return;
     case Token.WITH:
       validateWith(n);
       return;
     case Token.FOR:
       validateFor(n);
       return;
     case Token.FOR_OF:
       validateForOf(n);
       return;
     case Token.WHILE:
       validateWhile(n);
       return;
     case Token.DO:
       validateDo(n);
       return;
     case Token.SWITCH:
       validateSwitch(n);
       return;
     case Token.IF:
       validateIf(n);
       return;
     case Token.VAR:
     case Token.LET:
     case Token.CONST:
       validateNameDeclarationHelper(n.getType(), n);
       return;
     case Token.EXPR_RESULT:
       validateExprStmt(n);
       return;
     case Token.RETURN:
       validateReturn(n);
       return;
     case Token.THROW:
       validateThrow(n);
       return;
     case Token.TRY:
       validateTry(n);
       return;
     case Token.BREAK:
       validateBreak(n);
       return;
     case Token.CONTINUE:
       validateContinue(n);
       return;
     case Token.EMPTY:
       validateChildless(n);
       return;
     case Token.DEBUGGER:
       validateChildless(n);
       return;
     case Token.CLASS:
       validateClassDeclaration(n);
       return;
     case Token.IMPORT:
       validateImport(n);
       return;
     case Token.EXPORT:
       validateExport(n);
       return;
     default:
       violation("Expected statement but was " + Token.name(n.getType()) + ".", n);
   }
 }
  private void visitArrayPattern(NodeTraversal t, Node arrayPattern, Node parent) {
    Node rhs, nodeToDetach;
    if (NodeUtil.isNameDeclaration(parent) && !NodeUtil.isEnhancedFor(parent.getParent())) {
      // The array pattern is the only child, because Es6SplitVariableDeclarations
      // has already run.
      Preconditions.checkState(arrayPattern.getNext() == null);
      rhs = arrayPattern.getLastChild();
      nodeToDetach = parent;
    } else if (parent.isAssign()) {
      rhs = arrayPattern.getNext();
      nodeToDetach = parent.getParent();
      Preconditions.checkState(nodeToDetach.isExprResult());
    } else if (parent.isArrayPattern() || parent.isDefaultValue() || parent.isStringKey()) {
      // This is a nested array pattern. Don't do anything now; we'll visit it
      // after visiting the parent.
      return;
    } else if (NodeUtil.isEnhancedFor(parent) || NodeUtil.isEnhancedFor(parent.getParent())) {
      visitDestructuringPatternInEnhancedFor(arrayPattern);
      return;
    } else {
      Preconditions.checkState(parent.isCatch() || parent.isForOf());
      cannotConvertYet(
          arrayPattern, "ARRAY_PATTERN that is a child of a " + Token.name(parent.getType()));
      return;
    }

    // Convert 'var [x, y] = rhs' to:
    // var temp = rhs;
    // var x = temp[0];
    // var y = temp[1];
    String tempVarName = DESTRUCTURING_TEMP_VAR + (destructuringVarCounter++);
    Node tempDecl =
        IR.var(IR.name(tempVarName), rhs.detachFromParent())
            .useSourceInfoIfMissingFromForTree(arrayPattern);
    nodeToDetach.getParent().addChildBefore(tempDecl, nodeToDetach);

    int i = 0;
    for (Node child = arrayPattern.getFirstChild(), next; child != null; child = next, i++) {
      next = child.getNext();
      if (child.isEmpty()) {
        continue;
      }

      Node newLHS, newRHS;
      if (child.isDefaultValue()) {
        Node getElem = IR.getelem(IR.name(tempVarName), IR.number(i));
        //   [x = defaultValue] = rhs;
        // becomes
        //   var temp = rhs;
        //   x = (temp[0] === undefined) ? defaultValue : temp[0];
        newLHS = child.getFirstChild().detachFromParent();
        newRHS = defaultValueHook(getElem, child.getLastChild().detachFromParent());
      } else if (child.isRest()) {
        newLHS = child.detachFromParent();
        newLHS.setType(Token.NAME);
        // [].slice.call(temp, i)
        newRHS =
            IR.call(
                IR.getprop(IR.getprop(IR.arraylit(), IR.string("slice")), IR.string("call")),
                IR.name(tempVarName),
                IR.number(i));
      } else {
        newLHS = child.detachFromParent();
        newRHS = IR.getelem(IR.name(tempVarName), IR.number(i));
      }
      Node newNode;
      if (parent.isAssign()) {
        Node assignment = IR.assign(newLHS, newRHS);
        newNode = IR.exprResult(assignment);
      } else {
        newNode = IR.declaration(newLHS, newRHS, parent.getType());
      }
      newNode.useSourceInfoIfMissingFromForTree(arrayPattern);

      nodeToDetach.getParent().addChildBefore(newNode, nodeToDetach);
      // Explicitly visit the LHS of the new node since it may be a nested
      // destructuring pattern.
      visit(t, newLHS, newLHS.getParent());
    }
    nodeToDetach.detachFromParent();
    compiler.reportCodeChange();
  }
예제 #14
0
  public void validateExpression(Node n) {
    switch (n.getType()) {
        // Childless expressions
      case Token.FALSE:
      case Token.NULL:
      case Token.SUPER:
      case Token.THIS:
      case Token.TRUE:
        validateChildless(n);
        return;

        // General unary ops
      case Token.DELPROP:
      case Token.POS:
      case Token.NEG:
      case Token.NOT:
      case Token.INC:
      case Token.DEC:
      case Token.TYPEOF:
      case Token.VOID:
      case Token.BITNOT:
      case Token.CAST:
        validateUnaryOp(n);
        return;

        // General binary ops
      case Token.COMMA:
      case Token.OR:
      case Token.AND:
      case Token.BITOR:
      case Token.BITXOR:
      case Token.BITAND:
      case Token.EQ:
      case Token.NE:
      case Token.SHEQ:
      case Token.SHNE:
      case Token.LT:
      case Token.GT:
      case Token.LE:
      case Token.GE:
      case Token.INSTANCEOF:
      case Token.IN:
      case Token.LSH:
      case Token.RSH:
      case Token.URSH:
      case Token.SUB:
      case Token.ADD:
      case Token.MUL:
      case Token.MOD:
      case Token.DIV:
        validateBinaryOp(n);
        return;

        // Assignments
      case Token.ASSIGN:
      case Token.ASSIGN_BITOR:
      case Token.ASSIGN_BITXOR:
      case Token.ASSIGN_BITAND:
      case Token.ASSIGN_LSH:
      case Token.ASSIGN_RSH:
      case Token.ASSIGN_URSH:
      case Token.ASSIGN_ADD:
      case Token.ASSIGN_SUB:
      case Token.ASSIGN_MUL:
      case Token.ASSIGN_DIV:
      case Token.ASSIGN_MOD:
        validateAssignmentExpression(n);
        return;

      case Token.HOOK:
        validateTrinaryOp(n);
        return;

        // Node types that require special handling
      case Token.STRING:
        validateString(n);
        return;

      case Token.NUMBER:
        validateNumber(n);
        return;

      case Token.NAME:
        validateName(n);
        return;

      case Token.GETELEM:
        validateBinaryOp(n);
        return;

      case Token.GETPROP:
        validateGetProp(n);
        return;

      case Token.ARRAYLIT:
        validateArrayLit(n);
        return;

      case Token.OBJECTLIT:
        validateObjectLit(n);
        return;

      case Token.REGEXP:
        validateRegExpLit(n);
        return;

      case Token.CALL:
        validateCall(n);
        return;

      case Token.SPREAD:
        validateSpread(n);
        return;

      case Token.NEW:
        validateNew(n);
        return;

      case Token.FUNCTION:
        validateFunctionExpression(n);
        return;

      case Token.CLASS:
        validateClass(n);
        return;

      case Token.TEMPLATELIT:
        validateTemplateLit(n);
        return;

      case Token.YIELD:
        validateYield(n);
        return;

      default:
        violation("Expected expression but was " + Token.name(n.getType()), n);
    }
  }
예제 #15
0
 private void validateChildCount(Node n) {
   int expectedArity = Token.arity(n.getType());
   if (expectedArity != -1) {
     validateChildCount(n, expectedArity);
   }
 }
예제 #16
0
 private void validateNodeType(int type, Node n) {
   if (n.getType() != type) {
     violation("Expected " + Token.name(type) + " but was " + Token.name(n.getType()), n);
   }
 }
예제 #17
0
 /** Sanity check that our aliases are not already defined by someone else. */
 private void visitNameNode(Node n) {
   if (isAliasDefinition(n)) {
     throw new IllegalStateException("Existing alias definition for " + Token.name(n.getType()));
   }
 }
  private void visitObjectPattern(NodeTraversal t, Node objectPattern, Node parent) {
    Node rhs, nodeToDetach;
    if (NodeUtil.isNameDeclaration(parent) && !NodeUtil.isEnhancedFor(parent.getParent())) {
      rhs = objectPattern.getLastChild();
      nodeToDetach = parent;
    } else if (parent.isAssign() && parent.getParent().isExprResult()) {
      rhs = parent.getLastChild();
      nodeToDetach = parent.getParent();
    } else if (parent.isStringKey() || parent.isArrayPattern() || parent.isDefaultValue()) {
      // Nested object pattern; do nothing. We will visit it after rewriting the parent.
      return;
    } else if (NodeUtil.isEnhancedFor(parent) || NodeUtil.isEnhancedFor(parent.getParent())) {
      visitDestructuringPatternInEnhancedFor(objectPattern);
      return;
    } else {
      Preconditions.checkState(parent.isCatch(), parent);
      cannotConvertYet(
          objectPattern, "OBJECT_PATTERN that is a child of a " + Token.name(parent.getType()));
      return;
    }

    // Convert 'var {a: b, c: d} = rhs' to:
    // var temp = rhs;
    // var b = temp.a;
    // var d = temp.c;
    String tempVarName = DESTRUCTURING_TEMP_VAR + (destructuringVarCounter++);
    Node tempDecl =
        IR.var(IR.name(tempVarName), rhs.detachFromParent())
            .useSourceInfoIfMissingFromForTree(objectPattern);
    nodeToDetach.getParent().addChildBefore(tempDecl, nodeToDetach);

    for (Node child = objectPattern.getFirstChild(), next; child != null; child = next) {
      next = child.getNext();

      Node newLHS, newRHS;
      if (child.isStringKey()) {
        Preconditions.checkState(child.hasChildren());
        Node getprop =
            new Node(
                child.isQuotedString() ? Token.GETELEM : Token.GETPROP,
                IR.name(tempVarName),
                IR.string(child.getString()));

        Node value = child.removeFirstChild();
        if (!value.isDefaultValue()) {
          newLHS = value;
          newRHS = getprop;
        } else {
          newLHS = value.removeFirstChild();
          Node defaultValue = value.removeFirstChild();
          newRHS = defaultValueHook(getprop, defaultValue);
        }
      } else if (child.isComputedProp()) {
        if (child.getLastChild().isDefaultValue()) {
          newLHS = child.getLastChild().removeFirstChild();
          Node getelem = IR.getelem(IR.name(tempVarName), child.removeFirstChild());

          String intermediateTempVarName = DESTRUCTURING_TEMP_VAR + (destructuringVarCounter++);
          Node intermediateDecl = IR.var(IR.name(intermediateTempVarName), getelem);
          intermediateDecl.useSourceInfoIfMissingFromForTree(child);
          nodeToDetach.getParent().addChildBefore(intermediateDecl, nodeToDetach);

          newRHS =
              defaultValueHook(
                  IR.name(intermediateTempVarName), child.getLastChild().removeFirstChild());
        } else {
          newRHS = IR.getelem(IR.name(tempVarName), child.removeFirstChild());
          newLHS = child.removeFirstChild();
        }
      } else if (child.isDefaultValue()) {
        newLHS = child.removeFirstChild();
        Node defaultValue = child.removeFirstChild();
        Node getprop = IR.getprop(IR.name(tempVarName), IR.string(newLHS.getString()));
        newRHS = defaultValueHook(getprop, defaultValue);
      } else {
        throw new IllegalStateException("Unexpected OBJECT_PATTERN child: " + child);
      }

      Node newNode;
      if (NodeUtil.isNameDeclaration(parent)) {
        newNode = IR.declaration(newLHS, newRHS, parent.getType());
      } else if (parent.isAssign()) {
        newNode = IR.exprResult(IR.assign(newLHS, newRHS));
      } else {
        throw new IllegalStateException("not reached");
      }
      newNode.useSourceInfoIfMissingFromForTree(child);

      nodeToDetach.getParent().addChildBefore(newNode, nodeToDetach);

      // Explicitly visit the LHS of the new node since it may be a nested
      // destructuring pattern.
      visit(t, newLHS, newLHS.getParent());
    }

    nodeToDetach.detachFromParent();
    compiler.reportCodeChange();
  }