/** * @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)); } } }
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; } }
@Override public void visit(NodeTraversal t, Node n, Node parent) { if (!n.isAssign() || n.getFirstChild() == className) { return; } if (className.matchesQualifiedName(n.getFirstChild())) { compiler.report(JSError.make(n, CLASS_REASSIGNMENT)); } }
/** * Attempt to inline an global alias of a global name. This requires that the name is well * defined: assigned unconditionally, assigned exactly once. It is assumed that, the name for * which it is an alias must already meet these same requirements. * * @param alias The alias to inline * @return Whether the alias was inlined. */ private boolean inlineGlobalAliasIfPossible(Name name, Ref alias, GlobalNamespace namespace) { // Ensure that the alias is assigned to global name at that the // declaration. Node aliasParent = alias.node.getParent(); if (aliasParent.isAssign() && NodeUtil.isExecutedExactlyOnce(aliasParent) // We special-case for constructors here, to inline constructor aliases // more aggressively in global scope. // We do this because constructor properties are always collapsed, // so we want to inline the aliases also to avoid breakages. || aliasParent.isName() && name.isConstructor()) { Node lvalue = aliasParent.isName() ? aliasParent : aliasParent.getFirstChild(); if (!lvalue.isQualifiedName()) { return false; } name = namespace.getSlot(lvalue.getQualifiedName()); if (name != null && name.isInlinableGlobalAlias()) { Set<AstChange> newNodes = new LinkedHashSet<>(); List<Ref> refs = new ArrayList<>(name.getRefs()); for (Ref ref : refs) { switch (ref.type) { case SET_FROM_GLOBAL: continue; case DIRECT_GET: case ALIASING_GET: Node newNode = alias.node.cloneTree(); Node node = ref.node; node.getParent().replaceChild(node, newNode); newNodes.add(new AstChange(ref.module, ref.scope, newNode)); name.removeRef(ref); break; default: throw new IllegalStateException(); } } rewriteAliasProps(name, alias.node, 0, newNodes); // just set the original alias to null. aliasParent.replaceChild(alias.node, IR.nullNode()); compiler.reportCodeChange(); // Inlining the variable may have introduced new references // to descendants of {@code name}. So those need to be collected now. namespace.scanNewNodes(newNodes); return true; } } return false; }
private void maybeReplaceJqueryPrototypeAlias(Node n) { // Check to see if this is the assignment of the original alias. // If so, leave it intact. if (NodeUtil.isLValue(n)) { Node maybeAssign = n.getParent(); while (!NodeUtil.isStatement(maybeAssign) && !maybeAssign.isAssign()) { maybeAssign = maybeAssign.getParent(); } if (maybeAssign.isAssign()) { maybeAssign = maybeAssign.getParent(); if (maybeAssign.isBlock() || maybeAssign.isScript() || NodeUtil.isStatement(maybeAssign)) { return; } } } Node fn = n.getLastChild(); if (fn != null) { n.replaceChild(fn, IR.string("prototype").srcref(fn)); compiler.reportCodeChange(); } }
private 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); } } }
@Override public CheckLevel level(JSError error) { Node node = error.node; if (node != null) { boolean visitedFunction = false; for (Node current = node; current != null; current = current.getParent()) { Token type = current.getToken(); JSDocInfo info = null; if (type == Token.FUNCTION) { info = NodeUtil.getBestJSDocInfo(current); visitedFunction = true; } else if (type == Token.SCRIPT) { info = current.getJSDocInfo(); } else if (current.isVar() || current.isAssign()) { // There's one edge case we're worried about: // if the warning points to an assignment to a function, we // want the suppressions on that function to apply. // It's OK if we double-count some cases. Node rhs = NodeUtil.getRValueOfLValue(current.getFirstChild()); if (rhs != null) { if (rhs.isCast()) { rhs = rhs.getFirstChild(); } if (rhs.isFunction() && !visitedFunction) { info = NodeUtil.getBestJSDocInfo(current); } } } if (info != null) { for (String suppressor : info.getSuppressions()) { WarningsGuard guard = suppressors.get(suppressor); // Some @suppress tags are for other tools, and // may not have a warnings guard. if (guard != null) { CheckLevel newLevel = guard.level(error); if (newLevel != null) { return newLevel; } } } } } } return null; }
@Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isFunction()) { checkFunctionUse(t, n); } else if (n.isAssign()) { checkAssignment(t, n); } else if (n.isDelProp()) { checkDelete(t, n); } else if (n.isObjectLit()) { checkObjectLiteralOrClass(t, n); } else if (n.isClass()) { checkObjectLiteralOrClass(t, n.getLastChild()); } else if (n.isWith()) { checkWith(t, n); } }
/** * If this returns true, check for @extends and @implements annotations on this node. Otherwise, * it's probably an alias for an existing class, so skip those annotations. * * @return Whether the given node declares a function. True for the following forms: * <li> * <pre>function foo() {}</pre> * <li> * <pre>var foo = function() {};</pre> * <li> * <pre>foo.bar = function() {};</pre> */ private boolean declaresFunction(Node n) { if (n.isFunction()) { return true; } if (n.isAssign() && n.getLastChild().isFunction()) { return true; } if (NodeUtil.isNameDeclaration(n) && n.getFirstChild().hasChildren() && n.getFirstFirstChild().isFunction()) { return true; } return false; }
/** * Returns whether the node is the right hand side of an assignment or initialization of a * variable named *_ID of *_ID_. */ private boolean insideAssignmentToIdConstant(Node n) { Node parent = n.getParent(); if (parent.isAssign()) { String qname = parent.getFirstChild().getQualifiedName(); return qname != null && isIdName(qname); } else if (parent.isName()) { Node grandParent = parent.getParent(); if (grandParent != null && NodeUtil.isNameDeclaration(grandParent)) { String name = parent.getString(); return isIdName(name); } else { return false; } } else { return false; } }
/** Find functions that can be inlined. */ private void checkNameUsage(Node n, Node parent) { Preconditions.checkState(n.isName()); if (isCandidateUsage(n)) { return; } // Other refs to a function name remove its candidacy for inlining String name = n.getString(); FunctionState fs = fns.get(name); if (fs == null) { return; } // Unlike normal call/new parameters, references passed to // JSCompiler_ObjectPropertyString are not aliases of a value, but // a reference to the name itself, as such the value of the name is // unknown and can not be inlined. if (parent.isNew()) { Node target = parent.getFirstChild(); if (target.isName() && target .getString() .equals(ObjectPropertyStringPreprocess.EXTERN_OBJECT_PROPERTY_STRING)) { // This method is going to be replaced so don't inline it anywhere. fs.setInline(false); } } // If the name is being assigned to it can not be inlined. if (parent.isAssign() && parent.getFirstChild() == n) { // e.g. bar = something; <== we can't inline "bar" // so mark the function as uninlinable. // TODO(johnlenz): Should we just remove it from fns here? fs.setInline(false); } else { // e.g. var fn = bar; <== we can't inline "bar" // As this reference can't be inlined mark the function as // unremovable. fs.setRemove(false); } }
@Override public void visit(NodeTraversal t, Node n, Node parent) { if (isBehavior(n)) { if (!n.isVar() && !n.isAssign()) { compiler.report(JSError.make(n, POLYMER_UNQUALIFIED_BEHAVIOR)); return; } // Add @nocollapse. JSDocInfoBuilder newDocs = JSDocInfoBuilder.maybeCopyFrom(n.getJSDocInfo()); newDocs.recordNoCollapse(); n.setJSDocInfo(newDocs.build()); Node behaviorValue = n.getChildAtIndex(1); if (n.isVar()) { behaviorValue = n.getFirstChild().getFirstChild(); } suppressBehavior(behaviorValue); } }
private void visitCallNode(NodeTraversal t, Node call, Node parent) { String required = codingConvention.extractClassNameIfRequire(call, parent); if (required != null) { visitRequire(required, call); return; } String provided = codingConvention.extractClassNameIfProvide(call, parent); if (provided != null) { providedNames.add(provided); return; } if (codingConvention.isClassFactoryCall(call)) { if (parent.isName()) { providedNames.add(parent.getString()); } else if (parent.isAssign()) { providedNames.add(parent.getFirstChild().getQualifiedName()); } } Node callee = call.getFirstChild(); if (callee.isName()) { weakUsages.put(callee.getString(), callee); } else if (callee.isQualifiedName()) { Node root = NodeUtil.getRootOfQualifiedName(callee); if (root.isName()) { Var var = t.getScope().getVar(root.getString()); if (var == null || (!var.isExtern() && !var.isLocal())) { String name = getOutermostClassName(callee.getQualifiedName()); if (name == null) { name = callee.getQualifiedName(); } usages.put(name, call); } } } }
private void visitScriptNode(NodeTraversal t) { if (mode == Mode.SINGLE_FILE && requires.isEmpty()) { // Likely a file that isn't using Closure at all. return; } Set<String> namespaces = new HashSet<>(); // For every usage, check that there is a goog.require, and warn if not. for (Map.Entry<String, Node> entry : usages.entrySet()) { String namespace = entry.getKey(); if (namespace.endsWith(".call") || namespace.endsWith(".apply")) { namespace = namespace.substring(0, namespace.lastIndexOf('.')); } if (namespace.startsWith("goog.global.")) { continue; } Node node = entry.getValue(); JSDocInfo info = NodeUtil.getBestJSDocInfo(NodeUtil.getEnclosingStatement(node)); if (info != null && info.getSuppressions().contains("missingRequire")) { continue; } String outermostClassName = getOutermostClassName(namespace); // The parent namespace is also checked as part of the requires so that classes // used by goog.module are still checked properly. This may cause missing requires // to be missed but in practice that should happen rarely. String nonNullClassName = outermostClassName != null ? outermostClassName : namespace; String parentNamespace = null; int separatorIndex = nonNullClassName.lastIndexOf('.'); if (separatorIndex > 0) { parentNamespace = nonNullClassName.substring(0, separatorIndex); } boolean notProvidedByConstructors = !providedNames.contains(namespace) && !providedNames.contains(outermostClassName) && !providedNames.contains(parentNamespace); boolean notProvidedByRequires = !requires.containsKey(namespace) && !requires.containsKey(outermostClassName) && !requires.containsKey(parentNamespace); if (notProvidedByConstructors && notProvidedByRequires && !namespaces.contains(namespace) && !"goog".equals(parentNamespace)) { // TODO(mknichel): If the symbol is not explicitly provided, find the next best // symbol from the provides in the same file. String rootName = Splitter.on('.').split(namespace).iterator().next(); if (mode != Mode.SINGLE_FILE || closurizedNamespaces.contains(rootName)) { if (node.isCall()) { compiler.report(t.makeError(node, MISSING_REQUIRE_CALL_WARNING, namespace)); } else { compiler.report(t.makeError(node, MISSING_REQUIRE_WARNING, namespace)); } namespaces.add(namespace); } } } // For every goog.require, check that there is a usage (in either usages or weakUsages) // and warn if there is not. for (Map.Entry<String, Node> entry : requires.entrySet()) { String require = entry.getKey(); Node call = entry.getValue(); Node parent = call.getParent(); if (parent.isAssign()) { // var baz = goog.require('foo.bar.baz'); // Assume that the var 'baz' is used somewhere, and don't warn. continue; } if (!usages.containsKey(require) && !weakUsages.containsKey(require)) { reportExtraRequireWarning(call, require); } } }
/** * 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(); }
@Override public void visit(NodeTraversal t, Node n, Node parent) { JSDocInfo docInfo = n.getJSDocInfo(); if (docInfo != null && docInfo.isExport()) { if (parent.isAssign() && (n.isFunction() || n.isClass())) { JSDocInfo parentInfo = parent.getJSDocInfo(); if (parentInfo != null && parentInfo.isExport()) { // ScopedAliases produces export annotations on both the function/class // node and assign node, we only want to visit the assign node. return; } } String export = null; GenerateNodeContext context = null; switch (n.getType()) { case Token.FUNCTION: case Token.CLASS: if (parent.isScript()) { export = NodeUtil.getName(n); context = new GenerateNodeContext(n, Mode.EXPORT); } break; case Token.MEMBER_FUNCTION_DEF: export = n.getString(); context = new GenerateNodeContext(n, Mode.EXPORT); break; case Token.ASSIGN: Node grandparent = parent.getParent(); if (parent.isExprResult() && !n.getLastChild().isAssign()) { if (grandparent != null && grandparent.isScript() && n.getFirstChild().isQualifiedName()) { export = n.getFirstChild().getQualifiedName(); context = new GenerateNodeContext(n, Mode.EXPORT); } else if (allowLocalExports && n.getFirstChild().isGetProp()) { Node target = n.getFirstChild(); export = target.getLastChild().getString(); context = new GenerateNodeContext(n, Mode.EXTERN); } } break; case Token.VAR: case Token.LET: case Token.CONST: if (parent.isScript()) { if (n.getFirstChild().hasChildren() && !n.getFirstChild().getFirstChild().isAssign()) { export = n.getFirstChild().getString(); context = new GenerateNodeContext(n, Mode.EXPORT); } } break; case Token.GETPROP: if (allowLocalExports && parent.isExprResult()) { export = n.getLastChild().getString(); context = new GenerateNodeContext(n, Mode.EXTERN); } break; case Token.STRING_KEY: case Token.GETTER_DEF: case Token.SETTER_DEF: if (allowLocalExports) { export = n.getString(); context = new GenerateNodeContext(n, Mode.EXTERN); } break; } if (export != null) { exports.put(export, context); } else { // Don't produce extra warnings for functions values of object literals if (!n.isFunction() || !NodeUtil.isObjectLitKey(parent)) { if (allowLocalExports) { compiler.report(t.makeError(n, EXPORT_ANNOTATION_NOT_ALLOWED)); } else { compiler.report(t.makeError(n, NON_GLOBAL_ERROR)); } } } } }
/** * 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(); }
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 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 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; }
private void rewritePolymerClass(Node exprRoot, final ClassDefinition cls, NodeTraversal t) { // Add {@code @lends} to the object literal. Node call = exprRoot.getFirstChild(); if (call.isAssign()) { call = call.getChildAtIndex(1); } else if (call.isName()) { call = call.getFirstChild(); } Node objLit = cls.descriptor; if (hasShorthandAssignment(objLit)) { compiler.report(JSError.make(objLit, POLYMER_SHORTHAND_NOT_SUPPORTED)); return; } JSDocInfoBuilder objLitDoc = new JSDocInfoBuilder(true); objLitDoc.recordLends(cls.target.getQualifiedName() + ".prototype"); objLit.setJSDocInfo(objLitDoc.build()); this.addTypesToFunctions(objLit, cls.target.getQualifiedName()); this.switchDollarSignPropsToBrackets(objLit); this.quoteListenerAndHostAttributeKeys(objLit); // For simplicity add everything into a block, before adding it to the AST. Node block = IR.block(); if (cls.nativeBaseElement != null) { this.appendPolymerElementExterns(cls); } JSDocInfoBuilder constructorDoc = this.getConstructorDoc(cls); // Remove the original constructor JS docs from the objlit. Node ctorKey = cls.constructor.value.getParent(); if (ctorKey != null) { ctorKey.removeProp(Node.JSDOC_INFO_PROP); } if (cls.target.isGetProp()) { // foo.bar = Polymer({...}); Node assign = IR.assign(cls.target.cloneTree(), cls.constructor.value.cloneTree()); assign.setJSDocInfo(constructorDoc.build()); Node exprResult = IR.exprResult(assign); block.addChildToBack(exprResult); } else { // var foo = Polymer({...}); OR Polymer({...}); Node var = IR.var(cls.target.cloneTree(), cls.constructor.value.cloneTree()); var.setJSDocInfo(constructorDoc.build()); block.addChildToBack(var); } appendPropertiesToBlock(cls, block, cls.target.getQualifiedName() + ".prototype."); appendBehaviorMembersToBlock(cls, block); List<MemberDefinition> readOnlyProps = parseReadOnlyProperties(cls, block); addInterfaceExterns(cls, readOnlyProps); removePropertyDocs(objLit); block.useSourceInfoIfMissingFromForTree(exprRoot); Node stmts = block.removeChildren(); Node parent = exprRoot.getParent(); // If the call to Polymer() is not in the global scope and the assignment target is not // namespaced (which likely means it's exported to the global scope), put the type declaration // into the global scope at the start of the current script. // // This avoids unknown type warnings which are a result of the compiler's poor understanding of // types declared inside IIFEs or any non-global scope. We should revisit this decision after // moving to the new type inference system which should be able to infer these types better. if (!t.getScope().isGlobal() && !cls.target.isGetProp()) { Node scriptNode = NodeUtil.getEnclosingScript(exprRoot); scriptNode.addChildrenToFront(stmts); } else { Node beforeRoot = parent.getChildBefore(exprRoot); if (beforeRoot == null) { parent.addChildrenToFront(stmts); } else { parent.addChildrenAfter(stmts, beforeRoot); } } if (exprRoot.isVar()) { Node assignExpr = varToAssign(exprRoot); parent.replaceChild(exprRoot, assignExpr); } compiler.reportCodeChange(); }
boolean isSimpleAssignmentToName() { Node parent = getParent(); return parent.isAssign() && parent.getFirstChild() == nameNode; }
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(); }