/** * 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(); }
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); } }
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); } }
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); } }
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); } }
/** * 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; }
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); } }
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); } }
@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()); } }
private void validateAssignmentTarget(Node n) { if (!n.isValidAssignmentTarget()) { violation("Expected assignment target expression but was " + Token.name(n.getType()), n); } }
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(); }
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); } }
private void validateChildCount(Node n) { int expectedArity = Token.arity(n.getType()); if (expectedArity != -1) { validateChildCount(n, expectedArity); } }
private void validateNodeType(int type, Node n) { if (n.getType() != type) { violation("Expected " + Token.name(type) + " but was " + Token.name(n.getType()), n); } }
/** 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(); }