diff --git a/src/com/google/javascript/jscomp/ConvertToTypedInterface.java b/src/com/google/javascript/jscomp/ConvertToTypedInterface.java index 117bb813062..3279275e4b9 100644 --- a/src/com/google/javascript/jscomp/ConvertToTypedInterface.java +++ b/src/com/google/javascript/jscomp/ConvertToTypedInterface.java @@ -228,13 +228,7 @@ public void visit(NodeTraversal t, Node n, Node parent) { case VAR: case LET: case CONST: - while (n.hasMoreThanOneChild()) { - Node nameToSplit = n.getLastChild().detach(); - Node rhs = nameToSplit.hasChildren() ? nameToSplit.removeFirstChild() : null; - Node newDeclaration = IR.declaration(nameToSplit, rhs, n.getToken()).srcref(n); - parent.addChildAfter(newDeclaration, n); - t.reportCodeChange(); - } + splitNameDeclarationsAndRemoveDestructuring(n, t); break; case BLOCK: if (!parent.isFunction()) { @@ -247,6 +241,33 @@ public void visit(NodeTraversal t, Node n, Node parent) { break; } } + + /** + * Does two simplifications to const/let/var nodes. + * 1. Splits them so that each declaration is a separate statement. + * 2. Removes non-import destructuring statements, which we assume are not type declarations. + */ + static void splitNameDeclarationsAndRemoveDestructuring(Node n, NodeTraversal t) { + checkArgument(NodeUtil.isNameDeclaration(n)); + while (n.hasChildren()) { + Node lhsToSplit = n.getLastChild(); + if (lhsToSplit.isDestructuringLhs() && !isImportRhs(lhsToSplit.getLastChild())) { + // Remove destructuring statements, which we assume are not type declarations + NodeUtil.markFunctionsDeleted(lhsToSplit, t.getCompiler()); + NodeUtil.removeChild(n, lhsToSplit); + t.reportCodeChange(); + continue; + } + if (n.hasOneChild()) { + return; + } + // A name declaration with more than one LHS is split into separate declarations. + Node rhs = lhsToSplit.hasChildren() ? lhsToSplit.removeFirstChild() : null; + Node newDeclaration = IR.declaration(lhsToSplit.detach(), rhs, n.getToken()).srcref(n); + n.getParent().addChildAfter(newDeclaration, n); + t.reportCodeChange(); + } + } } private static class PropagateConstJsdoc extends NodeTraversal.AbstractPostOrderCallback { @@ -306,7 +327,7 @@ public void visit(NodeTraversal t, Node n, Node parent) { void recordNameDeclaration(NodeTraversal t, Node decl) { checkArgument(NodeUtil.isNameDeclaration(decl)); Node rhs = decl.getFirstChild().getLastChild(); - boolean isImport = rhs != null && isImportRhs(rhs); + boolean isImport = isImportRhs(rhs); for (Node name : NodeUtil.getLhsNodesOfDeclaration(decl)) { if (isImport) { currentFile.recordImport(name.getString()); @@ -427,7 +448,7 @@ private static class FileInfo { private final List declarations = new ArrayList<>(); void recordDeclaration(Node qnameNode, Scope scope) { - checkArgument(qnameNode.isQualifiedName()); + checkArgument(qnameNode.isQualifiedName(), qnameNode); declarations.add(PotentialDeclaration.from(qnameNode, scope)); } @@ -741,8 +762,8 @@ private static boolean isConstructor(Node functionNode) { return jsdoc != null && jsdoc.isConstructor(); } - private static boolean isImportRhs(Node rhs) { - if (!rhs.isCall()) { + private static boolean isImportRhs(@Nullable Node rhs) { + if (rhs == null || !rhs.isCall()) { return false; } Node callee = rhs.getFirstChild(); diff --git a/test/com/google/javascript/jscomp/ConvertToTypedInterfaceTest.java b/test/com/google/javascript/jscomp/ConvertToTypedInterfaceTest.java index 21705319663..a010ad9ffd1 100644 --- a/test/com/google/javascript/jscomp/ConvertToTypedInterfaceTest.java +++ b/test/com/google/javascript/jscomp/ConvertToTypedInterfaceTest.java @@ -1077,6 +1077,30 @@ public void testGoogScopeLeftoversAreRemoved() { "a.b.c.d.e.f.g.Foo = class {};")); } + public void testDestructuringDoesntCrash() { + test( + LINE_JOINER.join( + "goog.module('a.b.c');", + "", + "const Enum = goog.require('Enum');", + "const Foo = goog.require('Foo');", + "", + "const {A, B} = Enum;", + "", + "/** @type {Foo} */", + "exports.foo = use(A, B);", + ""), + LINE_JOINER.join( + "goog.module('a.b.c');", + "", + "const Enum = goog.require('Enum');", + "const Foo = goog.require('Foo');", + "", + "/** @type {Foo} */", + "exports.foo;", + "")); + } + public void testDescAnnotationCountsAsTyped() { test( LINE_JOINER.join(