mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-29 14:49:36 +08:00
fix(resolution): a C#/VB.NET type position names a type, never a same-named member (#2121)
Found by the README-wide sweep: AutoMapper's most-depended-on list held a
test class's `List` property (519) and an attribute's `Type` property (259);
eShop's held MSTest's `TestContext` and MAUI's `Easing` as properties.
A `references`/`type_of`/`returns` ref whose SITE is a type position — read
from the source at its column: `Type name` (a declaration names its type
first), `List<`, `Type?` (not `?.`), `Type[]`, a generic argument, `new X`,
VB's `As` / `New` / `Of` — and every `instantiates` ref (its column is the
`new`) now has only type candidates in exact-name and fuzzy matching: a
property, method (a constructor is one), field or enum case sharing the name
is not one. With the member out of the way the real type wins: `TypeMap
typeMap` now links the `TypeMap` class instead of a `TypeMap` property beside
it. Anything else — `Builder.Services`, `MapGet("/x", GetItems)`, a route's
reference to its action — keeps every candidate.
A/B vs main (edge diffs, every change classified): AutoMapper −1,871/+777,
Newtonsoft.Json −3,612/+599, serilog −447/+187, eShop −78/+15,
CleanArchitecture −9. Losses are BCL/framework types that had been pinned on
project members (List, Type, DateTime, CancellationToken, Exception,
TimeSpan, Easing, TestContext…); gains are the project's own types
(TypeMap, MemberMap, LogEvent, LogEventProperty, TypeNameHandling,
JTokenReader, OrderStatus). An earlier cut that dropped every member
candidate also dropped method groups, `Builder.Services` reads and ASP.NET
route→action references — the position reading keeps all of those.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
7a51969fb0
commit
e453e820a8
@@ -995,6 +995,46 @@ const BARE_CALL_KEYWORDS: ReadonlySet<string> = new Set([
|
||||
'return', 'await', 'yield', 'typeof', 'void', 'new', 'else', 'case', 'throw', 'in', 'of', 'instanceof', 'go', 'defer',
|
||||
]);
|
||||
|
||||
/**
|
||||
* A C# / VB.NET reference whose site is a TYPE position: `Type sourceType`,
|
||||
* `List<int>`, `new TypeMap()`, `Exception? e`, `Dictionary<string, Type>`,
|
||||
* VB's `As Type` / `New List(Of T)`. Read from the source at the reference's
|
||||
* column; anything else — a member read (`Builder.Services`), a method group
|
||||
* (`MapGet("/x", GetItems)`), a route's handler — keeps every candidate.
|
||||
*/
|
||||
function isDotNetTypeRef(ref: UnresolvedRef, context: ResolutionContext): boolean {
|
||||
if (ref.language !== 'csharp' && ref.language !== 'vbnet') return false;
|
||||
if (ref.referenceKind !== 'references' && ref.referenceKind !== 'instantiates' &&
|
||||
ref.referenceKind !== 'type_of' && ref.referenceKind !== 'returns') return false;
|
||||
const name = ref.referenceName;
|
||||
if (!/^[A-Za-z_]\w*$/.test(name)) return false;
|
||||
// `new TypeMap()` constructs a type; the reference's column is the `new`.
|
||||
if (ref.referenceKind === 'instantiates') return true;
|
||||
const line = context.getFileLines?.(ref.filePath)?.[ref.line - 1]
|
||||
?? context.readFile(ref.filePath)?.split('\n')[ref.line - 1];
|
||||
if (line === undefined || !line.startsWith(name, ref.column)) return false;
|
||||
const before = line.slice(0, ref.column);
|
||||
const after = line.slice(ref.column + name.length);
|
||||
if (ref.language === 'vbnet') return /\b(?:As|New|Of)\s+$/i.test(before);
|
||||
if (/\bnew\s+$/.test(before)) return true;
|
||||
// `Type name` — a declaration names its type first.
|
||||
if (/^\s+@?[A-Za-z_]/.test(after)) return !/^\s+(?:is|as|and|or|not|when|in|switch|with)\b/.test(after);
|
||||
// `List<int>`, `Type?`, `Type[]`, and the arguments of a generic.
|
||||
if (/^<|^\?(?![.?\[])|^\[\s*[,\]]/.test(after)) return true;
|
||||
return /^\s*[,>]/.test(after) && /<[^<>()]*$/.test(before);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a candidate can be what a .NET type position names. A property, a
|
||||
* method (a constructor is one) or an enum case shares the type's name, not
|
||||
* its meaning: AutoMapper's `Type sourceType` bound to an attribute's `Type`
|
||||
* property and `TypeMap typeMap` to a `TypeMap` property beside the `TypeMap`
|
||||
* class. A field never names a type either.
|
||||
*/
|
||||
function canNameInTypePosition(n: Node): boolean {
|
||||
return !(n.kind === 'property' || n.kind === 'method' || n.kind === 'enum_member' || n.kind === 'field');
|
||||
}
|
||||
|
||||
/** What only exists inside a type, reachable through a receiver alone. */
|
||||
const TYPE_MEMBER_KINDS: ReadonlySet<string> = new Set(['method', 'property', 'field', 'enum_member']);
|
||||
|
||||
@@ -1203,8 +1243,10 @@ export function matchByExactName(
|
||||
// upstream's language gate on the chosen result.
|
||||
const cMacroCall = ref.referenceKind === 'calls' && (ref.language === 'c' || ref.language === 'cpp') &&
|
||||
sameName.some((n) => n.kind === 'constant' && CPP_DEFINE_SIGNATURE.test(n.signature ?? ''));
|
||||
const typeRef = isDotNetTypeRef(ref, context);
|
||||
const candidates = sameName.filter((n) =>
|
||||
!(cMacroCall && n.kind !== 'function' && n.kind !== 'method') &&
|
||||
!(typeRef && !canNameInTypePosition(n)) &&
|
||||
// Type/value references retain same-family eligibility: a native namesake
|
||||
// must not hide the actual web type. Calls still gate only the winner.
|
||||
(!valueRef || sameLanguageFamily(n.language, ref.language)) &&
|
||||
@@ -4200,7 +4242,8 @@ export function matchFuzzy(
|
||||
|
||||
// Filter to callable kinds only (function, method, class)
|
||||
const callableKinds = new Set(['function', 'method', 'class']);
|
||||
const callableCandidates = candidates.filter((n) => callableKinds.has(n.kind))
|
||||
const typeRef = isDotNetTypeRef(ref, context);
|
||||
const callableCandidates = candidates.filter((n) => callableKinds.has(n.kind) && !(typeRef && !canNameInTypePosition(n)))
|
||||
.filter((n) => (ref.referenceKind !== 'references' && ref.referenceKind !== 'function_ref') ||
|
||||
sameLanguageFamily(n.language, ref.language));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user