Fix type reference detection in bridge generator

This CL fixes one particular case when the AST traversal wasn't thorough
enough; if you extend an interface, we need to also check the interface
that's being inherited from for any types that need to be pulled into
the bridge.

For example:

```
interface A {...}
interface B {
  a: A
}
interface C extends B {
  ...
}
```

If the bridge generator decides that interface `C` should be in the
bridge, we need to walk through its parents (in this case, `B`) to check
for any other nested type references that should be included in the
bridge. In this case, because `B` references the `A` interface, we need
to add the `A` interface to the bridge.

Change-Id: Id211e2e02c12fed9ebb7c6456640bf87c3be70ac
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2339560
Auto-Submit: Jack Franklin <jacktfranklin@chromium.org>
Commit-Queue: Paul Lewis <aerotwist@chromium.org>
Reviewed-by: Paul Lewis <aerotwist@chromium.org>
diff --git a/scripts/component_bridges/walk_tree.ts b/scripts/component_bridges/walk_tree.ts
index 263a441..95585cd 100644
--- a/scripts/component_bridges/walk_tree.ts
+++ b/scripts/component_bridges/walk_tree.ts
@@ -312,6 +312,31 @@
         // If it wasn't a type alias, it's an interface, so walk through the interface and add any found nested types.
         const nestedInterfaces = findNestedInterfacesInInterface(interfaceOrTypeAliasDeclaration);
         nestedInterfaces.forEach(nestedInterface => foundNestedReferences.add(nestedInterface));
+
+        // if the interface has any extensions, we need to dive into those too
+        // e.g. interface X extends Y means we have to check Y for any additional type references
+        if (interfaceOrTypeAliasDeclaration.heritageClauses) {
+          interfaceOrTypeAliasDeclaration.heritageClauses.forEach(heritageClause => {
+            const extendNames = heritageClause.types.map(heritageClauseName => {
+              if (ts.isIdentifier(heritageClauseName.expression)) {
+                return heritageClauseName.expression.escapedText.toString();
+              }
+              throw new Error('Unexpected heritageClauseName with no identifier.');
+            });
+
+            extendNames.forEach(interfaceName => {
+              const interfaceDec = findNodeForTypeReferenceName(state, interfaceName);
+              if (!interfaceDec) {
+                throw new Error(`Could not find interface: ${interfaceName}`);
+              }
+              if (!ts.isInterfaceDeclaration(interfaceDec)) {
+                throw new Error('Found invalid TypeScript: an interface cannot extend a type.');
+              }
+              const nestedInterfaces = findNestedInterfacesInInterface(interfaceDec);
+              nestedInterfaces.forEach(nestedInterface => foundNestedReferences.add(nestedInterface));
+            });
+          });
+        }
       }
       return foundNestedReferences;
     };
diff --git a/test/unittests/scripts/component_bridges/generate_closure_test.ts b/test/unittests/scripts/component_bridges/generate_closure_test.ts
index ce43567..18a48ef 100644
--- a/test/unittests/scripts/component_bridges/generate_closure_test.ts
+++ b/test/unittests/scripts/component_bridges/generate_closure_test.ts
@@ -1106,5 +1106,38 @@
 * }}`);
       assert.include(interfaces[0].join('\n'), 'export let Person');
     });
+
+    it('will include nested types from interfaces that get extended', () => {
+      const state = parseCode(`interface NamedThing {
+        name: Name;
+      }
+
+      interface Name {
+        first: string;
+        last: string;
+      }
+
+      interface Person extends NamedThing {
+        favouriteColour: string;
+      }
+
+      class Breadcrumbs extends HTMLElement {
+        public update(person: Person) {}
+      }`);
+
+      const interfaces = generateInterfaces(state);
+
+      assert.strictEqual(interfaces.length, 2);
+      assert.include(interfaces[0].join('\n'), `* @typedef {{
+* name:Name,
+* favouriteColour:string,
+* }}`);
+      assert.include(interfaces[0].join('\n'), 'export let Person');
+      assert.include(interfaces[1].join('\n'), `* @typedef {{
+* first:string,
+* last:string,
+* }}`);
+      assert.include(interfaces[1].join('\n'), 'export let Name');
+    });
   });
 });