private void maybeRewriteClassDefinition(Node n, Node target, Node value) { if (isGoogDefineClass(value)) { if (!target.isQualifiedName()) { compiler.report(JSError.make(n, GOOG_CLASS_TARGET_INVALID)); } ClassDefinition def = extractClassDefinition(target, value); if (def != null) { value.detachFromParent(); target.detachFromParent(); rewriteGoogDefineClass(n, def); } } }
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); }
@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 } } } }
/** Converts ES6 arrow functions to standard anonymous ES3 functions. */ private void visitArrowFunction(NodeTraversal t, Node n) { n.setIsArrowFunction(false); Node body = n.getLastChild(); if (!body.isBlock()) { body.detachFromParent(); body = IR.block(IR.returnNode(body).srcref(body)).srcref(body); n.addChildToBack(body); } UpdateThisAndArgumentsReferences updater = new UpdateThisAndArgumentsReferences(); NodeTraversal.traverse(compiler, body, updater); addVarDecls(t, updater.changedThis, updater.changedArguments); compiler.reportCodeChange(); }
private Node tryUnfoldAssignOp(Node n, Node left, Node right) { if (late) { return n; } if (!n.hasChildren() || n.getSecondChild() != n.getLastChild()) { return n; } if (mayHaveSideEffects(left)) { return n; } // Tries to convert x += y -> x = x + y; Token op = NodeUtil.getOpFromAssignmentOp(n); Node replacement = IR.assign( left.detachFromParent(), new Node(op, left.cloneTree(), right.detachFromParent()).srcref(n)); n.getParent().replaceChild(n, replacement); reportCodeChange(); return replacement; }
/** Converts ES6 arrow functions to standard anonymous ES3 functions. */ private void visitArrowFunction(NodeTraversal t, Node n) { n.setIsArrowFunction(false); Node body = n.getLastChild(); if (!body.isBlock()) { body.detachFromParent(); Node newBody = IR.block(IR.returnNode(body).srcref(body)).srcref(body); n.addChildToBack(newBody); } UpdateThisNodes thisUpdater = new UpdateThisNodes(); NodeTraversal.traverse(compiler, body, thisUpdater); if (thisUpdater.changed) { addThisVar(t); } compiler.reportCodeChange(); }
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(); }
// exp1, exp1 static Node fuseExpressionIntoExpression(Node exp1, Node exp2) { if (exp2.isEmpty()) { return exp1; } Node comma = new Node(Token.COMMA, exp1); comma.useSourceInfoIfMissingFrom(exp2); // We can just join the new comma expression with another comma but // lets keep all the comma's in a straight line. That way we can use // tree comparison. if (exp2.isComma()) { Node leftMostChild = exp2; while (leftMostChild.isComma()) { leftMostChild = leftMostChild.getFirstChild(); } Node parent = leftMostChild.getParent(); comma.addChildToBack(leftMostChild.detachFromParent()); parent.addChildToFront(comma); return exp2; } else { comma.addChildToBack(exp2); return comma; } }
private Node tryFoldUnaryOperator(Node n) { Preconditions.checkState(n.hasOneChild(), n); Node left = n.getFirstChild(); Node parent = n.getParent(); if (left == null) { return n; } TernaryValue leftVal = NodeUtil.getPureBooleanValue(left); if (leftVal == TernaryValue.UNKNOWN) { return n; } switch (n.getType()) { case NOT: // Don't fold !0 and !1 back to false. if (late && left.isNumber()) { double numValue = left.getDouble(); if (numValue == 0 || numValue == 1) { return n; } } Node replacementNode = NodeUtil.booleanNode(!leftVal.toBoolean(true)); parent.replaceChild(n, replacementNode); reportCodeChange(); return replacementNode; case POS: if (NodeUtil.isNumericResult(left)) { // POS does nothing to numeric values. parent.replaceChild(n, left.detachFromParent()); reportCodeChange(); return left; } return n; case NEG: if (left.isName()) { if (left.getString().equals("Infinity")) { // "-Infinity" is valid and a literal, don't modify it. return n; } else if (left.getString().equals("NaN")) { // "-NaN" is "NaN". n.removeChild(left); parent.replaceChild(n, left); reportCodeChange(); return left; } } if (left.isNumber()) { double negNum = -left.getDouble(); Node negNumNode = IR.number(negNum); parent.replaceChild(n, negNumNode); reportCodeChange(); return negNumNode; } else { // left is not a number node, so do not replace, but warn the // user because they can't be doing anything good report(NEGATING_A_NON_NUMBER_ERROR, left); return n; } case BITNOT: try { double val = left.getDouble(); if (val >= Integer.MIN_VALUE && val <= Integer.MAX_VALUE) { int intVal = (int) val; if (intVal == val) { Node notIntValNode = IR.number(~intVal); parent.replaceChild(n, notIntValNode); reportCodeChange(); return notIntValNode; } else { report(FRACTIONAL_BITWISE_OPERAND, left); return n; } } else { report(BITWISE_OPERAND_OUT_OF_RANGE, left); return n; } } catch (UnsupportedOperationException ex) { // left is not a number node, so do not replace, but warn the // user because they can't be doing anything good report(NEGATING_A_NON_NUMBER_ERROR, left); return n; } default: return n; } }
private Node tryFoldObjectPropAccess(Node n, Node left, Node right) { Preconditions.checkArgument(NodeUtil.isGet(n)); if (!left.isObjectLit() || !right.isString()) { return n; } if (NodeUtil.isAssignmentTarget(n)) { // If GETPROP/GETELEM is used as assignment target the object literal is // acting as a temporary we can't fold it here: // "{a:x}.a += 1" is not "x += 1" return n; } // find the last definition in the object literal Node key = null; Node value = null; for (Node c = left.getFirstChild(); c != null; c = c.getNext()) { if (c.getString().equals(right.getString())) { switch (c.getType()) { case SETTER_DEF: continue; case GETTER_DEF: case STRING_KEY: if (value != null && mayHaveSideEffects(value)) { // The previously found value had side-effects return n; } key = c; value = key.getFirstChild(); break; default: throw new IllegalStateException(); } } else if (mayHaveSideEffects(c.getFirstChild())) { // We don't handle the side-effects here as they might need a temporary // or need to be reordered. return n; } } // Didn't find a definition of the name in the object literal, it might // be coming from the Object prototype if (value == null) { return n; } if (value.isFunction() && NodeUtil.referencesThis(value)) { // 'this' may refer to the object we are trying to remove return n; } Node replacement = value.detachFromParent(); if (key.isGetterDef()) { replacement = IR.call(replacement); replacement.putBooleanProp(Node.FREE_CALL, true); } n.getParent().replaceChild(n, replacement); reportCodeChange(); return n; }
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(); }
/** * Declares global variables to serve as aliases for the values in an object literal, optionally * removing all of the object literal's keys and values. * * @param alias The object literal's flattened name (e.g. "a$b$c") * @param objlit The OBJLIT node * @param varNode The VAR node to which new global variables should be added as children * @param nameToAddAfter The child of {@code varNode} after which new variables should be added * (may be null) * @param varParent {@code varNode}'s parent * @return The number of variables added */ private int declareVarsForObjLitValues( Name objlitName, String alias, Node objlit, Node varNode, Node nameToAddAfter, Node varParent) { int numVars = 0; int arbitraryNameCounter = 0; boolean discardKeys = !objlitName.shouldKeepKeys(); for (Node key = objlit.getFirstChild(), nextKey; key != null; key = nextKey) { Node value = key.getFirstChild(); nextKey = key.getNext(); // A get or a set can not be rewritten as a VAR. if (key.isGetterDef() || key.isSetterDef()) { continue; } // We generate arbitrary names for keys that aren't valid JavaScript // identifiers, since those keys are never referenced. (If they were, // this object literal's child names wouldn't be collapsible.) The only // reason that we don't eliminate them entirely is the off chance that // their values are expressions that have side effects. boolean isJsIdentifier = !key.isNumber() && TokenStream.isJSIdentifier(key.getString()); String propName = isJsIdentifier ? key.getString() : String.valueOf(++arbitraryNameCounter); // If the name cannot be collapsed, skip it. String qName = objlitName.getFullName() + '.' + propName; Name p = nameMap.get(qName); if (p != null && !p.canCollapse()) { continue; } String propAlias = appendPropForAlias(alias, propName); Node refNode = null; if (discardKeys) { objlit.removeChild(key); value.detachFromParent(); } else { // Substitute a reference for the value. refNode = IR.name(propAlias); if (key.getBooleanProp(Node.IS_CONSTANT_NAME)) { refNode.putBooleanProp(Node.IS_CONSTANT_NAME, true); } key.replaceChild(value, refNode); } // Declare the collapsed name as a variable with the original value. Node nameNode = IR.name(propAlias); nameNode.addChildToFront(value); if (key.getBooleanProp(Node.IS_CONSTANT_NAME)) { nameNode.putBooleanProp(Node.IS_CONSTANT_NAME, true); } Node newVar = IR.var(nameNode).useSourceInfoIfMissingFromForTree(key); if (nameToAddAfter != null) { varParent.addChildAfter(newVar, nameToAddAfter); } else { varParent.addChildBefore(newVar, varNode); } compiler.reportCodeChange(); nameToAddAfter = newVar; // Update the global name's node ancestry if it hasn't already been // done. (Duplicate keys in an object literal can bring us here twice // for the same global name.) if (isJsIdentifier && p != null) { if (!discardKeys) { Ref newAlias = p.getDeclaration().cloneAndReclassify(Ref.Type.ALIASING_GET); newAlias.node = refNode; p.addRef(newAlias); } p.getDeclaration().node = nameNode; if (value.isFunction()) { checkForHosedThisReferences(value, key.getJSDocInfo(), p); } } numVars++; } return numVars; }
private Node tryExpandJqueryEachCall( NodeTraversal t, Node n, Node callbackFunction, List<Node> keyNodes, List<Node> valueNodes) { Node callTarget = n.getFirstChild(); Node objectToLoopOver = callTarget.getNext(); // New block to contain the expanded statements Node fncBlock = IR.block().srcref(callTarget); boolean isValidExpansion = true; // Expand the jQuery.expandedEach call Node key = objectToLoopOver.getFirstChild(), val = null; for (int i = 0; key != null; key = key.getNext(), i++) { if (key != null) { if (objectToLoopOver.isArrayLit()) { // Arrays have a value of their index number val = IR.number(i).srcref(key); } else { val = key.getFirstChild(); } } // Keep track of the replaced nodes so we can reset the tree List<Node> newKeys = new ArrayList<>(); List<Node> newValues = new ArrayList<>(); List<Node> origGetElems = new ArrayList<>(); List<Node> newGetProps = new ArrayList<>(); // Replace all of the key nodes with the prop name for (int j = 0; j < keyNodes.size(); j++) { Node origNode = keyNodes.get(j); Node ancestor = origNode.getParent(); Node newNode = IR.string(key.getString()).srcref(key); newKeys.add(newNode); ancestor.replaceChild(origNode, newNode); // Walk up the tree to see if the key is used in a GETELEM // assignment while (ancestor != null && !NodeUtil.isStatement(ancestor) && !ancestor.isGetElem()) { ancestor = ancestor.getParent(); } // Convert GETELEM nodes to GETPROP nodes so that they can be // renamed or removed. if (ancestor != null && ancestor.isGetElem()) { Node propObject = ancestor; while (propObject.isGetProp() || propObject.isGetElem()) { propObject = propObject.getFirstChild(); } Node ancestorClone = ancestor.cloneTree(); // Run the peephole passes to handle cases such as // obj['lit' + key] = val; peepholePasses.process(null, ancestorClone.getChildAtIndex(1)); Node prop = ancestorClone.getChildAtIndex(1); if (prop.isString() && NodeUtil.isValidPropertyName(LanguageMode.ECMASCRIPT3, prop.getString())) { Node target = ancestorClone.getFirstChild(); Node newGetProp = IR.getprop(target.detachFromParent(), prop.detachFromParent()); newGetProps.add(newGetProp); origGetElems.add(ancestor); ancestor.getParent().replaceChild(ancestor, newGetProp); } else { if (prop.isString() && !NodeUtil.isValidPropertyName(LanguageMode.ECMASCRIPT3, prop.getString())) { t.report(n, JQUERY_UNABLE_TO_EXPAND_INVALID_NAME_ERROR, prop.getString()); } isValidExpansion = false; } } } if (isValidExpansion) { // Replace all of the value nodes with the prop value for (int j = 0; val != null && j < valueNodes.size(); j++) { Node origNode = valueNodes.get(j); Node newNode = val.cloneTree(); newValues.add(newNode); origNode.getParent().replaceChild(origNode, newNode); } // Wrap the new tree in an anonymous function call Node fnc = IR.function( IR.name("").srcref(key), IR.paramList().srcref(key), callbackFunction.getChildAtIndex(2).cloneTree()) .srcref(key); Node call = IR.call(fnc).srcref(key); call.putBooleanProp(Node.FREE_CALL, true); fncBlock.addChildToBack(IR.exprResult(call).srcref(call)); } // Reset the source tree for (int j = 0; j < newGetProps.size(); j++) { newGetProps.get(j).getParent().replaceChild(newGetProps.get(j), origGetElems.get(j)); } for (int j = 0; j < newKeys.size(); j++) { newKeys.get(j).getParent().replaceChild(newKeys.get(j), keyNodes.get(j)); } for (int j = 0; j < newValues.size(); j++) { newValues.get(j).getParent().replaceChild(newValues.get(j), valueNodes.get(j)); } if (!isValidExpansion) { return null; } } return fncBlock; }
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(); }
private void visitSuper(Node node, Node parent) { Node enclosing = parent; Node potentialCallee = node; if (!parent.isCall()) { enclosing = parent.getParent(); potentialCallee = parent; } if (!enclosing.isCall() || enclosing.getFirstChild() != potentialCallee) { cannotConvertYet(node, "Only calls to super or to a method of super are supported."); return; } Node clazz = NodeUtil.getEnclosingClass(node); if (clazz == null) { compiler.report(JSError.make(node, NO_SUPERTYPE)); return; } if (NodeUtil.getClassNameNode(clazz) == null) { // Unnamed classes of the form: // f(class extends D { ... }); // give the problem that there is no name to be used in the call to goog.base for the // translation of super calls. // This will throw an error when the class is processed. return; } Node enclosingMemberDef = NodeUtil.getEnclosingClassMember(node); if (enclosingMemberDef.isStaticMember()) { Node superName = clazz.getFirstChild().getNext(); if (!superName.isQualifiedName()) { // This has already been reported, just don't need to continue processing the class. return; } Node callTarget; potentialCallee.detachFromParent(); if (potentialCallee == node) { // of the form super() potentialCallee = IR.getprop(superName.cloneTree(), IR.string(enclosingMemberDef.getString())); enclosing.putBooleanProp(Node.FREE_CALL, false); } else { // of the form super.method() potentialCallee.replaceChild(node, superName.cloneTree()); } callTarget = IR.getprop(potentialCallee, IR.string("call")); enclosing.addChildToFront(callTarget); enclosing.addChildAfter(IR.thisNode(), callTarget); enclosing.useSourceInfoIfMissingFromForTree(enclosing); compiler.reportCodeChange(); return; } String methodName; Node callName = enclosing.removeFirstChild(); if (callName.isSuper()) { methodName = enclosingMemberDef.getString(); } else { methodName = callName.getLastChild().getString(); } Node baseCall = baseCall(clazz, methodName, enclosing.removeChildren()) .useSourceInfoIfMissingFromForTree(enclosing); enclosing.getParent().replaceChild(enclosing, baseCall); compiler.reportCodeChange(); }
private Node tryFoldAssign(Node n, Node left, Node right) { Preconditions.checkArgument(n.isAssign()); if (!late) { return n; } // Tries to convert x = x + y -> x += y; if (!right.hasChildren() || right.getSecondChild() != right.getLastChild()) { // RHS must have two children. return n; } if (mayHaveSideEffects(left)) { return n; } Node newRight; if (areNodesEqualForInlining(left, right.getFirstChild())) { newRight = right.getLastChild(); } else if (NodeUtil.isCommutative(right.getType()) && areNodesEqualForInlining(left, right.getLastChild())) { newRight = right.getFirstChild(); } else { return n; } Token newType = null; switch (right.getType()) { case ADD: newType = Token.ASSIGN_ADD; break; case BITAND: newType = Token.ASSIGN_BITAND; break; case BITOR: newType = Token.ASSIGN_BITOR; break; case BITXOR: newType = Token.ASSIGN_BITXOR; break; case DIV: newType = Token.ASSIGN_DIV; break; case LSH: newType = Token.ASSIGN_LSH; break; case MOD: newType = Token.ASSIGN_MOD; break; case MUL: newType = Token.ASSIGN_MUL; break; case RSH: newType = Token.ASSIGN_RSH; break; case SUB: newType = Token.ASSIGN_SUB; break; case URSH: newType = Token.ASSIGN_URSH; break; default: return n; } Node newNode = new Node(newType, left.detachFromParent(), newRight.detachFromParent()); n.getParent().replaceChild(n, newNode); reportCodeChange(); return newNode; }
/** * 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(); }
/** 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. }
private void transformLoopClosure() { if (loopObjectMap.isEmpty()) { return; } for (Node loopNode : loopObjectMap.keySet()) { // Introduce objects to reflect the captured scope variables. // Fields are initially left as undefined to avoid cases like: // var $jscomp$loop$0 = {i: 0, j: $jscomp$loop$0.i} // They are initialized lazily by changing declarations into assignments // later. LoopObject object = loopObjectMap.get(loopNode); Node objectLit = IR.objectlit(); Node objectLitNextIteration = IR.objectlit(); for (Var var : object.vars) { objectLit.addChildToBack(IR.stringKey(var.name, IR.name("undefined"))); objectLitNextIteration.addChildToBack( IR.stringKey(var.name, IR.getprop(IR.name(object.name), IR.string(var.name)))); } Node updateLoopObject = IR.assign(IR.name(object.name), objectLitNextIteration); loopNode .getParent() .addChildBefore( IR.var(IR.name(object.name), objectLit).useSourceInfoIfMissingFromForTree(loopNode), loopNode); if (NodeUtil.isVanillaFor(loopNode)) { // For // The initializer is pulled out and placed prior to the loop. Node initializer = loopNode.getFirstChild(); loopNode.replaceChild(initializer, IR.empty()); if (!initializer.isEmpty()) { loopNode.getParent().addChildBefore(initializer, loopNode); } Node increment = loopNode.getChildAtIndex(2); if (increment.isEmpty()) { loopNode.replaceChild( increment, updateLoopObject.useSourceInfoIfMissingFromForTree(loopNode)); } else { Node placeHolder = IR.empty(); loopNode.replaceChild(increment, placeHolder); loopNode.replaceChild( placeHolder, IR.comma(updateLoopObject, increment).useSourceInfoIfMissingFromForTree(loopNode)); } } else if (loopNode.isDo()) { // do-while, put at the end of the block loopNode .getFirstChild() .addChildToBack( IR.exprResult(updateLoopObject).useSourceInfoIfMissingFromForTree(loopNode)); } else { // For-in, for-of or while, put at the end of the block loopNode .getLastChild() .addChildToBack( IR.exprResult(updateLoopObject).useSourceInfoIfMissingFromForTree(loopNode)); } // For captured variables, change declarations to assignments on the // corresponding field of the introduced object. Rename all references // accordingly. for (Var var : object.vars) { for (Node reference : referenceMap.get(var)) { // For-of and for-in declarations are not altered, since they are // used as temporary variables for assignment. if (NodeUtil.isEnhancedFor(loopNode) && loopNode.getFirstChild() == reference.getParent()) { loopNode .getLastChild() .addChildToFront( IR.exprResult( IR.assign( IR.getprop(IR.name(object.name), IR.string(var.name)), IR.name(var.name))) .useSourceInfoIfMissingFromForTree(reference)); } else { if (NodeUtil.isNameDeclaration(reference.getParent())) { Node declaration = reference.getParent(); Node grandParent = declaration.getParent(); // Normalize: "let i = 0, j = 0;" becomes "let i = 0; let j = 0;" while (declaration.getChildCount() > 1) { Node name = declaration.getLastChild(); grandParent.addChildAfter( IR.declaration(name.detachFromParent(), declaration.getType()) .useSourceInfoIfMissingFromForTree(declaration), declaration); } // Change declaration to assignment, or just drop it if there's // no initial value. if (reference.hasChildren()) { declaration = reference.getParent(); // Might have changed now Node newReference = IR.name(var.name); Node replacement = IR.exprResult(IR.assign(newReference, reference.removeFirstChild())) .useSourceInfoIfMissingFromForTree(declaration); grandParent.replaceChild(declaration, replacement); reference = newReference; } else { grandParent.removeChild(declaration); } } // Change reference to GETPROP. reference .getParent() .replaceChild( reference, IR.getprop(IR.name(object.name), IR.string(var.name)) .useSourceInfoIfMissingFromForTree(reference)); } } } } // Create wrapper functions and call them. for (Node function : functionLoopObjectsMap.keySet()) { Node returnNode = IR.returnNode(); Collection<LoopObject> objects = functionLoopObjectsMap.get(function); Node[] objectNames = new Node[objects.size()]; Node[] objectNamesForCall = new Node[objects.size()]; int i = 0; for (LoopObject object : objects) { objectNames[i] = IR.name(object.name); objectNamesForCall[i] = IR.name(object.name); i++; } Node iife = IR.function(IR.name(""), IR.paramList(objectNames), IR.block(returnNode)); Node call = IR.call(iife, objectNamesForCall); call.putBooleanProp(Node.FREE_CALL, true); function .getParent() .replaceChild(function, call.useSourceInfoIfMissingFromForTree(function)); returnNode.addChildToFront(function); } }
/** * Verifies that the compiler pass's JS output matches the expected output and (optionally) that * an expected warning is issued. Or, if an error is expected, this method just verifies that the * error is encountered. * * @param compiler A compiler that has been initialized via {@link Compiler#init} * @param expected Expected output, or null if an error is expected * @param error Expected error, or null if no error is expected * @param warning Expected warning, or null if no warning is expected * @param description The description of the expected warning, or null if no warning is expected * or if the warning's description should not be examined */ private void test( Compiler compiler, List<SourceFile> expected, DiagnosticType error, DiagnosticType warning, String description) { RecentChange recentChange = new RecentChange(); compiler.addChangeHandler(recentChange); Node root = compiler.parseInputs(); assertNotNull("Unexpected parse error(s): " + Joiner.on("\n").join(compiler.getErrors()), root); if (!expectParseWarningsThisTest) { assertEquals( "Unexpected parse warnings(s): " + Joiner.on("\n").join(compiler.getWarnings()), 0, compiler.getWarnings().length); } if (astValidationEnabled) { (new AstValidator(compiler)).validateRoot(root); } Node externsRoot = root.getFirstChild(); Node mainRoot = root.getLastChild(); // Save the tree for later comparison. Node rootClone = root.cloneTree(); Node externsRootClone = rootClone.getFirstChild(); Node mainRootClone = rootClone.getLastChild(); Map<Node, Node> mtoc = NodeUtil.mapMainToClone(mainRoot, mainRootClone); int numRepetitions = getNumRepetitions(); ErrorManager[] errorManagers = new ErrorManager[numRepetitions]; int aggregateWarningCount = 0; List<JSError> aggregateWarnings = Lists.newArrayList(); boolean hasCodeChanged = false; assertFalse("Code should not change before processing", recentChange.hasCodeChanged()); for (int i = 0; i < numRepetitions; ++i) { if (compiler.getErrorCount() == 0) { errorManagers[i] = new BlackHoleErrorManager(compiler); // Only run process closure primitives once, if asked. if (closurePassEnabled && i == 0) { recentChange.reset(); new ProcessClosurePrimitives(compiler, null, CheckLevel.ERROR, false) .process(null, mainRoot); hasCodeChanged = hasCodeChanged || recentChange.hasCodeChanged(); } // Only run the type checking pass once, if asked. // Running it twice can cause unpredictable behavior because duplicate // objects for the same type are created, and the type system // uses reference equality to compare many types. if (!runTypeCheckAfterProcessing && typeCheckEnabled && i == 0) { TypeCheck check = createTypeCheck(compiler, typeCheckLevel); check.processForTesting(externsRoot, mainRoot); } // Only run the normalize pass once, if asked. if (normalizeEnabled && i == 0) { normalizeActualCode(compiler, externsRoot, mainRoot); } if (enableInferConsts && i == 0) { new InferConsts(compiler).process(externsRoot, mainRoot); } if (computeSideEffects && i == 0) { PureFunctionIdentifier.Driver mark = new PureFunctionIdentifier.Driver(compiler, null, false); mark.process(externsRoot, mainRoot); } if (markNoSideEffects && i == 0) { MarkNoSideEffectCalls mark = new MarkNoSideEffectCalls(compiler); mark.process(externsRoot, mainRoot); } if (gatherExternPropertiesEnabled && i == 0) { (new GatherExternProperties(compiler)).process(externsRoot, mainRoot); } recentChange.reset(); getProcessor(compiler).process(externsRoot, mainRoot); if (astValidationEnabled) { (new AstValidator(compiler)).validateRoot(root); } if (checkLineNumbers) { (new LineNumberCheck(compiler)).process(externsRoot, mainRoot); } if (runTypeCheckAfterProcessing && typeCheckEnabled && i == 0) { TypeCheck check = createTypeCheck(compiler, typeCheckLevel); check.processForTesting(externsRoot, mainRoot); } hasCodeChanged = hasCodeChanged || recentChange.hasCodeChanged(); aggregateWarningCount += errorManagers[i].getWarningCount(); Collections.addAll(aggregateWarnings, compiler.getWarnings()); if (normalizeEnabled) { boolean verifyDeclaredConstants = true; new Normalize.VerifyConstants(compiler, verifyDeclaredConstants) .process(externsRoot, mainRoot); } } } if (error == null) { assertEquals( "Unexpected error(s): " + Joiner.on("\n").join(compiler.getErrors()), 0, compiler.getErrorCount()); // Verify the symbol table. ErrorManager symbolTableErrorManager = new BlackHoleErrorManager(compiler); Node expectedRoot = null; if (expected != null) { expectedRoot = parseExpectedJs(expected); expectedRoot.detachFromParent(); } JSError[] stErrors = symbolTableErrorManager.getErrors(); if (expectedSymbolTableError != null) { assertEquals("There should be one error.", 1, stErrors.length); assertEquals(expectedSymbolTableError, stErrors[0].getType()); } else { assertEquals( "Unexpected symbol table error(s): " + Joiner.on("\n").join(stErrors), 0, stErrors.length); } if (warning == null) { assertEquals( "Unexpected warning(s): " + Joiner.on("\n").join(aggregateWarnings), 0, aggregateWarningCount); } else { assertEquals( "There should be one warning, repeated " + numRepetitions + " time(s). Warnings: " + aggregateWarnings, numRepetitions, aggregateWarningCount); for (int i = 0; i < numRepetitions; ++i) { JSError[] warnings = errorManagers[i].getWarnings(); JSError actual = warnings[0]; assertEquals(warning, actual.getType()); // Make sure that source information is always provided. if (!allowSourcelessWarnings) { assertTrue( "Missing source file name in warning", actual.sourceName != null && !actual.sourceName.isEmpty()); assertTrue("Missing line number in warning", -1 != actual.lineNumber); assertTrue("Missing char number in warning", -1 != actual.getCharno()); } if (description != null) { assertEquals(description, actual.description); } } } // If we ran normalize on the AST, we must also run normalize on the // clone before checking for changes. if (normalizeEnabled) { normalizeActualCode(compiler, externsRootClone, mainRootClone); } boolean codeChange = !mainRootClone.isEquivalentTo(mainRoot); boolean externsChange = !externsRootClone.isEquivalentTo(externsRoot); // Generally, externs should not be changed by the compiler passes. if (externsChange && !allowExternsChanges) { String explanation = externsRootClone.checkTreeEquals(externsRoot); fail( "Unexpected changes to externs" + "\nExpected: " + compiler.toSource(externsRootClone) + "\nResult: " + compiler.toSource(externsRoot) + "\n" + explanation); } if (!codeChange && !externsChange) { assertFalse( "compiler.reportCodeChange() was called " + "even though nothing changed", hasCodeChanged); } else { assertTrue( "compiler.reportCodeChange() should have been called." + "\nOriginal: " + mainRootClone.toStringTree() + "\nNew: " + mainRoot.toStringTree(), hasCodeChanged); } // Check correctness of the changed-scopes-only traversal NodeUtil.verifyScopeChanges(mtoc, mainRoot, false, compiler); if (expected != null) { if (compareAsTree) { String explanation; if (compareJsDoc) { explanation = expectedRoot.checkTreeEqualsIncludingJsDoc(mainRoot); } else { explanation = expectedRoot.checkTreeEquals(mainRoot); } assertNull( "\nExpected: " + compiler.toSource(expectedRoot) + "\nResult: " + compiler.toSource(mainRoot) + "\n" + explanation, explanation); } else if (expected != null) { String[] expectedSources = new String[expected.size()]; for (int i = 0; i < expected.size(); ++i) { try { expectedSources[i] = expected.get(i).getCode(); } catch (IOException e) { throw new RuntimeException("failed to get source code", e); } } assertEquals(Joiner.on("").join(expectedSources), compiler.toSource(mainRoot)); } } // Verify normalization is not invalidated. Node normalizeCheckRootClone = root.cloneTree(); Node normalizeCheckExternsRootClone = normalizeCheckRootClone.getFirstChild(); Node normalizeCheckMainRootClone = normalizeCheckRootClone.getLastChild(); new PrepareAst(compiler).process(normalizeCheckExternsRootClone, normalizeCheckMainRootClone); String explanation = normalizeCheckMainRootClone.checkTreeEquals(mainRoot); assertNull( "Node structure normalization invalidated." + "\nExpected: " + compiler.toSource(normalizeCheckMainRootClone) + "\nResult: " + compiler.toSource(mainRoot) + "\n" + explanation, explanation); // TODO(johnlenz): enable this for most test cases. // Currently, this invalidates test for while-loops, for-loop // initializers, and other naming. However, a set of code // (Closure primitive rewrites, etc) runs before the Normalize pass, // so this can't be force on everywhere. if (normalizeEnabled) { new Normalize(compiler, true) .process(normalizeCheckExternsRootClone, normalizeCheckMainRootClone); explanation = normalizeCheckMainRootClone.checkTreeEquals(mainRoot); assertNull( "Normalization invalidated." + "\nExpected: " + compiler.toSource(normalizeCheckMainRootClone) + "\nResult: " + compiler.toSource(mainRoot) + "\n" + explanation, explanation); } } else { String errors = ""; for (JSError actualError : compiler.getErrors()) { errors += actualError.description + "\n"; } assertEquals("There should be one error. " + errors, 1, compiler.getErrorCount()); assertEquals(errors, error, compiler.getErrors()[0].getType()); if (warning != null) { String warnings = ""; for (JSError actualError : compiler.getWarnings()) { warnings += actualError.description + "\n"; } assertEquals("There should be one warning. " + warnings, 1, compiler.getWarningCount()); assertEquals(warnings, warning, compiler.getWarnings()[0].getType()); } } }
/** * Expand jQuery.extend (and derivative) calls into direct object assignments Example: * jQuery.extend(obj1, {prop1: val1, prop2: val2}) -> obj1.prop1 = val1; obj1.prop2 = val2; */ private void maybeExpandJqueryExtendCall(Node n) { Node callTarget = n.getFirstChild(); Node objectToExtend = callTarget.getNext(); // first argument Node extendArg = objectToExtend.getNext(); // second argument boolean ensureObjectDefined = true; if (extendArg == null) { // Only one argument was specified, so extend jQuery namespace extendArg = objectToExtend; objectToExtend = callTarget.getFirstChild(); ensureObjectDefined = false; } else if (objectToExtend.isGetProp() && (objectToExtend.getLastChild().getString().equals("prototype") || convention.isPrototypeAlias(objectToExtend))) { ensureObjectDefined = false; } // Check for an empty object literal if (!extendArg.hasChildren()) { return; } // Since we are expanding jQuery.extend calls into multiple statements, // encapsulate the new statements in a new block. Node fncBlock = IR.block().srcref(n); if (ensureObjectDefined) { Node assignVal = IR.or(objectToExtend.cloneTree(), IR.objectlit().srcref(n)).srcref(n); Node assign = IR.assign(objectToExtend.cloneTree(), assignVal).srcref(n); fncBlock.addChildrenToFront(IR.exprResult(assign).srcref(n)); } while (extendArg.hasChildren()) { Node currentProp = extendArg.removeFirstChild(); currentProp.setType(Token.STRING); Node propValue = currentProp.removeFirstChild(); Node newProp; if (currentProp.isQuotedString()) { newProp = IR.getelem(objectToExtend.cloneTree(), currentProp).srcref(currentProp); } else { newProp = IR.getprop(objectToExtend.cloneTree(), currentProp).srcref(currentProp); } Node assignNode = IR.assign(newProp, propValue).srcref(currentProp); fncBlock.addChildToBack(IR.exprResult(assignNode).srcref(currentProp)); } // Check to see if the return value is used. If not, replace the original // call with new block. Otherwise, wrap the statements in an // immediately-called anonymous function. if (n.getParent().isExprResult()) { Node parent = n.getParent(); parent.getParent().replaceChild(parent, fncBlock); } else { Node targetVal; if ("jQuery.prototype".equals(objectToExtend.getQualifiedName())) { // When extending the jQuery prototype, return the jQuery namespace. // This is not commonly used. targetVal = objectToExtend.removeFirstChild(); } else { targetVal = objectToExtend.detachFromParent(); } fncBlock.addChildToBack(IR.returnNode(targetVal).srcref(targetVal)); Node fnc = IR.function(IR.name("").srcref(n), IR.paramList().srcref(n), fncBlock).srcref(n); // add an explicit "call" statement so that we can maintain // the same reference for "this" Node newCallTarget = IR.getprop(fnc, IR.string("call").srcref(n)).srcref(n); n.replaceChild(callTarget, newCallTarget); n.putBooleanProp(Node.FREE_CALL, false); // remove any other pre-existing call arguments while (newCallTarget.getNext() != null) { n.removeChildAfter(newCallTarget); } n.addChildToBack(IR.thisNode().srcref(n)); } compiler.reportCodeChange(); }
private static Node maybeDetach(Node node) { if (node != null && node.getParent() != null) { node.detachFromParent(); } return node; }
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(); }