mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-27 07:23:43 +08:00
fix(resolution): a lone same-file definition scores as a strong match
matchByExactName scored a winner among several same-named candidates by directory proximity alone: 0.7 when the files share two folders, else 0.4. findBestMatch already ranks the definition in the reference's own file first, so the target was right, but a shallow file's own function read as an uncertain guess (below 0.6), and because the resolver keeps the most confident strategy, the same-file definition lost to a framework's naming guess in another file (ASP.NET's PascalCase and Service rules at 0.7 and 0.85). isDecisiveSameFileMatch now scores it 0.9, as a lone candidate, when it is the only candidate in the file, is not the caller itself, and, as a member of a type, module or namespace, belongs to one the caller is in. The PR checked that last rule for methods only. We extended it to every member kind whose owner the file declares, other than a function, so a sibling enum's entry or a sibling class's field is not raised. Validation, main vs this commit, 21 repos in 11 languages (flask, click, fastapi, gin, cobra, etcd, ripgrep, jq, Slim, serilog, eShopOnWeb, nowinandroid, okhttp, http4s, playframework, spring-modulith, express, zod, bulletproof-react, excalidraw, vben): - no edge added or removed; - 9 targets moved, all in eShopOnWeb and all right: C# properties and fields named after their type that the code reads (Color Color), which ASP.NET's naming rule had linked to the class in another project; - 6,927 edges keep their target and gain confidence, 535 of them from 0.4; in 874 a framework rule's 0.7-0.85 now loses to the same target by exact name; 30 end in another file, Go type positions the type gate moves to the package's own type; - as written, the PR also raised 77 edges onto another type's member to 0.9, about 48 of them wrong (okhttp's InterceptorOverridesTest enum entries, playframework's sibling providers' `get`); they keep main's confidence. Index CPU, median of interleaved runs: okhttp 32.3 vs 32.4 s, etcd 33.4 vs 33.1 s. Tests: 3 of 7 fail on main; the PR as written fails the sibling-member case. js-class-expressions pinned the old 0.4 for `this.run()` reaching the class's own `run` in a shallow file; it now expects 0.9. Original PR #1938 by @danusha2345. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
committed by
Colby Mchenry
co-authored by
Claude Opus 5.5
parent
0b238452a3
commit
cde4cd74eb
@@ -12049,7 +12049,9 @@ export function matchByExactName(
|
||||
if (bestMatch && isCrossFileReachable(bestMatch, ref, context)) {
|
||||
// Lower confidence when the match is from a distant/unrelated module
|
||||
const proximity = computePathProximity(ref.filePath, bestMatch.filePath);
|
||||
const confidence = proximity >= 30 ? 0.7 : 0.4;
|
||||
const confidence = isDecisiveSameFileMatch(bestMatch, ref, candidates, context)
|
||||
? 0.9
|
||||
: proximity >= 30 ? 0.7 : 0.4;
|
||||
return {
|
||||
original: ref,
|
||||
targetNodeId: bestMatch.id,
|
||||
@@ -26730,6 +26732,55 @@ function computePathProximity(filePath1: string, filePath2: string): number {
|
||||
return pathProximityFromDirs(dir1, filePath2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether `findBestMatch` picked `best` because it is THE definition in the
|
||||
* call site's own file, so the edge deserves a same-file confidence rather than
|
||||
* the directory-proximity one. Proximity measures shared folders, so a
|
||||
* same-file match in a shallow path (`scripts/x.py` → its own `main`) scored
|
||||
* 0.4 like a guess across unrelated modules. Decisive only when:
|
||||
* - `best` is the ONLY same-file candidate — several (two classes' `run` in
|
||||
* one file) are separated by line distance, which is not evidence; and
|
||||
* - a member of a type, module or namespace belongs to one the caller is
|
||||
* inside — a bare call does not reach a sibling class's method just
|
||||
* because it is written in that file, nor is okhttp's `Dns { … }` in one
|
||||
* nested object the `Dns` entry of the enum beside it. A method's owner is
|
||||
* always such a scope; another kind's is judged when the file declares it
|
||||
* as anything but a function, whose nested definitions lexical
|
||||
* reachability already scoped; and
|
||||
* - `best` is not the caller itself — `super.onCreate()`,
|
||||
* `parent::__construct()` and `json.dumps` inside `dumps` all arrive here
|
||||
* without their receiver, and recursion cannot be told apart from them.
|
||||
* 0.9 matches the unique exact-match above: the same-file bonus in
|
||||
* findBestMatch outweighs any directory proximity, so a lone same-file
|
||||
* definition is as settled as a lone candidate. The resolver keeps the most
|
||||
* confident strategy, so this also outranks a sub-0.9 framework guess that
|
||||
* sent the name to another file (ASP.NET's naming rule took eShopOnWeb's
|
||||
* `ToastService.OnShow` for the `ToastService` class, not the component's
|
||||
* property of that name) — the file's own definition is the one the name
|
||||
* means.
|
||||
*/
|
||||
function isDecisiveSameFileMatch(
|
||||
best: Node,
|
||||
ref: UnresolvedRef,
|
||||
candidates: Node[],
|
||||
context: ResolutionContext
|
||||
): boolean {
|
||||
if (best.filePath !== ref.filePath || best.id === ref.fromNodeId) return false;
|
||||
if (candidates.some((c) => c !== best && c.filePath === ref.filePath)) return false;
|
||||
const sep = best.qualifiedName.lastIndexOf('::');
|
||||
if (sep <= 0) return best.kind !== 'method';
|
||||
const owner = best.qualifiedName.slice(0, sep);
|
||||
if (best.kind !== 'method') {
|
||||
const cut = owner.lastIndexOf('::');
|
||||
const ownerName = cut >= 0 ? owner.slice(cut + 2) : owner;
|
||||
const declared = (context.getNodesInFileNamed?.(best.filePath, ownerName) ?? context.getNodesInFile(best.filePath))
|
||||
.filter((n) => n.qualifiedName === owner);
|
||||
if (declared.every((n) => n.kind === 'function' || n.kind === 'method')) return true;
|
||||
}
|
||||
const from = context.getNodeById?.(ref.fromNodeId);
|
||||
return !!from && (from.qualifiedName === owner || from.qualifiedName.startsWith(`${owner}::`));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the best matching node when there are multiple candidates
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user