fix(resolution): constrain inheritance/import reference target kinds (#1536, #1537) (#1796)

* fix(resolution): gate extends/implements to real supertypes

An inheritance reference bound to whatever local symbol shared its name.
The name-matcher scores node kind as a bonus, never a filter, and awards
no bonus at all for inheritance refs, so `use std::error::Error;` +
`impl Error for MapperError {}` resolved to the local `MapperError::Error`
VARIANT — an implementation relationship absent from the source.

Two changes, both needed. Filtering by kind alone was measured and it
only RELOCATES the false edge: with enum members excluded, the same 7
refs moved onto an unrelated local `type Error` alias, which is a legal
supertype kind and therefore harder for a consumer to reject.

1. Eligibility before ranking. `matchByExactName` restricts its candidate
   pool to kinds that can BE a supertype, so a legitimate trait outranks
   a same-named variant instead of merely losing its edge. `resolveOne`
   is wrapped by a gate that applies the same set to every other strategy
   at one seam — filtering inside the name-matcher would have missed the
   framework, import, chain and CFML paths.
2. Locality. A name imported from outside the repository has no in-repo
   referent at all, so no candidate is correct. Only oracles that cannot
   be wrong are consulted: Rust `use` paths rooted at a stdlib crate, and
   `isExternalImport` for ES modules. Generalizing the Rust side to "the
   module path doesn't resolve to a file" was tried and reverted — a
   crate re-exporting a sibling's modules (`pub use pupil_core::ports;`)
   has no directory to walk, and that version deleted 13 real trait
   implementations.

Measured on a Rust/Tauri project (2,682 nodes): the 11 false inheritance
edges are gone, all 59 real trait relationships are preserved, and node
count is unchanged. On this repository as a control, the only edge
removed is a class recorded as extending a function. Synthesized-edge
counts are identical in both.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(resolution): an import never resolves to a member of a type

`import * as path from 'node:path'` is unresolvable — the module is
external — so the name-matcher fell back to finding any node called
`path`, and a common word like path/url/join/get matches a class property
or interface method somewhere in almost any repo. Nothing in any
supported language lets an import bind to a member that only exists
inside a type; you import the type.

Same shape as the inheritance gate that precedes it: eligibility applied
to the candidate pool before ranking, plus the resolveOne gate as the
backstop for every other strategy.

On this repository as a control: 19 imports pointing at methods and 4 at
properties are gone (all of them coincidences — `Walker::join`,
`Telemetry::events`), 3 refs now find the module constant they actually
name, node count unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(resolution): classify SFC script imports as ES module specifiers

`isExternalImport` had a TS/JS branch listing typescript/tsx/javascript/jsx/
arkts, so for Svelte, Vue and Astro it fell through every branch and returned
false — "not external" — for `import { Foo } from 'some-npm-pkg'`.

An SFC imports inside its `<script>` block (Astro: the `---` frontmatter) with
ordinary ES module syntax; `extractImportMappings` already routes all three
through the same `extractJSImports`. So the classifier disagreed with the
extractor about what those imports are.

Effect on the preceding commit: its locality check asks `isExternalImport`, so
it silently did nothing for SFCs. A class in a `.svelte`/`.vue`/`.astro` file
implementing a type imported from an npm package still bound to whatever local
class shared that name — verified against this branch before the fix, all three
languages.

The language set is now one constant used by both the classifier and the
locality check, so they cannot drift apart again. Relative and aliased
specifiers are unaffected: the branch returns "not external" for `./…`,
workspace members, tsconfig alias prefixes, `@/`, `~/` and `src/` exactly as it
does for `.ts`.

No edge changes on this repository as a control.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: ctype_lab <cksgud1226@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-09-08 15:49:10 -05:00
committed by GitHub
co-authored by Claude Opus 5 ctype_lab
parent 040ba388da
commit 374b3b4209
6 changed files with 478 additions and 5 deletions
+4
View File
@@ -221,6 +221,10 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
#### Symbols, tests and the viewer
- Imports from Node built-ins or npm packages no longer connect to unrelated type members with matching names; re-index after upgrading to clear existing false dependencies. Thanks @ctype-lab. (#1537)
- Inheritance relationships no longer attach external Rust or npm supertypes to unrelated local symbols with the same name, including in Svelte, Vue and Astro components; re-index after upgrading to clear existing false relationships. Thanks @ctype-lab. (#1536)
- PHP static calls through imported class aliases now reach the correct class when services and repositories share method names, so callers and impact analysis show the right dependencies after re-indexing. (#1545)
- TypeScript/JavaScript: a call through a field of the enclosing class — `this.mailer.send()` — now resolves on the field's declared type, so a delegating wrapper that shares the method's name no longer records itself as its own callee and `callers`, `impact` and trace stop lying on that shape. A field whose type is external or a builtin stays unresolved rather than guessed. Re-index after upgrading. (#1496)
- TypeScript and JavaScript collection calls through local variables and their nested properties no longer link to unrelated project methods; re-index after upgrading. (#1566)