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');
+ });
});
});