A C# method called through a static factory or fluent chain — `Foo.Create().Bar()`, `JObject.Parse(s).Property(...)`, `Instant.FromUtc(...).InZone(zone)` — lost the receiver's type, so the chained method didn't resolve and the call was invisible to callers/impact/trace. Ports the #645/#608 mechanism to C# (additive, like Java #751): - Part 1: capture C# return types in the extractor, reading the `returns` field (`static Foo Create()` -> `Foo`); predefined/array/generic/nullable/namespaced types are normalized or skipped. - Part 2: encode a chained `member_access_expression` receiver (`Foo.Create(args).Bar()`) as `inner().Bar` with normalized empty parens, so factory calls that take arguments still split. Non-chained member calls keep their existing `recv.Method` text. - Part 3: resolve via the shared matchDottedCallChain (now Java/Kotlin/C#), validated by resolveMethodOnType so a wrong inference yields NO edge. Known limitation (safe): C# extension-method chains don't resolve, since the method lives on the extension class, not the receiver's type — no edge, never a wrong one. Validated: synthetic decoy + args + absent-method safety tests; full suite green; real-repo A/B on Newtonsoft.Json (945 .cs: +3, 0 lost) and nodatime (488 .cs: +73, 0 lost) — node count identical (no explosion), 0 edges lost, precision spot-checked verbatim (Instant.FromUtc().InZone(), Offset.FromHoursAndMinutes().Plus(), OffsetDateTimePattern.CreateWithInvariantCulture().WithTwoDigitYearMax()). EXTRACTION_VERSION 7 -> 8. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
3e04650850
commit
aa07dc59d4
@@ -32,6 +32,26 @@ export function blankCsharpPreprocessorDirectives(source: string): string {
|
||||
return source.replace(re, (m, indent) => indent + ' '.repeat(m.length - indent.length));
|
||||
}
|
||||
|
||||
/**
|
||||
* A C# method's declared return type, normalized to the bare class name a chained
|
||||
* `Foo.Create().Bar()` could be called on (the #645/#608 mechanism). The return
|
||||
* type lives in the `returns` field (`static Foo Create()` → `Foo`); built-in
|
||||
* `predefined_type` (void/int/string/…) and arrays yield undefined, generics are
|
||||
* unwrapped to the base type, nullable `Foo?` is stripped, and a dotted namespace
|
||||
* is reduced to the simple name. Constructors have no `returns` field → undefined.
|
||||
*/
|
||||
function extractCsharpReturnType(node: SyntaxNode, source: string): string | undefined {
|
||||
const typeNode = node.childForFieldName('returns');
|
||||
if (!typeNode) return undefined;
|
||||
if (typeNode.type === 'predefined_type' || typeNode.type === 'array_type') return undefined;
|
||||
let t = getNodeText(typeNode, source).trim();
|
||||
t = t.replace(/\?+$/, ''); // nullable `Foo?`
|
||||
t = t.replace(/<[^>]*>/g, ''); // generics `List<Foo>` → `List`
|
||||
const last = t.split('.').pop()?.trim(); // namespace `Ns.Foo` → `Foo`
|
||||
if (!last || !/^[A-Za-z_]\w*$/.test(last)) return undefined;
|
||||
return last;
|
||||
}
|
||||
|
||||
export const csharpExtractor: LanguageExtractor = {
|
||||
preParse: blankCsharpPreprocessorDirectives,
|
||||
functionTypes: [],
|
||||
@@ -67,6 +87,7 @@ export const csharpExtractor: LanguageExtractor = {
|
||||
bodyField: 'body',
|
||||
paramsField: 'parameters',
|
||||
returnField: 'type',
|
||||
getReturnType: extractCsharpReturnType,
|
||||
getVisibility: (node) => {
|
||||
for (let i = 0; i < node.childCount; i++) {
|
||||
const child = node.child(i);
|
||||
|
||||
Reference in New Issue
Block a user