static ClassDeclarationMetadata create(Node classNode, Node parent) { Node classNameNode = classNode.getFirstChild(); Node superClassNameNode = classNameNode.getNext(); // 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 return null. if (NodeUtil.isStatement(classNode)) { return new ClassDeclarationMetadata( classNode, classNameNode.getString(), false, classNameNode, superClassNameNode); } else if (parent.isAssign() && parent.getParent().isExprResult()) { // Add members after the EXPR_RESULT node: // example.C = class {}; example.C.prototype.foo = function() {}; String fullClassName = parent.getFirstChild().getQualifiedName(); if (fullClassName == null) { return null; } return new ClassDeclarationMetadata( parent.getParent(), fullClassName, true, classNameNode, superClassNameNode); } else if (parent.isName()) { // Add members after the 'var' statement. // var C = class {}; C.prototype.foo = function() {}; return new ClassDeclarationMetadata( parent.getParent(), parent.getString(), true, classNameNode, superClassNameNode); } else { // Cannot handle this class declaration. return null; } }
/** * @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 addStatement(GenerateNodeContext context, Node stmt) { CodingConvention convention = compiler.getCodingConvention(); Node n = context.getNode(); Node exprRoot = n; while (!NodeUtil.isStatementBlock(exprRoot.getParent())) { exprRoot = exprRoot.getParent(); } // It's important that any class-building calls (goog.inherits) // come right after the class definition, so move the export after that. while (true) { Node next = exprRoot.getNext(); if (next != null && NodeUtil.isExprCall(next) && convention.getClassesDefinedByCall(next.getFirstChild()) != null) { exprRoot = next; } else { break; } } Node block = exprRoot.getParent(); block.addChildAfter(stmt, exprRoot); }
private static void fuseExpressionIntoControlFlowStatement(Node before, Node control) { Preconditions.checkArgument(before.isExprResult(), "before must be expression result"); // Now we are just left with two statements. The comma tree of the first // n - 1 statements (which can be used in an expression) and the last // statement. We perform specific fusion based on the last statement's type. switch (control.getToken()) { case IF: case RETURN: case THROW: case SWITCH: case EXPR_RESULT: before.getParent().removeChild(before); fuseExpressionIntoFirstChild(before.removeFirstChild(), control); return; case FOR: before.getParent().removeChild(before); if (NodeUtil.isForIn(control)) { fuseExpressionIntoSecondChild(before.removeFirstChild(), control); } else { fuseExpressionIntoFirstChild(before.removeFirstChild(), control); } return; case LABEL: fuseExpressionIntoControlFlowStatement(before, control.getLastChild()); return; case BLOCK: fuseExpressionIntoControlFlowStatement(before, control.getFirstChild()); return; default: throw new IllegalStateException("Statement fusion missing."); } }
/** Replace the current assign with its right hand side. */ void remove() { Node parent = assignNode.getParent(); if (mayHaveSecondarySideEffects) { Node replacement = assignNode.getLastChild().detachFromParent(); // Aggregate any expressions in GETELEMs. for (Node current = assignNode.getFirstChild(); current.getType() != Token.NAME; current = current.getFirstChild()) { if (current.getType() == Token.GETELEM) { replacement = new Node(Token.COMMA, current.getLastChild().detachFromParent(), replacement); replacement.copyInformationFrom(current); } } parent.replaceChild(assignNode, replacement); } else { Node gramps = parent.getParent(); if (parent.getType() == Token.EXPR_RESULT) { gramps.removeChild(parent); } else { parent.replaceChild(assignNode, assignNode.getLastChild().detachFromParent()); } } }
private void handleContinue(Node node) { String label = null; if (node.hasChildren()) { label = node.getFirstChild().getString(); } Node cur; Node lastJump; // Similar to handBreak's logic with a few minor variation. Node parent = node.getParent(); for (cur = node, lastJump = node; !isContinueTarget(cur, parent, label); cur = parent, parent = parent.getParent()) { if (cur.getType() == Token.TRY && NodeUtil.hasFinally(cur)) { if (lastJump == node) { createEdge(lastJump, Branch.UNCOND, cur.getLastChild()); } else { finallyMap.put(lastJump, computeFallThrough(cur.getLastChild())); } lastJump = cur; } Preconditions.checkState(parent != null, "Cannot find continue target."); } Node iter = cur; if (cur.getChildCount() == 4) { iter = cur.getFirstChild().getNext().getNext(); } if (lastJump == node) { createEdge(node, Branch.UNCOND, iter); } else { finallyMap.put(lastJump, iter); } }
/** Returns whether this node must be coerced to a string. */ private static boolean inForcedStringContext(Node n) { if (n.getParent().isGetElem() && n.getParent().getLastChild() == n) { return true; } // we can fold in the case "" + new String("") return n.getParent().isAdd(); }
/** * 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(); }
private void visitObjectWithComputedProperty(Node obj, Node parent) { Preconditions.checkArgument(obj.isObjectLit()); List<Node> props = new ArrayList<>(); Node currElement = obj.getFirstChild(); while (currElement != null) { if (currElement.getBooleanProp(Node.COMPUTED_PROP_GETTER) || currElement.getBooleanProp(Node.COMPUTED_PROP_SETTER)) { cannotConvertYet(currElement, "computed getter/setter"); return; } else if (currElement.isGetterDef() || currElement.isSetterDef()) { currElement = currElement.getNext(); } else { Node nextNode = currElement.getNext(); obj.removeChild(currElement); props.add(currElement); currElement = nextNode; } } String objName = FRESH_COMP_PROP_VAR + compiler.getUniqueNameIdSupplier().get(); props = Lists.reverse(props); Node result = IR.name(objName); for (Node propdef : props) { if (propdef.isComputedProp()) { Node propertyExpression = propdef.removeFirstChild(); Node value = propdef.removeFirstChild(); result = IR.comma(IR.assign(IR.getelem(IR.name(objName), propertyExpression), value), result); } else { if (!propdef.hasChildren()) { Node name = IR.name(propdef.getString()).useSourceInfoIfMissingFrom(propdef); propdef.addChildToBack(name); } Node val = propdef.removeFirstChild(); propdef.setType(Token.STRING); int type = propdef.isQuotedString() ? Token.GETELEM : Token.GETPROP; Node access = new Node(type, IR.name(objName), propdef); result = IR.comma(IR.assign(access, val), result); } } Node statement = obj; while (!NodeUtil.isStatement(statement)) { statement = statement.getParent(); } result.useSourceInfoIfMissingFromForTree(obj); parent.replaceChild(obj, result); Node var = IR.var(IR.name(objName), obj); var.useSourceInfoIfMissingFromForTree(statement); statement.getParent().addChildBefore(var, statement); compiler.reportCodeChange(); }
/** Applies optimizations to all previously marked nodes. */ public void applyChanges() { for (Node n : toRemove) { n.getParent().removeChild(n); compiler.reportCodeChange(); } for (Node n : toReplaceWithZero) { n.getParent().replaceChild(n, Node.newNumber(0).copyInformationFrom(n)); compiler.reportCodeChange(); } }
public void reattach() { // If the place-holder no longer has a parent, this implies the function // has been removed from the AST. if (placeHolder.getParent() != null) { if (before == null) { placeHolder.getParent().addChildrenToFront(original); placeHolder.getParent().removeChildAfter(original); } else { placeHolder.getParent().addChildAfter(original, before); placeHolder.getParent().removeChildAfter(original); } } }
/** * Creates a new block. * * @param parent The containing block. * @param root The root node of the block. */ BasicBlock(BasicBlock parent, Node root) { this.parent = parent; this.root = root; this.isFunction = root.isFunction(); if (root.getParent() != null) { int pType = root.getParent().getType(); this.isLoop = pType == Token.DO || pType == Token.WHILE || pType == Token.FOR; } else { this.isLoop = false; } }
private void rewriteClassDefinition(Node n, Node parent, NodeTraversal t) { if (parent.getParent().isConst() || parent.getParent().isLet()) { compiler.report(JSError.make(n, POLYMER_INVALID_DECLARATION)); return; } ClassDefinition def = extractClassDefinition(n); if (def != null) { if (NodeUtil.isNameDeclaration(parent.getParent()) || parent.isAssign()) { rewritePolymerClass(parent.getParent(), def, t); } else { rewritePolymerClass(parent, def, t); } } }
boolean isDeclaration() { Node parent = getParent(); Node grandparent = parent.getParent(); return DECLARATION_PARENTS.contains(parent.getType()) || parent.isParamList() && grandparent.isFunction(); }
/** @return Whether the name is used in a way that might be a candidate for inlining. */ static boolean isCandidateUsage(Node name) { Node parent = name.getParent(); Preconditions.checkState(name.isName()); if (parent.isVar() || parent.isFunction()) { // This is a declaration. Duplicate declarations are handle during // function candidate gathering. return true; } if (parent.isCall() && parent.getFirstChild() == name) { // This is a normal reference to the function. return true; } // Check for a ".call" to the named function: // CALL // GETPROP/GETELEM // NAME // STRING == "call" // This-Value // Function-parameter-1 // ... if (NodeUtil.isGet(parent) && name == parent.getFirstChild() && name.getNext().isString() && name.getNext().getString().equals("call")) { Node gramps = name.getAncestor(2); if (gramps.isCall() && gramps.getFirstChild() == parent) { // Yep, a ".call". return true; } } return false; }
/** Try to fold {@code left instanceof right} into {@code true} or {@code false}. */ private Node tryFoldInstanceof(Node n, Node left, Node right) { Preconditions.checkArgument(n.isInstanceOf()); // TODO(johnlenz) Use type information if available to fold // instanceof. if (NodeUtil.isLiteralValue(left, true) && !mayHaveSideEffects(right)) { Node replacementNode = null; if (NodeUtil.isImmutableValue(left)) { // Non-object types are never instances. replacementNode = IR.falseNode(); } else if (right.isName() && "Object".equals(right.getString())) { replacementNode = IR.trueNode(); } if (replacementNode != null) { n.getParent().replaceChild(n, replacementNode); reportCodeChange(); return replacementNode; } } return n; }
/** Try to fold array-length. e.g [1, 2, 3].length ==> 3, [x, y].length ==> 2 */ private Node tryFoldGetProp(Node n, Node left, Node right) { Preconditions.checkArgument(n.isGetProp()); if (left.isObjectLit()) { return tryFoldObjectPropAccess(n, left, right); } if (right.isString() && right.getString().equals("length")) { int knownLength = -1; switch (left.getType()) { case ARRAYLIT: if (mayHaveSideEffects(left)) { // Nope, can't fold this, without handling the side-effects. return n; } knownLength = left.getChildCount(); break; case STRING: knownLength = left.getString().length(); break; default: // Not a foldable case, forget it. return n; } Preconditions.checkState(knownLength != -1); Node lengthNode = IR.number(knownLength); n.getParent().replaceChild(n, lengthNode); reportCodeChange(); return lengthNode; } return n; }
private static boolean isLhsOfForInExpression(Node n) { Node parent = n.getParent(); if (parent.isVar()) { return isLhsOfForInExpression(parent); } return NodeUtil.isForIn(parent) && parent.getFirstChild() == n; }
/** * 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(); }
/** * Inline a function that fulfills the requirements of canInlineReferenceDirectly into the call * site, replacing only the CALL node. */ private Node inlineReturnValue(Node callNode, Node fnNode) { Node block = fnNode.getLastChild(); Node callParentNode = callNode.getParent(); // NOTE: As the normalize pass guarantees globals aren't being // shadowed and an expression can't introduce new names, there is // no need to check for conflicts. // Create an argName -> expression map, checking for side effects. Map<String, Node> argMap = FunctionArgumentInjector.getFunctionCallParameterMap( fnNode, callNode, this.safeNameIdSupplier); Node newExpression; if (!block.hasChildren()) { Node srcLocation = block; newExpression = NodeUtil.newUndefinedNode(srcLocation); } else { Node returnNode = block.getFirstChild(); Preconditions.checkArgument(returnNode.getType() == Token.RETURN); // Clone the return node first. Node safeReturnNode = returnNode.cloneTree(); Node inlineResult = FunctionArgumentInjector.inject(safeReturnNode, null, argMap); Preconditions.checkArgument(safeReturnNode == inlineResult); newExpression = safeReturnNode.removeFirstChild(); } callParentNode.replaceChild(callNode, newExpression); return newExpression; }
private static boolean reportIfNonObject(NodeTraversal t, Node n, DiagnosticType diagnosticType) { if (n.isAdd() || !NodeUtil.mayBeObect(n)) { t.report(n.getParent(), diagnosticType); return true; } return false; }
/** * Main entry point for type inference when running over the whole tree. * * @param externsRoot The root of the externs parse tree. * @param jsRoot The root of the input parse tree to be checked. */ public void process(Node externsRoot, Node jsRoot) { Node externsAndJs = jsRoot.getParent(); Preconditions.checkState(externsAndJs != null); Preconditions.checkState(externsRoot == null || externsAndJs.hasChild(externsRoot)); inferTypes(externsAndJs); }
/** * Determines if the given name is a declaration, which can be a declaration of a variable, * function, or argument. */ private static boolean isDeclaration(Node n) { switch (n.getParent().getType()) { case Token.LET: case Token.CONST: case Token.VAR: case Token.FUNCTION: case Token.CATCH: return true; case Token.PARAM_LIST: return n.getParent().getParent().isFunction(); default: return false; } }
/** * Adds an interface for the given ClassDefinition to externs. This allows generated setter * functions for read-only properties to avoid renaming altogether. * * @see https://www.polymer-project.org/0.8/docs/devguide/properties.html#read-only */ private void addInterfaceExterns( final ClassDefinition cls, List<MemberDefinition> readOnlyProps) { Node block = IR.block(); String interfaceName = getInterfaceName(cls); Node fnNode = IR.function(IR.name(""), IR.paramList(), IR.block()); Node varNode = IR.var(NodeUtil.newQName(compiler, interfaceName), fnNode); JSDocInfoBuilder info = new JSDocInfoBuilder(true); info.recordInterface(); varNode.setJSDocInfo(info.build()); block.addChildToBack(varNode); appendPropertiesToBlock(cls, block, interfaceName + ".prototype."); for (MemberDefinition prop : readOnlyProps) { // Add all _set* functions to avoid renaming. String propName = prop.name.getString(); String setterName = "_set" + propName.substring(0, 1).toUpperCase() + propName.substring(1); Node setterExprNode = IR.exprResult(NodeUtil.newQName(compiler, interfaceName + ".prototype." + setterName)); JSDocInfoBuilder setterInfo = new JSDocInfoBuilder(true); JSTypeExpression propType = getTypeFromProperty(prop); setterInfo.recordParameter(propName, propType); setterExprNode.getFirstChild().setJSDocInfo(setterInfo.build()); block.addChildToBack(setterExprNode); } Node parent = polymerElementExterns.getParent(); Node stmts = block.removeChildren(); parent.addChildrenToBack(stmts); compiler.reportCodeChange(); }
private boolean validateUsage(Node n) { // There are only three valid usage patterns for of goog.defineClass // var ClassName = googDefineClass // namespace.ClassName = googDefineClass // and within an objectlit, used by the goog.defineClass. Node parent = n.getParent(); switch (parent.getType()) { case Token.NAME: return true; case Token.ASSIGN: return n == parent.getLastChild() && parent.getParent().isExprResult(); case Token.STRING_KEY: return isContainedInGoogDefineClass(parent); } return false; }
/** * Removes all CALL nodes in the given TweakInfos, replacing calls to getter functions with the * tweak's default value. */ private boolean stripAllCalls(Map<String, TweakInfo> tweakInfos) { for (TweakInfo tweakInfo : tweakInfos.values()) { boolean isRegistered = tweakInfo.isRegistered(); for (TweakFunctionCall functionCall : tweakInfo.functionCalls) { Node callNode = functionCall.callNode; Node parent = callNode.getParent(); if (functionCall.tweakFunc.isGetterFunction()) { Node newValue; if (isRegistered) { newValue = tweakInfo.getDefaultValueNode().cloneNode(); } else { // When we find a getter of an unregistered tweak, there has // already been a warning about it, so now just use a default // value when stripping. TweakFunction registerFunction = functionCall.tweakFunc.registerFunction; newValue = registerFunction.createDefaultValueNode(); } parent.replaceChild(callNode, newValue); } else { Node voidZeroNode = IR.voidNode(IR.number(0).srcref(callNode)).srcref(callNode); parent.replaceChild(callNode, voidZeroNode); } } } return !tweakInfos.isEmpty(); }
private static boolean isLhsOfEnhancedForExpression(Node n) { Node parent = n.getParent(); if (NodeUtil.isNameDeclaration(parent)) { return isLhsOfEnhancedForExpression(parent); } return NodeUtil.isEnhancedFor(parent) && parent.getFirstChild() == n; }
/** Traverses a parse tree recursively with a scope, starting at that scope's root. */ void traverseAtScope(Scope s) { Node n = s.getRootNode(); if (n.isFunction()) { // We need to do some extra magic to make sure that the scope doesn't // get re-created when we dive into the function. if (inputId == null) { inputId = NodeUtil.getInputId(n); } sourceName = getSourceName(n); curNode = n; pushScope(s); Node args = n.getFirstChild().getNext(); Node body = args.getNext(); traverseBranch(args, n); traverseBranch(body, n); popScope(); } else if (n.isBlock()) { if (inputId == null) { inputId = NodeUtil.getInputId(n); } sourceName = getSourceName(n); curNode = n; pushScope(s); traverseBranch(n, n.getParent()); popScope(); } else { Preconditions.checkState(s.isGlobal(), "Expected global scope. Got:", s); traverseWithScope(n, s); } }
/** * Removes unreferenced arguments from a function declaration and when possible the function's * callSites. * * @param fnScope The scope inside the function */ private void removeUnreferencedFunctionArgs(Scope fnScope) { // TODO(johnlenz): Update type registry for function signature changes. Node function = fnScope.getRootNode(); Preconditions.checkState(function.getType() == Token.FUNCTION); if (NodeUtil.isGetOrSetKey(function.getParent())) { // The parameters object literal setters can not be removed. return; } Node argList = getFunctionArgList(function); boolean modifyCallers = modifyCallSites && callSiteOptimizer.canModifyCallers(function); if (!modifyCallers) { // Strip unreferenced args off the end of the function declaration. Node lastArg; while ((lastArg = argList.getLastChild()) != null) { Var var = fnScope.getVar(lastArg.getString()); if (!referenced.contains(var)) { Preconditions.checkNotNull(var == null); argList.removeChild(lastArg); compiler.reportCodeChange(); } else { break; } } } else { callSiteOptimizer.optimize(fnScope, referenced); } }
/** Add an @this annotation to all functions in the objLit. */ private void addTypesToFunctions(Node objLit, String thisType) { Preconditions.checkState(objLit.isObjectLit()); for (Node keyNode : objLit.children()) { Node value = keyNode.getLastChild(); if (value != null && value.isFunction()) { JSDocInfoBuilder fnDoc = JSDocInfoBuilder.maybeCopyFrom(keyNode.getJSDocInfo()); fnDoc.recordThisType( new JSTypeExpression(new Node(Token.BANG, IR.string(thisType)), VIRTUAL_FILE)); keyNode.setJSDocInfo(fnDoc.build()); } } // Add @this and @return to default property values. for (MemberDefinition property : extractProperties(objLit)) { if (!property.value.isObjectLit()) { continue; } if (hasShorthandAssignment(property.value)) { compiler.report(JSError.make(property.value, POLYMER_SHORTHAND_NOT_SUPPORTED)); return; } Node defaultValue = NodeUtil.getFirstPropMatchingKey(property.value, "value"); if (defaultValue == null || !defaultValue.isFunction()) { continue; } Node defaultValueKey = defaultValue.getParent(); JSDocInfoBuilder fnDoc = JSDocInfoBuilder.maybeCopyFrom(defaultValueKey.getJSDocInfo()); fnDoc.recordThisType( new JSTypeExpression(new Node(Token.BANG, IR.string(thisType)), VIRTUAL_FILE)); fnDoc.recordReturnType(getTypeFromProperty(property)); defaultValueKey.setJSDocInfo(fnDoc.build()); } }