/** * Duplicates the PolymerElement externs with a different element base class if needed. For * example, if the base class is HTMLInputElement, then a class PolymerInputElement will be added. * If the element does not extend a native HTML element, this method is a no-op. */ private void appendPolymerElementExterns(final ClassDefinition cls) { if (!nativeExternsAdded.add(cls.nativeBaseElement)) { return; } Node block = IR.block(); Node baseExterns = polymerElementExterns.cloneTree(); String polymerElementType = getPolymerElementType(cls); baseExterns.getFirstChild().setString(polymerElementType); String elementType = tagNameMap.get(cls.nativeBaseElement); JSTypeExpression elementBaseType = new JSTypeExpression(new Node(Token.BANG, IR.string(elementType)), VIRTUAL_FILE); JSDocInfoBuilder baseDocs = JSDocInfoBuilder.copyFrom(baseExterns.getJSDocInfo()); baseDocs.changeBaseType(elementBaseType); baseExterns.setJSDocInfo(baseDocs.build()); block.addChildToBack(baseExterns); for (Node baseProp : polymerElementProps) { Node newProp = baseProp.cloneTree(); Node newPropRootName = NodeUtil.getRootOfQualifiedName(newProp.getFirstChild().getFirstChild()); newPropRootName.setString(polymerElementType); block.addChildToBack(newProp); } Node parent = polymerElementExterns.getParent(); Node stmts = block.removeChildren(); parent.addChildrenAfter(stmts, polymerElementExterns); compiler.reportCodeChange(); }
/** * 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(); }
/** 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. }
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(); }
/** * 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; }
/** * Expand a jQuery.expandedEach call * * <p>Expanded jQuery.expandedEach calls will replace the GETELEM nodes of a property assignment * with GETPROP nodes to allow for renaming. */ private void maybeExpandJqueryEachCall(NodeTraversal t, Node n) { Node objectToLoopOver = n.getChildAtIndex(1); if (objectToLoopOver == null) { return; } Node callbackFunction = objectToLoopOver.getNext(); if (callbackFunction == null || !callbackFunction.isFunction()) { return; } // Run the peephole optimizations on the first argument to handle // cases like ("a " + "b").split(" ") peepholePasses.process(null, n.getChildAtIndex(1)); // Create a reference tree Node nClone = n.cloneTree(); objectToLoopOver = nClone.getChildAtIndex(1); // Check to see if the first argument is something we recognize and can // expand. if (!objectToLoopOver.isObjectLit() && !(objectToLoopOver.isArrayLit() && isArrayLitValidForExpansion(objectToLoopOver))) { t.report(n, JQUERY_UNABLE_TO_EXPAND_INVALID_LIT_ERROR, (String) null); return; } // Find all references to the callback function arguments List<Node> keyNodeReferences = new ArrayList<>(); List<Node> valueNodeReferences = new ArrayList<>(); new NodeTraversal( compiler, new FindCallbackArgumentReferences( callbackFunction, keyNodeReferences, valueNodeReferences, objectToLoopOver.isArrayLit())) .traverseInnerNode( NodeUtil.getFunctionBody(callbackFunction), callbackFunction, t.getScope()); if (keyNodeReferences.isEmpty()) { // We didn't do anything useful ... t.report(n, JQUERY_USELESS_EACH_EXPANSION, (String) null); return; } Node fncBlock = tryExpandJqueryEachCall( t, nClone, callbackFunction, keyNodeReferences, valueNodeReferences); if (fncBlock != null && fncBlock.hasChildren()) { replaceOriginalJqueryEachCall(n, fncBlock); } else { // We didn't do anything useful ... t.report(n, JQUERY_USELESS_EACH_EXPANSION, (String) null); } }
/** * Notice that "call" and "bind" have the same argument signature, except that all the arguments * of "bind" (except the first) are optional. */ private FunctionType getCallOrBindSignature(boolean isCall) { boolean isBind = !isCall; FunctionBuilder builder = new FunctionBuilder(registry) .withReturnType(isCall ? getReturnType() : getBindReturnType(-1)) .withTemplateKeys(getTemplateTypeMap().getTemplateKeys()); Node origParams = getParametersNode(); if (origParams != null) { Node params = origParams.cloneTree(); Node thisTypeNode = Node.newString(Token.NAME, "thisType"); thisTypeNode.setJSType(registry.createOptionalNullableType(getTypeOfThis())); params.addChildToFront(thisTypeNode); if (isBind) { // The arguments of bind() are unique in that they are all // optional but not undefinable. for (Node current = thisTypeNode.getNext(); current != null; current = current.getNext()) { current.setOptionalArg(true); } } else if (isCall) { // The first argument of call() is optional iff all the arguments // are optional. It's sufficient to check the first argument. Node firstArg = thisTypeNode.getNext(); if (firstArg == null || firstArg.isOptionalArg() || firstArg.isVarArgs()) { thisTypeNode.setOptionalArg(true); } } builder.withParamsNode(params); } return builder.build(); }
/** * @param name The Name whose properties references should be updated. * @param value The value to use when rewriting. * @param depth The chain depth. * @param newNodes Expression nodes that have been updated. */ private static void rewriteAliasProps(Name name, Node value, int depth, Set<AstChange> newNodes) { if (name.props == null) { return; } Preconditions.checkState(!value.matchesQualifiedName(name.getFullName())); for (Name prop : name.props) { rewriteAliasProps(prop, value, depth + 1, newNodes); List<Ref> refs = new ArrayList<>(prop.getRefs()); for (Ref ref : refs) { Node target = ref.node; for (int i = 0; i <= depth; i++) { if (target.isGetProp()) { target = target.getFirstChild(); } else if (NodeUtil.isObjectLitKey(target)) { // Object literal key definitions are a little trickier, as we // need to find the assignment target Node gparent = target.getParent().getParent(); if (gparent.isAssign()) { target = gparent.getFirstChild(); } else { Preconditions.checkState(NodeUtil.isObjectLitKey(gparent)); target = gparent; } } else { throw new IllegalStateException("unexpected: " + target); } } Preconditions.checkState(target.isGetProp() || target.isName()); target.getParent().replaceChild(target, value.cloneTree()); prop.removeRef(ref); // Rescan the expression root. newNodes.add(new AstChange(ref.module, ref.scope, ref.node)); } } }
private void splitFiles(String[] input) { Compiler compiler = new Compiler(); List<SourceFile> files = Lists.newArrayList(); for (int i = 0; i < input.length; i++) { files.add(SourceFile.fromCode("file" + i, input[i])); } compiler.init(ImmutableList.<SourceFile>of(), files, new CompilerOptions()); compiler.parse(); Node original = compiler.getRoot(); Node root = original.cloneTree(); AstParallelizer parallelizer = AstParallelizer.createNewFileLevelAstParallelizer(root); List<Node> forest = parallelizer.split(); assertEquals(input.length, forest.size()); int i = 0; for (Node n : forest) { Node tree = compiler.parseTestCode(input[i++]); assertEquals(compiler.toSource(tree), compiler.toSource(n)); } parallelizer.join(); assertTrue(original.isEquivalentTo(root)); }
/** * Prepare an template AST to use when performing matches. * * @param templateFunctionNode The template declaration function to extract the template AST from. * @return The first node of the template AST sequence to use when matching. */ private Node initTemplate(Node templateFunctionNode) { Node prepped = templateFunctionNode.cloneTree(); prepTemplatePlaceholders(prepped); Node body = prepped.getLastChild(); Node startNode; if (body.hasOneChild() && body.getFirstChild().isExprResult()) { // When matching an expression, don't require it to be a complete // statement. startNode = body.getFirstFirstChild(); } else { startNode = body.getFirstChild(); } for (int i = 0; i < templateLocals.size(); i++) { // reserve space in the locals array. this.localVarMatches.add(null); } for (int i = 0; i < templateParams.size(); i++) { // reserve space in the params array. this.paramNodeMatches.add(null); } return startNode; }
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(); }
/** * Constructs a Node that represents an access to the given class member, qualified by either the * static or the instance access context, depending on whether the member is static. * * <p><b>WARNING:</b> {@code member} may be modified/destroyed by this method, do not use it * afterwards. */ static Node getQualifiedMemberAccess( AbstractCompiler compiler, Node member, Node staticAccess, Node instanceAccess) { Node context = member.isStaticMember() ? staticAccess : instanceAccess; context = context.cloneTree(); if (member.isComputedProp()) { return IR.getelem(context, member.removeFirstChild()); } else { return NodeUtil.newPropertyAccess(compiler, context, member.getString()); } }
/** * Splits at function level with {@link AstParallelizer#split()}, verify the output matches what * is expected and then verify {@link AstParallelizer#join()} can reverse the whole process. */ private void splitFunctions(String input, String... output) { Compiler compiler = new Compiler(); Node original = compiler.parseTestCode(input); Node root = original.cloneTree(); AstParallelizer parallelizer = AstParallelizer.createNewFunctionLevelAstParallelizer(root, true); List<Node> forest = parallelizer.split(); assertEquals(output.length, forest.size()); int i = 0; for (Node n : forest) { Node tree = compiler.parseTestCode(output[i++]); assertEquals(compiler.toSource(tree), compiler.toSource(n)); } parallelizer.join(); assertTrue(original.isEquivalentTo(root)); }
/** * Get the return value of calling "bind" on this function with the specified number of arguments. * * <p>If -1 is passed, then we will return a result that accepts any parameters. */ public FunctionType getBindReturnType(int argsToBind) { FunctionBuilder builder = new FunctionBuilder(registry) .withReturnType(getReturnType()) .withTemplateKeys(getTemplateTypeMap().getTemplateKeys()); if (argsToBind >= 0) { Node origParams = getParametersNode(); if (origParams != null) { Node params = origParams.cloneTree(); for (int i = 1; i < argsToBind && params.getFirstChild() != null; i++) { if (params.getFirstChild().isVarArgs()) { break; } params.removeFirstChild(); } builder.withParamsNode(params); } } return builder.build(); }
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; }
/** Helper for transpiling DEFAULT_VALUE trees. */ private static Node defaultValueHook(Node getprop, Node defaultValue) { return IR.hook(IR.sheq(getprop, IR.name("undefined")), defaultValue, getprop.cloneTree()); }
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(); }
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(); }
/** * 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(); }
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; }
/** * 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(); }
/** * 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()); } } }
/** * Classes are processed in 3 phases: * * <ol> * <li>The class name is extracted. * <li>Class members are processed and rewritten. * <li>The constructor is built. * </ol> */ private void visitClass(Node classNode, Node parent) { checkClassReassignment(classNode); // Collect Metadata ClassDeclarationMetadata metadata = ClassDeclarationMetadata.create(classNode, parent); if (metadata == null || metadata.fullClassName == null) { cannotConvert( parent, "Can only convert classes that are declarations or the right hand" + " side of a simple assignment."); return; } if (metadata.hasSuperClass() && !metadata.superClassNameNode.isQualifiedName()) { compiler.report(JSError.make(metadata.superClassNameNode, DYNAMIC_EXTENDS_TYPE)); return; } boolean useUnique = NodeUtil.isStatement(classNode) && !NodeUtil.isInFunction(classNode); String uniqueFullClassName = useUnique ? getUniqueClassName(metadata.fullClassName) : metadata.fullClassName; Node classNameAccess = NodeUtil.newQName(compiler, uniqueFullClassName); Node prototypeAccess = NodeUtil.newPropertyAccess(compiler, classNameAccess, "prototype"); Preconditions.checkState( NodeUtil.isStatement(metadata.insertionPoint), "insertion point must be a statement: %s", metadata.insertionPoint); Node constructor = null; JSDocInfo ctorJSDocInfo = null; // Process all members of the class Node classMembers = classNode.getLastChild(); Map<String, JSDocInfo> prototypeMembersToDeclare = new LinkedHashMap<>(); Map<String, JSDocInfo> classMembersToDeclare = new LinkedHashMap<>(); for (Node member : classMembers.children()) { if (member.isEmpty()) { continue; } Preconditions.checkState( member.isMemberFunctionDef() || member.isGetterDef() || member.isSetterDef() || (member.isComputedProp() && !member.getBooleanProp(Node.COMPUTED_PROP_VARIABLE)), "Member variables should have been transpiled earlier: ", member); if (member.isGetterDef() || member.isSetterDef()) { JSTypeExpression typeExpr = getTypeFromGetterOrSetter(member).clone(); addToDefinePropertiesObject(metadata, member); Map<String, JSDocInfo> membersToDeclare = member.isStaticMember() ? classMembersToDeclare : prototypeMembersToDeclare; JSDocInfo existingJSDoc = membersToDeclare.get(member.getString()); JSTypeExpression existingType = existingJSDoc == null ? null : existingJSDoc.getType(); if (existingType != null && !existingType.equals(typeExpr)) { compiler.report(JSError.make(member, CONFLICTING_GETTER_SETTER_TYPE, member.getString())); } else { JSDocInfoBuilder jsDoc = new JSDocInfoBuilder(false); jsDoc.recordType(typeExpr); if (member.getJSDocInfo() != null && member.getJSDocInfo().isExport()) { jsDoc.recordExport(); } if (member.isStaticMember()) { jsDoc.recordNoCollapse(); } membersToDeclare.put(member.getString(), jsDoc.build()); } } else if (member.isMemberFunctionDef() && member.getString().equals("constructor")) { ctorJSDocInfo = member.getJSDocInfo(); constructor = member.getFirstChild().detachFromParent(); if (!metadata.anonymous) { // Turns class Foo { constructor: function() {} } into function Foo() {}, // i.e. attaches the name the ctor function. constructor.replaceChild(constructor.getFirstChild(), metadata.classNameNode.cloneNode()); } } else { Node qualifiedMemberAccess = getQualifiedMemberAccess(compiler, member, classNameAccess, prototypeAccess); Node method = member.getLastChild().detachFromParent(); Node assign = IR.assign(qualifiedMemberAccess, method); assign.useSourceInfoIfMissingFromForTree(member); JSDocInfo info = member.getJSDocInfo(); if (member.isStaticMember() && NodeUtil.referencesThis(assign.getLastChild())) { JSDocInfoBuilder memberDoc = JSDocInfoBuilder.maybeCopyFrom(info); memberDoc.recordThisType( new JSTypeExpression( new Node(Token.BANG, new Node(Token.QMARK)), member.getSourceFileName())); info = memberDoc.build(); } if (info != null) { assign.setJSDocInfo(info); } Node newNode = NodeUtil.newExpr(assign); metadata.insertNodeAndAdvance(newNode); } } // Add declarations for properties that were defined with a getter and/or setter, // so that the typechecker knows those properties exist on the class. // This is a temporary solution. Eventually, the type checker should understand // Object.defineProperties calls directly. for (Map.Entry<String, JSDocInfo> entry : prototypeMembersToDeclare.entrySet()) { String declaredMember = entry.getKey(); Node declaration = IR.getprop(prototypeAccess.cloneTree(), IR.string(declaredMember)); declaration.setJSDocInfo(entry.getValue()); metadata.insertNodeAndAdvance( IR.exprResult(declaration).useSourceInfoIfMissingFromForTree(classNode)); } for (Map.Entry<String, JSDocInfo> entry : classMembersToDeclare.entrySet()) { String declaredMember = entry.getKey(); Node declaration = IR.getprop(classNameAccess.cloneTree(), IR.string(declaredMember)); declaration.setJSDocInfo(entry.getValue()); metadata.insertNodeAndAdvance( IR.exprResult(declaration).useSourceInfoIfMissingFromForTree(classNode)); } if (metadata.definePropertiesObjForPrototype.hasChildren()) { Node definePropsCall = IR.exprResult( IR.call( NodeUtil.newQName(compiler, "Object.defineProperties"), prototypeAccess.cloneTree(), metadata.definePropertiesObjForPrototype)); definePropsCall.useSourceInfoIfMissingFromForTree(classNode); metadata.insertNodeAndAdvance(definePropsCall); } if (metadata.definePropertiesObjForClass.hasChildren()) { Node definePropsCall = IR.exprResult( IR.call( NodeUtil.newQName(compiler, "Object.defineProperties"), classNameAccess.cloneTree(), metadata.definePropertiesObjForClass)); definePropsCall.useSourceInfoIfMissingFromForTree(classNode); metadata.insertNodeAndAdvance(definePropsCall); } Preconditions.checkNotNull(constructor); JSDocInfo classJSDoc = NodeUtil.getBestJSDocInfo(classNode); JSDocInfoBuilder newInfo = JSDocInfoBuilder.maybeCopyFrom(classJSDoc); newInfo.recordConstructor(); if (metadata.hasSuperClass()) { String superClassString = metadata.superClassNameNode.getQualifiedName(); if (newInfo.isInterfaceRecorded()) { newInfo.recordExtendedInterface( new JSTypeExpression( new Node(Token.BANG, IR.string(superClassString)), metadata.superClassNameNode.getSourceFileName())); } else { Node inherits = IR.call( NodeUtil.newQName(compiler, INHERITS), NodeUtil.newQName(compiler, metadata.fullClassName), NodeUtil.newQName(compiler, superClassString)); Node inheritsCall = IR.exprResult(inherits); compiler.needsEs6Runtime = true; inheritsCall.useSourceInfoIfMissingFromForTree(classNode); Node enclosingStatement = NodeUtil.getEnclosingStatement(classNode); enclosingStatement.getParent().addChildAfter(inheritsCall, enclosingStatement); newInfo.recordBaseType( new JSTypeExpression( new Node(Token.BANG, IR.string(superClassString)), metadata.superClassNameNode.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)); } newInfo.mergePropertyBitfieldFrom(ctorJSDocInfo); } if (NodeUtil.isStatement(classNode)) { constructor.getFirstChild().setString(""); Node ctorVar = IR.let(metadata.classNameNode.cloneNode(), constructor); ctorVar.useSourceInfoIfMissingFromForTree(classNode); parent.replaceChild(classNode, ctorVar); } else { parent.replaceChild(classNode, constructor); } if (NodeUtil.isStatement(constructor)) { constructor.setJSDocInfo(newInfo.build()); } 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()); } else if (constructor.getParent().isName()) { // Is a newly created VAR node. Node var = constructor.getParent().getParent(); var.setJSDocInfo(newInfo.build()); } else if (parent.isAssign()) { // The constructor function is the RHS of an assignment. // Add the JSDoc to the ASSIGN node. parent.setJSDocInfo(newInfo.build()); } else { throw new IllegalStateException("Unexpected parent node " + parent); } compiler.reportCodeChange(); }