/** * Removes all CALL nodes in the given TweakInfos, replacing calls to getter functions with the * tweak's default value. */ private boolean stripAllCalls(Map<String, TweakInfo> tweakInfos) { for (TweakInfo tweakInfo : tweakInfos.values()) { boolean isRegistered = tweakInfo.isRegistered(); for (TweakFunctionCall functionCall : tweakInfo.functionCalls) { Node callNode = functionCall.callNode; Node parent = callNode.getParent(); if (functionCall.tweakFunc.isGetterFunction()) { Node newValue; if (isRegistered) { newValue = tweakInfo.getDefaultValueNode().cloneNode(); } else { // When we find a getter of an unregistered tweak, there has // already been a warning about it, so now just use a default // value when stripping. TweakFunction registerFunction = functionCall.tweakFunc.registerFunction; newValue = registerFunction.createDefaultValueNode(); } parent.replaceChild(callNode, newValue); } else { Node voidZeroNode = IR.voidNode(IR.number(0).srcref(callNode)).srcref(callNode); parent.replaceChild(callNode, voidZeroNode); } } } return !tweakInfos.isEmpty(); }
/** Replace the current assign with its right hand side. */ void remove() { Node parent = assignNode.getParent(); if (mayHaveSecondarySideEffects) { Node replacement = assignNode.getLastChild().detachFromParent(); // Aggregate any expressions in GETELEMs. for (Node current = assignNode.getFirstChild(); current.getType() != Token.NAME; current = current.getFirstChild()) { if (current.getType() == Token.GETELEM) { replacement = new Node(Token.COMMA, current.getLastChild().detachFromParent(), replacement); replacement.copyInformationFrom(current); } } parent.replaceChild(assignNode, replacement); } else { Node gramps = parent.getParent(); if (parent.getType() == Token.EXPR_RESULT) { gramps.removeChild(parent); } else { parent.replaceChild(assignNode, assignNode.getLastChild().detachFromParent()); } } }
/** Processes trailing default and rest parameters. */ private void visitParamList(Node paramList, Node function) { Node insertSpot = null; Node block = function.getLastChild(); for (int i = 0; i < paramList.getChildCount(); i++) { Node param = paramList.getChildAtIndex(i); if (param.isDefaultValue()) { Node nameOrPattern = param.removeFirstChild(); Node defaultValue = param.removeFirstChild(); Node newParam = nameOrPattern.isName() ? nameOrPattern : IR.name(DESTRUCTURING_TEMP_VAR + (destructuringVarCounter++)); Node lhs = nameOrPattern.cloneTree(); Node rhs = defaultValueHook(newParam.cloneTree(), defaultValue); Node newStatement = nameOrPattern.isName() ? IR.exprResult(IR.assign(lhs, rhs)) : IR.var(lhs, rhs); newStatement.useSourceInfoIfMissingFromForTree(param); block.addChildAfter(newStatement, insertSpot); insertSpot = newStatement; paramList.replaceChild(param, newParam); newParam.setOptionalArg(true); compiler.reportCodeChange(); } else if (param.isRest()) { // rest parameter param.setType(Token.NAME); param.setVarArgs(true); // Transpile to: param = [].slice.call(arguments, i); Node newArr = IR.exprResult( IR.assign( IR.name(param.getString()), IR.call( IR.getprop( IR.getprop(IR.arraylit(), IR.string("slice")), IR.string("call")), IR.name("arguments"), IR.number(i)))); block.addChildAfter(newArr.useSourceInfoIfMissingFromForTree(param), insertSpot); compiler.reportCodeChange(); } else if (param.isDestructuringPattern()) { String tempVarName = DESTRUCTURING_TEMP_VAR + (destructuringVarCounter++); paramList.replaceChild(param, IR.name(tempVarName)); Node newDecl = IR.var(param, IR.name(tempVarName)); block.addChildAfter(newDecl, insertSpot); insertSpot = newDecl; } } // 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. }
@Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isThis()) { Node name = IR.name(THIS_VAR).srcref(n); parent.replaceChild(n, name); changedThis = true; } else if (n.isName() && n.getString().equals("arguments")) { Node name = IR.name(ARGUMENTS_VAR).srcref(n); parent.replaceChild(n, name); changedArguments = true; } }
/** * Expressions such as [foo() + 'a' + 'b'] generate parse trees where no node has two const * children ((foo() + 'a') + 'b'), so tryFoldAdd() won't fold it -- tryFoldLeftChildAdd() will * (for Strings). Specifically, it folds Add expressions where: - The left child is also and add * expression - The right child is a constant value - The left child's right child is a STRING * constant. */ private Node tryFoldChildAddString(Node n, Node left, Node right) { if (NodeUtil.isLiteralValue(right, false) && left.isAdd()) { Node ll = left.getFirstChild(); Node lr = ll.getNext(); // Left's right child MUST be a string. We would not want to fold // foo() + 2 + 'a' because we don't know what foo() will return, and // therefore we don't know if left is a string concat, or a numeric add. if (lr.isString()) { String leftString = NodeUtil.getStringValue(lr); String rightString = NodeUtil.getStringValue(right); if (leftString != null && rightString != null) { left.removeChild(ll); String result = leftString + rightString; n.replaceChild(left, ll); n.replaceChild(right, IR.string(result)); reportCodeChange(); return n; } } } if (NodeUtil.isLiteralValue(left, false) && right.isAdd()) { Node rl = right.getFirstChild(); Node rr = right.getLastChild(); // Left's right child MUST be a string. We would not want to fold // foo() + 2 + 'a' because we don't know what foo() will return, and // therefore we don't know if left is a string concat, or a numeric add. if (rl.isString()) { String leftString = NodeUtil.getStringValue(left); String rightString = NodeUtil.getStringValue(rl); if (leftString != null && rightString != null) { right.removeChild(rr); String result = leftString + rightString; n.replaceChild(right, rr); n.replaceChild(left, IR.string(result)); reportCodeChange(); return n; } } } return n; }
/** * Rename or remove labels. * * @param node The label node. * @param parent The parent of the label node. */ private void visitLabel(Node node, Node parent) { Node nameNode = node.getFirstChild(); Preconditions.checkState(nameNode != null); String name = nameNode.getString(); LabelInfo li = getLabelInfo(name); // This is a label... if (li.referenced || !removeUnused) { String newName = getNameForId(li.id); if (!name.equals(newName)) { // ... and it is used, give it the short name. nameNode.setString(newName); compiler.reportCodeChange(); } } else { // ... and it is not referenced, just remove it. Node newChild = node.getLastChild(); node.removeChild(newChild); parent.replaceChild(node, newChild); if (newChild.isBlock()) { NodeUtil.tryMergeBlock(newChild); } compiler.reportCodeChange(); } // Remove the label from the current stack of labels. namespaceStack.peek().renameMap.remove(name); }
private void visitForOf(Node node, Node parent) { Node variable = node.removeFirstChild(); Node iterable = node.removeFirstChild(); Node body = node.removeFirstChild(); Node iterName = IR.name(ITER_BASE + compiler.getUniqueNameIdSupplier().get()); Node getNext = IR.call(IR.getprop(iterName.cloneTree(), IR.string("next"))); String variableName = variable.isName() ? variable.getQualifiedName() : variable.getFirstChild().getQualifiedName(); // var or let Node iterResult = IR.name(ITER_RESULT + variableName); Node makeIter = IR.call(NodeUtil.newQualifiedNameNode(compiler.getCodingConvention(), MAKE_ITER), iterable); Node init = IR.var(iterName.cloneTree(), makeIter); Node initIterResult = iterResult.cloneTree(); initIterResult.addChildToFront(getNext.cloneTree()); init.addChildToBack(initIterResult); Node cond = IR.not(IR.getprop(iterResult.cloneTree(), IR.string("done"))); Node incr = IR.assign(iterResult.cloneTree(), getNext.cloneTree()); body.addChildToFront( IR.var(IR.name(variableName), IR.getprop(iterResult.cloneTree(), IR.string("value")))); Node newFor = IR.forNode(init, cond, incr, body); newFor.useSourceInfoIfMissingFromForTree(node); parent.replaceChild(node, newFor); compiler.reportCodeChange(); }
/** * 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(); }
/** * Inline a function that fulfills the requirements of canInlineReferenceDirectly into the call * site, replacing only the CALL node. */ private Node inlineReturnValue(Node callNode, Node fnNode) { Node block = fnNode.getLastChild(); Node callParentNode = callNode.getParent(); // NOTE: As the normalize pass guarantees globals aren't being // shadowed and an expression can't introduce new names, there is // no need to check for conflicts. // Create an argName -> expression map, checking for side effects. Map<String, Node> argMap = FunctionArgumentInjector.getFunctionCallParameterMap( fnNode, callNode, this.safeNameIdSupplier); Node newExpression; if (!block.hasChildren()) { Node srcLocation = block; newExpression = NodeUtil.newUndefinedNode(srcLocation); } else { Node returnNode = block.getFirstChild(); Preconditions.checkArgument(returnNode.getType() == Token.RETURN); // Clone the return node first. Node safeReturnNode = returnNode.cloneTree(); Node inlineResult = FunctionArgumentInjector.inject(safeReturnNode, null, argMap); Preconditions.checkArgument(safeReturnNode == inlineResult); newExpression = safeReturnNode.removeFirstChild(); } callParentNode.replaceChild(callNode, newExpression); return newExpression; }
private Node tryReduceVoid(Node n) { Node child = n.getFirstChild(); if ((!child.isNumber() || child.getDouble() != 0.0) && !mayHaveSideEffects(n)) { n.replaceChild(child, IR.number(0)); reportCodeChange(); } return n; }
void replaceNodeInPlace(Node n, Node replacement) { Node parent = n.getParent(); if (n.hasChildren()) { Node children = n.removeChildren(); replacement.addChildrenToFront(children); } parent.replaceChild(n, replacement); }
@Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isThis()) { Node name = IR.name(THIS_VAR).srcref(n); parent.replaceChild(n, name); changed = true; } }
/** * Processes array literals or calls containing spreads. Eg.: [1, 2, ...x, 4, 5] => [1, * 2].concat(x, [4, 5]); Eg.: f(...arr) => f.apply(null, arr) Eg.: new F(...args) => new * Function.prototype.bind.apply(F, [].concat(args)) */ private void visitArrayLitOrCallWithSpread(Node node, Node parent) { Preconditions.checkArgument(node.isCall() || node.isArrayLit() || node.isNew()); List<Node> groups = new ArrayList<>(); Node currGroup = null; Node callee = node.isArrayLit() ? null : node.removeFirstChild(); Node currElement = node.removeFirstChild(); while (currElement != null) { if (currElement.isSpread()) { if (currGroup != null) { groups.add(currGroup); currGroup = null; } groups.add(currElement.removeFirstChild()); } else { if (currGroup == null) { currGroup = IR.arraylit(); } currGroup.addChildToBack(currElement); } currElement = node.removeFirstChild(); } if (currGroup != null) { groups.add(currGroup); } Node result = null; Node joinedGroups = IR.call( IR.getprop(IR.arraylit(), IR.string("concat")), groups.toArray(new Node[groups.size()])); if (node.isArrayLit()) { result = joinedGroups; } else if (node.isCall()) { if (NodeUtil.mayHaveSideEffects(callee) && callee.isGetProp()) { Node statement = node; while (!NodeUtil.isStatement(statement)) { statement = statement.getParent(); } Node freshVar = IR.name(FRESH_SPREAD_VAR + freshSpreadVarCounter++); Node n = IR.var(freshVar.cloneTree()); n.useSourceInfoIfMissingFromForTree(statement); statement.getParent().addChildBefore(n, statement); callee.addChildToFront(IR.assign(freshVar.cloneTree(), callee.removeFirstChild())); result = IR.call(IR.getprop(callee, IR.string("apply")), freshVar, joinedGroups); } else { Node context = callee.isGetProp() ? callee.getFirstChild().cloneTree() : IR.nullNode(); result = IR.call(IR.getprop(callee, IR.string("apply")), context, joinedGroups); } } else { Node bindApply = NodeUtil.newQualifiedNameNode( compiler.getCodingConvention(), "Function.prototype.bind.apply"); result = IR.newNode(bindApply, callee, joinedGroups); } result.useSourceInfoIfMissingFromForTree(node); parent.replaceChild(node, result); compiler.reportCodeChange(); }
@Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.THIS: parent.replaceChild(n, Node.newString(Token.NAME, "$$_self")); thisChanged = true; compiler.reportCodeChange(); } }
private void visitObjectWithComputedProperty(Node obj, Node parent) { Preconditions.checkArgument(obj.isObjectLit()); List<Node> props = new ArrayList<>(); Node currElement = obj.getFirstChild(); while (currElement != null) { if (currElement.getBooleanProp(Node.COMPUTED_PROP_GETTER) || currElement.getBooleanProp(Node.COMPUTED_PROP_SETTER)) { cannotConvertYet(currElement, "computed getter/setter"); return; } else if (currElement.isGetterDef() || currElement.isSetterDef()) { currElement = currElement.getNext(); } else { Node nextNode = currElement.getNext(); obj.removeChild(currElement); props.add(currElement); currElement = nextNode; } } String objName = FRESH_COMP_PROP_VAR + compiler.getUniqueNameIdSupplier().get(); props = Lists.reverse(props); Node result = IR.name(objName); for (Node propdef : props) { if (propdef.isComputedProp()) { Node propertyExpression = propdef.removeFirstChild(); Node value = propdef.removeFirstChild(); result = IR.comma(IR.assign(IR.getelem(IR.name(objName), propertyExpression), value), result); } else { if (!propdef.hasChildren()) { Node name = IR.name(propdef.getString()).useSourceInfoIfMissingFrom(propdef); propdef.addChildToBack(name); } Node val = propdef.removeFirstChild(); propdef.setType(Token.STRING); int type = propdef.isQuotedString() ? Token.GETELEM : Token.GETPROP; Node access = new Node(type, IR.name(objName), propdef); result = IR.comma(IR.assign(access, val), result); } } Node statement = obj; while (!NodeUtil.isStatement(statement)) { statement = statement.getParent(); } result.useSourceInfoIfMissingFromForTree(obj); parent.replaceChild(obj, result); Node var = IR.var(IR.name(objName), obj); var.useSourceInfoIfMissingFromForTree(statement); statement.getParent().addChildBefore(var, statement); compiler.reportCodeChange(); }
@Override protected void aliasNode(Node throwNode, Node parent) { Node name = NodeUtil.newName( compiler.getCodingConvention(), getAliasName(), throwNode, getAliasName()); Node aliasCall = IR.call(name, throwNode.removeFirstChild()); aliasCall.putBooleanProp(Node.FREE_CALL, true); Node exprResult = IR.exprResult(aliasCall); parent.replaceChild(throwNode, exprResult); }
/** Flattens a stub declaration. This is mostly a hack to support legacy users. */ private void flattenSimpleStubDeclaration(Name name, String alias) { Ref ref = Iterables.getOnlyElement(name.getRefs()); Node nameNode = NodeUtil.newName(compiler, alias, ref.node, name.getFullName()); Node varNode = IR.var(nameNode).useSourceInfoIfMissingFrom(nameNode); Preconditions.checkState(ref.node.getParent().isExprResult()); Node parent = ref.node.getParent(); Node grandparent = parent.getParent(); grandparent.replaceChild(parent, varNode); compiler.reportCodeChange(); }
@Override public void visit(NodeTraversal t, Node n, Node parent) { // System.out.println(n); switch (n.getType()) { case Token.FUNCTION: Node functionNameNode = n.getFirstChild(); String currFunctionName = functionNameNode.getString(); if (!anonymizedFnNodes.contains(n)) { if (currFunctionName.length() == 0) { Set<String> closures = findClosures(n); // System.out.println("closures" + closures); n.detachFromParent(); // detach anonymous function from. // change all references to 'this' in method to $$_self boolean thisChanged = changeThisTo$$_self(n); // clone the parameters of modified $$$anonym(originalParams..., closures..) so that // we can use it for parameters of function call below Node clonedorigParamNode = n.getChildAtIndex(1).cloneTree(); clonedorigParamNode.setType(Token.LP); // parent // give 'n' a name and attach to end // e.g. function $$anonym(originalParams..., closures..) String anonymName = "$$anonym" + anonymCount++; Node funcNameNode = Node.newString(Token.NAME, anonymName); n.replaceChild(n.getFirstChild(), funcNameNode); Node parametersNode = n.getChildAtIndex(1); addParamsToMethod(closures, parametersNode, thisChanged, "$$_self"); root.getFirstChild().addChildrenToBack(n); // replace original anonymous call to new anonymous function that closes over // params and then makes a call to our named function Node newAnonymNode = createAnonymWithParamCall(closures, anonymName, clonedorigParamNode, thisChanged); parent.addChildrenToBack(newAnonymNode); // add to list of anonymized nodes so that we can ignore it on second pass anonymizedFnNodes.add(newAnonymNode); compiler.reportCodeChange(); } else { // named function just find closures and add as parameter // TODO } } } }
@Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isCall()) { Node target = n.getFirstChild(); // TODO(johnlenz): add this to the coding convention // so we can remove goog.reflect.sinkValue as well. if (target.isName() && target.getString().equals(PROTECTOR_FN)) { Node expr = n.getLastChild(); n.detachChildren(); parent.replaceChild(n, expr); } } }
/** * Attempt to inline an global alias of a global name. This requires that the name is well * defined: assigned unconditionally, assigned exactly once. It is assumed that, the name for * which it is an alias must already meet these same requirements. * * @param alias The alias to inline * @return Whether the alias was inlined. */ private boolean inlineGlobalAliasIfPossible(Name name, Ref alias, GlobalNamespace namespace) { // Ensure that the alias is assigned to global name at that the // declaration. Node aliasParent = alias.node.getParent(); if (aliasParent.isAssign() && NodeUtil.isExecutedExactlyOnce(aliasParent) // We special-case for constructors here, to inline constructor aliases // more aggressively in global scope. // We do this because constructor properties are always collapsed, // so we want to inline the aliases also to avoid breakages. || aliasParent.isName() && name.isConstructor()) { Node lvalue = aliasParent.isName() ? aliasParent : aliasParent.getFirstChild(); if (!lvalue.isQualifiedName()) { return false; } name = namespace.getSlot(lvalue.getQualifiedName()); if (name != null && name.isInlinableGlobalAlias()) { Set<AstChange> newNodes = new LinkedHashSet<>(); List<Ref> refs = new ArrayList<>(name.getRefs()); for (Ref ref : refs) { switch (ref.type) { case SET_FROM_GLOBAL: continue; case DIRECT_GET: case ALIASING_GET: Node newNode = alias.node.cloneTree(); Node node = ref.node; node.getParent().replaceChild(node, newNode); newNodes.add(new AstChange(ref.module, ref.scope, newNode)); name.removeRef(ref); break; default: throw new IllegalStateException(); } } rewriteAliasProps(name, alias.node, 0, newNodes); // just set the original alias to null. aliasParent.replaceChild(alias.node, IR.nullNode()); compiler.reportCodeChange(); // Inlining the variable may have introduced new references // to descendants of {@code name}. So those need to be collected now. namespace.scanNewNodes(newNodes); return true; } } return false; }
/** Remove useless calls: Object.defineProperties(o, {}) -> o */ private Node tryFoldCall(Node n) { Preconditions.checkArgument(n.isCall()); if (NodeUtil.isObjectDefinePropertiesDefinition(n)) { Node srcObj = n.getLastChild(); if (srcObj.isObjectLit() && !srcObj.hasChildren()) { Node parent = n.getParent(); Node destObj = n.getSecondChild().detachFromParent(); parent.replaceChild(n, destObj); reportCodeChange(); } } return n; }
private void visitForOf(Node node, Node parent) { Node variable = node.removeFirstChild(); Node iterable = node.removeFirstChild(); Node body = node.removeFirstChild(); Node iterName = IR.name(ITER_BASE + compiler.getUniqueNameIdSupplier().get()); Node getNext = IR.call(IR.getprop(iterName.cloneTree(), IR.string("next"))); String variableName; int declType; if (variable.isName()) { declType = Token.NAME; variableName = variable.getQualifiedName(); } else { Preconditions.checkState( NodeUtil.isNameDeclaration(variable), "Expected var, let, or const. Got %s", variable); declType = variable.getType(); variableName = variable.getFirstChild().getQualifiedName(); } Node iterResult = IR.name(ITER_RESULT + variableName); Node makeIter = IR.call(NodeUtil.newQName(compiler, MAKE_ITER), iterable); compiler.needsEs6Runtime = true; Node init = IR.var(iterName.cloneTree(), makeIter); Node initIterResult = iterResult.cloneTree(); initIterResult.addChildToFront(getNext.cloneTree()); init.addChildToBack(initIterResult); Node cond = IR.not(IR.getprop(iterResult.cloneTree(), IR.string("done"))); Node incr = IR.assign(iterResult.cloneTree(), getNext.cloneTree()); Node declarationOrAssign; if (declType == Token.NAME) { declarationOrAssign = IR.exprResult( IR.assign( IR.name(variableName), IR.getprop(iterResult.cloneTree(), IR.string("value")))); } else { declarationOrAssign = new Node(declType, IR.name(variableName)); declarationOrAssign .getFirstChild() .addChildToBack(IR.getprop(iterResult.cloneTree(), IR.string("value"))); } body.addChildToFront(declarationOrAssign); Node newFor = IR.forNode(init, cond, incr, body); newFor.useSourceInfoIfMissingFromForTree(node); parent.replaceChild(node, newFor); compiler.reportCodeChange(); }
/** * Expressions such as [foo() * 10 * 20] generate parse trees where no node has two const children * ((foo() * 10) * 20), so performArithmeticOp() won't fold it -- tryFoldLeftChildOp() will. * Specifically, it folds associative expressions where: - The left child is also an associative * expression of the same time. - The right child is a constant NUMBER constant. - The left * child's right child is a NUMBER constant. */ private Node tryFoldLeftChildOp(Node n, Node left, Node right) { Token opType = n.getType(); Preconditions.checkState( (NodeUtil.isAssociative(opType) && NodeUtil.isCommutative(opType)) || n.isAdd()); Preconditions.checkState(!n.isAdd() || !NodeUtil.mayBeString(n, shouldUseTypes)); // Use getNumberValue to handle constants like "NaN" and "Infinity" // other values are converted to numbers elsewhere. Double rightValObj = NodeUtil.getNumberValue(right, shouldUseTypes); if (rightValObj != null && left.getType() == opType) { Preconditions.checkState(left.getChildCount() == 2); Node ll = left.getFirstChild(); Node lr = ll.getNext(); Node valueToCombine = ll; Node replacement = performArithmeticOp(opType, valueToCombine, right); if (replacement == null) { valueToCombine = lr; replacement = performArithmeticOp(opType, valueToCombine, right); } if (replacement != null) { // Remove the child that has been combined left.removeChild(valueToCombine); // Replace the left op with the remaining child. n.replaceChild(left, left.removeFirstChild()); // New "-Infinity" node need location info explicitly // added. replacement.useSourceInfoIfMissingFromForTree(right); n.replaceChild(right, replacement); reportCodeChange(); } } return n; }
/** Collapse VARs and EXPR_RESULT node into FOR loop initializers where possible. */ private void maybeCollapseIntoForStatements(Node n, Node parent) { // Only SCRIPT, BLOCK, and LABELs can have FORs that can be collapsed into. // LABELs are not supported here. if (parent == null || !NodeUtil.isStatementBlock(parent)) { return; } // Is the current node something that can be in a for loop initializer? if (!NodeUtil.isExpressionNode(n) && !NodeUtil.isVar(n)) { return; } // Is the next statement a valid FOR? Node nextSibling = n.getNext(); if (nextSibling != null && nextSibling.getType() == Token.FOR && !NodeUtil.isForIn(nextSibling) && nextSibling.getFirstChild().getType() == Token.EMPTY) { // Does the current node contain an in operator? If so, embedding // the expression in a for loop can cause some Javascript parsers (such // as the Playstation 3's browser based on Access's NetFront // browser) to fail to parse the code. // See bug 1778863 for details. if (NodeUtil.containsType(n, Token.IN)) { return; } // Move the current node into the FOR loop initializer. Node forNode = nextSibling; Node oldInitializer = forNode.getFirstChild(); parent.removeChild(n); Node newInitializer; if (NodeUtil.isVar(n)) { newInitializer = n; } else { // Extract the expression from EXPR_RESULT node. Preconditions.checkState(n.hasOneChild()); newInitializer = n.getFirstChild(); n.removeChild(newInitializer); } forNode.replaceChild(oldInitializer, newInitializer); compiler.reportCodeChange(); } }
/** Add blocks to IF, WHILE, DO, etc. */ private void normalizeBlocks(Node n) { if (NodeUtil.isControlStructure(n) && !n.isLabel() && !n.isSwitch()) { for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (NodeUtil.isControlStructureCodeBlock(n, c) && !c.isBlock()) { Node newBlock = IR.block().srcref(n); n.replaceChild(c, newBlock); newBlock.setIsAddedBlock(true); if (!c.isEmpty()) { newBlock.addChildrenToFront(c); } c = newBlock; reportChange(); } } } }
/** Try to fold a AND/OR node. */ private Node tryFoldAndOr(Node n, Node left, Node right) { Node parent = n.getParent(); Node result = null; Token type = n.getType(); TernaryValue leftVal = NodeUtil.getImpureBooleanValue(left); if (leftVal != TernaryValue.UNKNOWN) { boolean lval = leftVal.toBoolean(true); // (TRUE || x) => TRUE (also, (3 || x) => 3) // (FALSE && x) => FALSE if (lval && type == Token.OR || !lval && type == Token.AND) { result = left; } else if (!mayHaveSideEffects(left)) { // (FALSE || x) => x // (TRUE && x) => x result = right; } else { // Left side may have side effects, but we know its boolean value. // e.g. true_with_sideeffects || foo() => true_with_sideeffects, foo() // or: false_with_sideeffects && foo() => false_with_sideeffects, foo() // This, combined with PeepholeRemoveDeadCode, helps reduce expressions // like "x() || false || z()". n.detachChildren(); result = IR.comma(left, right); } } // Note: Right hand side folding is handled by // PeepholeMinimizeConditions#tryMinimizeCondition if (result != null) { // Fold it! n.detachChildren(); parent.replaceChild(n, result); reportCodeChange(); return result; } else { return n; } }
private void maybeReplaceJqueryPrototypeAlias(Node n) { // Check to see if this is the assignment of the original alias. // If so, leave it intact. if (NodeUtil.isLValue(n)) { Node maybeAssign = n.getParent(); while (!NodeUtil.isStatement(maybeAssign) && !maybeAssign.isAssign()) { maybeAssign = maybeAssign.getParent(); } if (maybeAssign.isAssign()) { maybeAssign = maybeAssign.getParent(); if (maybeAssign.isBlock() || maybeAssign.isScript() || NodeUtil.isStatement(maybeAssign)) { return; } } } Node fn = n.getLastChild(); if (fn != null) { n.replaceChild(fn, IR.string("prototype").srcref(fn)); compiler.reportCodeChange(); } }
private Node tryFoldInForcedStringContext(Node n) { // For now, we only know how to fold ctors. Preconditions.checkArgument(n.isNew()); Node objectType = n.getFirstChild(); if (!objectType.isName()) { return n; } if (objectType.getString().equals("String")) { Node value = objectType.getNext(); String stringValue = null; if (value == null) { stringValue = ""; } else { if (!NodeUtil.isImmutableValue(value)) { return n; } stringValue = NodeUtil.getStringValue(value); } if (stringValue == null) { return n; } Node parent = n.getParent(); Node newString = IR.string(stringValue); parent.replaceChild(n, newString); newString.useSourceInfoIfMissingFrom(parent); reportCodeChange(); return newString; } return n; }
private void replaceOriginalJqueryEachCall(Node n, Node expandedBlock) { // Check to see if the return value of the original jQuery.expandedEach // call is used. If so, we need to wrap each loop expansion in an anonymous // function and return the original objectToLoopOver. if (n.getParent().isExprResult()) { Node parent = n.getParent(); Node grandparent = parent.getParent(); Node insertAfter = parent; while (expandedBlock.hasChildren()) { Node child = expandedBlock.getFirstChild().detachFromParent(); grandparent.addChildAfter(child, insertAfter); insertAfter = child; } grandparent.removeChild(parent); } else { // Return the original object Node callTarget = n.getFirstChild(); Node objectToLoopOver = callTarget.getNext(); objectToLoopOver.detachFromParent(); Node ret = IR.returnNode(objectToLoopOver).srcref(callTarget); expandedBlock.addChildToBack(ret); // Wrap all of the expanded loop calls in a new anonymous function Node fnc = IR.function( IR.name("").srcref(callTarget), IR.paramList().srcref(callTarget), expandedBlock); n.replaceChild(callTarget, fnc); n.putBooleanProp(Node.FREE_CALL, true); // remove any other pre-existing call arguments while (fnc.getNext() != null) { n.removeChildAfter(fnc); } } compiler.reportCodeChange(); }
@Override protected void aliasNode(Node n, Node parent) { Node aliasNode = NodeUtil.newName(compiler.getCodingConvention(), getAliasName(), n, getAliasName()); parent.replaceChild(n, aliasNode); }