diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8d8ab6f79..d0a0dec2e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -21,6 +21,10 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
### Fixes
+- An inheritance relationship is no longer invented between a type and an unrelated symbol that merely shares a name with its supertype. When a class or struct implemented something defined outside your project — a trait from the Rust standard library, an interface from an npm package — the graph would attach that relationship to whatever local symbol happened to have the same name: an enum variant, a type alias, even a function. On a Rust project of moderate size this fabricated a dozen implementation relationships, which then showed up in diagrams and in answers about who implements what. A supertype that isn't in your project is now simply left unresolved, and when several same-named symbols compete, only ones that can actually be a supertype are considered — so the real trait wins instead of losing to a same-named variant. This covers Svelte, Vue and Astro components too, whose `\n
hi
\n'],
+ ['vue', 'src/Box.vue', '\n\n'],
+ ['astro', 'src/Box.astro', '---\n$IMPORT$\nexport class SfcBox implements Serializable {\n n = 1;\n}\n---\n\n'],
+ ])('drops an npm supertype in a %s single-file component', async (_lang, file, body) => {
+ // An SFC imports inside its \nhi
\n`
+ );
+ const { edges } = await load();
+ expect(has(edges, 'SfcBox', 'Serializable', 'class')).toBe(true);
+ });
+
+ it('does not resolve an import to a type member that shares its name', async () => {
+ // `import * as path from 'node:path'` is unresolvable — the module is
+ // external — so the name-matcher looked for any node called `path` and
+ // found a class property. No language lets you import a type's member.
+ write('src/types.ts', `export class Request {\n path = '';\n url = '';\n}\n`);
+ write(
+ 'src/run.ts',
+ `import * as path from 'node:path';\n\nexport function run() {\n return path.join('a', 'b');\n}\n`
+ );
+ const cg = await CodeGraph.init(dir, { silent: true });
+ await cg.indexAll();
+ const db = (cg as any).db.db;
+ const rows: { tgt: string; tgtKind: string }[] = db
+ .prepare(
+ `SELECT t.name tgt, t.kind tgtKind
+ FROM edges e JOIN nodes t ON t.id = e.target
+ WHERE e.kind = 'imports'`
+ )
+ .all();
+ cg.close?.();
+ expect(rows.filter((r) => r.tgtKind === 'property' || r.tgtKind === 'field')).toEqual([]);
+ });
+
+ it('keeps class extends class and class implements interface', async () => {
+ write(
+ 'src/base.ts',
+ `export interface Runner { run(): void }\n` +
+ `export class Base { run(): void {} }\n` +
+ `export class Child extends Base implements Runner { run(): void {} }\n`
+ );
+ const { edges } = await load();
+ expect(has(edges, 'Child', 'Base', 'class')).toBe(true);
+ expect(has(edges, 'Child', 'Runner', 'interface')).toBe(true);
+ });
+});
diff --git a/src/resolution/import-resolver.ts b/src/resolution/import-resolver.ts
index a32a97916..e65ae40f6 100644
--- a/src/resolution/import-resolver.ts
+++ b/src/resolution/import-resolver.ts
@@ -287,6 +287,21 @@ const C_CPP_STDLIB_HEADERS = new Set([
'version',
]);
+/**
+ * Languages whose imports are ES-module specifiers, extracted by
+ * `extractJSImports` and therefore classified by the same bare-specifier /
+ * alias / workspace rules. Svelte, Vue and Astro belong here: an SFC imports
+ * inside its `