Node tryFuseStatementsAggressively(Node n) { if (!NodeUtil.isStatementBlock(n)) { return n; } Node cur = n.getFirstChild(); while (cur != null) { if (!cur.isExprResult()) { cur = cur.getNext(); continue; } Node next = cur.getNext(); while (next != null && next.isExprResult()) { next = next.getNext(); } if (cur.getNext() != next) { cur = fuseIntoOneStatement(n, cur, next); reportCodeChange(); } if (cur.isExprResult() && next != null && isFusableControlStatement(next)) { fuseExpressionIntoControlFlowStatement(cur, next); reportCodeChange(); next = next.getNext(); } cur = next; } return n; }
void visitSubtree(Node n, Node parent) { if (n.isCall()) { boolean isModuleDetected = codingConvention.extractIsModuleFile(n, parent); if (isModuleDetected) { this.isModuleFile = true; } String require = codingConvention.extractClassNameIfRequire(n, parent); if (require != null) { requires.add(require); } String provide = codingConvention.extractClassNameIfProvide(n, parent); if (provide != null) { provides.add(provide); } return; } else if (parent != null && !parent.isExprResult() && !parent.isScript()) { return; } for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { visitSubtree(child, n); } }
/** * @return Whether the node is an expression result of an assignment to a property of * PolymerElement. */ private boolean isPolymerElementPropExpr(Node value) { return value != null && value.isExprResult() && value.getFirstChild().getFirstChild().isGetProp() && NodeUtil.getRootOfQualifiedName(value.getFirstChild().getFirstChild()) .matchesQualifiedName(POLYMER_ELEMENT_NAME); }
private static void fuseExpressionIntoControlFlowStatement(Node before, Node control) { Preconditions.checkArgument(before.isExprResult(), "before must be expression result"); // Now we are just left with two statements. The comma tree of the first // n - 1 statements (which can be used in an expression) and the last // statement. We perform specific fusion based on the last statement's type. switch (control.getToken()) { case IF: case RETURN: case THROW: case SWITCH: case EXPR_RESULT: before.getParent().removeChild(before); fuseExpressionIntoFirstChild(before.removeFirstChild(), control); return; case FOR: before.getParent().removeChild(before); if (NodeUtil.isForIn(control)) { fuseExpressionIntoSecondChild(before.removeFirstChild(), control); } else { fuseExpressionIntoFirstChild(before.removeFirstChild(), control); } return; case LABEL: fuseExpressionIntoControlFlowStatement(before, control.getLastChild()); return; case BLOCK: fuseExpressionIntoControlFlowStatement(before, control.getFirstChild()); return; default: throw new IllegalStateException("Statement fusion missing."); } }
@Override public void visit(NodeTraversal t, Node n, Node parent) { if (!n.isCall() || !n.getFirstChild().isName()) { return; } Node callTarget = n.getFirstChild(); String callName = callTarget.getString(); if (startMarkerName.equals(callName)) { if (!parent.isExprResult()) { compiler.report(t.makeError(n, INVALID_MARKER_USAGE, startMarkerName)); return; } markerStack.push(parent); return; } if (!endMarkerName.equals(callName)) { return; } Node endMarkerNode = parent; if (!endMarkerNode.isExprResult()) { compiler.report(t.makeError(n, INVALID_MARKER_USAGE, endMarkerName)); return; } if (markerStack.isEmpty()) { compiler.report(t.makeError(n, UNMATCHED_END_MARKER, startMarkerName, endMarkerName)); return; } Node startMarkerNode = markerStack.pop(); if (endMarkerNode.getParent() != startMarkerNode.getParent()) { // The end marker isn't in the same block as the start marker. compiler.report(t.makeError(n, UNMATCHED_END_MARKER, startMarkerName, endMarkerName)); return; } // This is a valid marker set add it to the list of markers to process. validMarkers.add(new Marker(startMarkerNode, endMarkerNode)); }
@Override void add(Node n, Context context) { Node parent = n.getParent(); if (parent != null && (parent.isBlock() || parent.isScript())) { if (n.isFunction()) { add(getFunctionAnnotation(n)); } else if (n.isExprResult() && n.getFirstChild().isAssign()) { Node rhs = n.getFirstChild().getLastChild(); add(getTypeAnnotation(rhs)); } else if (n.isVar() && n.getFirstChild().getFirstChild() != null) { add(getTypeAnnotation(n.getFirstChild().getFirstChild())); } } super.add(n, context); }
private boolean canFuseIntoOneStatement(Node block) { // If we are favoring semi-colon, we shouldn't fuse script blocks. if (!favorsCommaOverSemiColon && !block.isBlock()) { return false; } // Nothing to do here. if (!block.hasChildren() || block.hasOneChild()) { return false; } Node last = block.getLastChild(); for (Node c = block.getFirstChild(); c != null; c = c.getNext()) { if (!c.isExprResult() && c != last) { return false; } } return isFusableControlStatement(last); }
/** * Updates the initial assignment to a collapsible property at global scope by changing it to a * variable declaration (e.g. a.b = 1 -> var a$b = 1). The property's value may either be a * primitive or an object literal or function whose properties aren't collapsible. * * @param alias The flattened property name (e.g. "a$b") * @param refName The name for the reference being updated. * @param ref An object containing information about the assignment getting updated */ private void updateSimpleDeclaration(String alias, Name refName, Ref ref) { Node rvalue = ref.node.getNext(); Node parent = ref.node.getParent(); Node grandparent = parent.getParent(); Node greatGrandparent = grandparent.getParent(); if (rvalue != null && rvalue.isFunction()) { checkForHosedThisReferences(rvalue, refName.docInfo, refName); } // Create the new alias node. Node nameNode = NodeUtil.newName(compiler, alias, grandparent.getFirstChild(), refName.getFullName()); NodeUtil.copyNameAnnotations(ref.node.getLastChild(), nameNode); if (grandparent.isExprResult()) { // BEFORE: a.b.c = ...; // exprstmt // assign // getprop // getprop // name a // string b // string c // NODE // AFTER: var a$b$c = ...; // var // name a$b$c // NODE // Remove the r-value (NODE). parent.removeChild(rvalue); nameNode.addChildToFront(rvalue); Node varNode = IR.var(nameNode); greatGrandparent.replaceChild(grandparent, varNode); } else { // This must be a complex assignment. Preconditions.checkNotNull(ref.getTwin()); // BEFORE: // ... (x.y = 3); // // AFTER: // var x$y; // ... (x$y = 3); Node current = grandparent; Node currentParent = grandparent.getParent(); for (; !currentParent.isScript() && !currentParent.isBlock(); current = currentParent, currentParent = currentParent.getParent()) {} // Create a stub variable declaration right // before the current statement. Node stubVar = IR.var(nameNode.cloneTree()).useSourceInfoIfMissingFrom(nameNode); currentParent.addChildBefore(stubVar, current); parent.replaceChild(ref.node, nameNode); } compiler.reportCodeChange(); }
@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); } } }
@Override public void visit(NodeTraversal t, Node n, Node parent) { JSDocInfo docInfo = n.getJSDocInfo(); if (docInfo != null && docInfo.isExport()) { if (parent.isAssign() && (n.isFunction() || n.isClass())) { JSDocInfo parentInfo = parent.getJSDocInfo(); if (parentInfo != null && parentInfo.isExport()) { // ScopedAliases produces export annotations on both the function/class // node and assign node, we only want to visit the assign node. return; } } String export = null; GenerateNodeContext context = null; switch (n.getType()) { case Token.FUNCTION: case Token.CLASS: if (parent.isScript()) { export = NodeUtil.getName(n); context = new GenerateNodeContext(n, Mode.EXPORT); } break; case Token.MEMBER_FUNCTION_DEF: export = n.getString(); context = new GenerateNodeContext(n, Mode.EXPORT); break; case Token.ASSIGN: Node grandparent = parent.getParent(); if (parent.isExprResult() && !n.getLastChild().isAssign()) { if (grandparent != null && grandparent.isScript() && n.getFirstChild().isQualifiedName()) { export = n.getFirstChild().getQualifiedName(); context = new GenerateNodeContext(n, Mode.EXPORT); } else if (allowLocalExports && n.getFirstChild().isGetProp()) { Node target = n.getFirstChild(); export = target.getLastChild().getString(); context = new GenerateNodeContext(n, Mode.EXTERN); } } break; case Token.VAR: case Token.LET: case Token.CONST: if (parent.isScript()) { if (n.getFirstChild().hasChildren() && !n.getFirstChild().getFirstChild().isAssign()) { export = n.getFirstChild().getString(); context = new GenerateNodeContext(n, Mode.EXPORT); } } break; case Token.GETPROP: if (allowLocalExports && parent.isExprResult()) { export = n.getLastChild().getString(); context = new GenerateNodeContext(n, Mode.EXTERN); } break; case Token.STRING_KEY: case Token.GETTER_DEF: case Token.SETTER_DEF: if (allowLocalExports) { export = n.getString(); context = new GenerateNodeContext(n, Mode.EXTERN); } break; } if (export != null) { exports.put(export, context); } else { // Don't produce extra warnings for functions values of object literals if (!n.isFunction() || !NodeUtil.isObjectLitKey(parent)) { if (allowLocalExports) { compiler.report(t.makeError(n, EXPORT_ANNOTATION_NOT_ALLOWED)); } else { compiler.report(t.makeError(n, NON_GLOBAL_ERROR)); } } } } }
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(); }