fix(extraction): C# produces references edges for type annotations (#381) (#470)

Indexing any C# project produced zero `references` edges, so
`codegraph_callers SomeDto` returned no hits even when the DTO was used
as a param/return type across the codebase, and `codegraph_callees` on
a service class only saw its `using` imports — the headline structural
query silently degraded to text-search on half of every typical backend
stack.

Two root causes:

1. `csharp.ts` was missing `returnField` (default `'return_type'` doesn't
   exist on C# AST; the field is `'type'`) AND had
   `paramsField:'parameter_list'` (the node TYPE, not the field NAME
   `'parameters'`) — so parameter type extraction silently no-op'd.
2. `extractTypeRefsFromSubtree` only emitted refs for `type_identifier`
   leaves. C# tree-sitter doesn't produce `type_identifier` — it uses
   `identifier`, `predefined_type`, `qualified_name`, `generic_name`,
   `array_type`, `nullable_type`, `tuple_type`, etc.

Fix:

- `csharp.ts`: `paramsField:'parameters'`, `returnField:'type'`.
- Route C# through a dedicated `extractCsharpTypeRefs` +
  `walkCsharpTypePosition`. Descends ONLY into known type fields
  (`parameter.type`, `method.type`, `property.type`,
  `variable_declaration.type`, `tuple_element.type`), so parameter
  NAMES like `request` in `Build(UserDto request)` never leak as type
  refs.
- Hook `extractField` and `extractProperty` to call
  `extractTypeAnnotations` so property/field type refs land in the graph.

Validation on dotnet/eShop (527 .cs files):
  C# `references` edges: 35 -> 925 (+26x)
  No regression in calls/imports/instantiates/extends/implements.

Closes #381.
This commit is contained in:
Colby Mchenry
2026-05-26 17:23:17 -05:00
committed by GitHub
parent f1b79eeae1
commit 046e03a05f
4 changed files with 187 additions and 2 deletions
+53
View File
@@ -742,6 +742,59 @@ func UseAliased() {
expect(target?.filePath.replace(/\\/g, '/')).toBe('pkgb/lib.go');
});
it('C# extracts references from method/property/field types (#381)', async () => {
// Pre-#381, every C# project produced ZERO `references` edges:
// csharp.ts was missing returnField, and the type-leaf walker
// only recognized TS/Java's `type_identifier` nodes — C# uses
// `identifier`/`predefined_type`/`qualified_name`/`generic_name`.
const srcDir = path.join(tempDir, 'src');
fs.mkdirSync(srcDir, { recursive: true });
fs.writeFileSync(
path.join(srcDir, 'Dtos.cs'),
`namespace MyApp;
public class SessionInfoDto { public string Id { get; set; } = ""; }
public class UserDto { public string Name { get; set; } = ""; }
`
);
fs.writeFileSync(
path.join(srcDir, 'Service.cs'),
`using System.Threading.Tasks;
namespace MyApp;
public class DataExporter
{
public SessionInfoDto Build(UserDto user, SessionInfoDto session) { return session; }
public Task<SessionInfoDto> BuildAsync(UserDto user) { return Task.FromResult(new SessionInfoDto()); }
public SessionInfoDto Latest { get; set; } = new();
private UserDto _cached;
}
`
);
cg = await CodeGraph.init(tempDir, { index: true });
const sessionDto = cg
.getNodesByKind('class')
.find((n) => n.name === 'SessionInfoDto');
const userDto = cg
.getNodesByKind('class')
.find((n) => n.name === 'UserDto');
expect(sessionDto).toBeDefined();
expect(userDto).toBeDefined();
const sessionIncoming = cg
.getIncomingEdges(sessionDto!.id)
.filter((e) => e.kind === 'references');
const userIncoming = cg
.getIncomingEdges(userDto!.id)
.filter((e) => e.kind === 'references');
// SessionInfoDto: Build return, Build param, BuildAsync return (inside Task<>), Latest property.
// UserDto: Build param, BuildAsync param, _cached field.
expect(sessionIncoming.length).toBeGreaterThanOrEqual(4);
expect(userIncoming.length).toBeGreaterThanOrEqual(3);
});
it('Go: leaves stdlib calls (fmt.Println, etc.) external', async () => {
fs.writeFileSync(
path.join(tempDir, 'go.mod'),