fix(extraction): C# record-struct kind fidelity + bodiless positional records (#831 follow-up) (#838)
The shipped grammar parses every record form as record_declaration (no record_struct_declaration node), so 'record struct' mis-kinded as class. classifyClassNode now distinguishes the value-type form by its struct keyword child, and extractStruct accepts bodiless positional records (the no-body gate is for C/C++ forward declarations) instead of crashing mid-file on them. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
ed117ef90b
commit
2c7bbd5387
@@ -56,13 +56,24 @@ export const csharpExtractor: LanguageExtractor = {
|
||||
preParse: blankCsharpPreprocessorDirectives,
|
||||
functionTypes: [],
|
||||
// Records are first-class type declarations in modern C# (DTOs, value objects,
|
||||
// MediatR/CQRS messages). `record` / `record class` parse as record_declaration
|
||||
// (reference type → class); `record struct` as record_struct_declaration (value
|
||||
// type → struct). Without these, references to a record never resolve (#237).
|
||||
// MediatR/CQRS messages). Without these, references to a record never resolve
|
||||
// (#237). The shipped grammar parses EVERY record form as record_declaration —
|
||||
// `record struct` / `readonly record struct` included (it has no
|
||||
// record_struct_declaration node; that structTypes entry is forward-compat
|
||||
// only) — so classifyClassNode tells the value-type form apart by its
|
||||
// `struct` keyword child. (#831 follow-up)
|
||||
classTypes: ['class_declaration', 'record_declaration'],
|
||||
methodTypes: ['method_declaration', 'constructor_declaration'],
|
||||
interfaceTypes: ['interface_declaration'],
|
||||
structTypes: ['struct_declaration', 'record_struct_declaration'],
|
||||
classifyClassNode: (node) => {
|
||||
if (node.type === 'record_declaration') {
|
||||
for (let i = 0; i < node.childCount; i++) {
|
||||
if (node.child(i)?.type === 'struct') return 'struct';
|
||||
}
|
||||
}
|
||||
return 'class';
|
||||
},
|
||||
enumTypes: ['enum_declaration'],
|
||||
enumMemberTypes: ['enum_member_declaration'],
|
||||
typeAliasTypes: [],
|
||||
|
||||
@@ -1238,8 +1238,10 @@ export class TreeSitterExtractor {
|
||||
if (!this.extractor) return;
|
||||
|
||||
// Skip forward declarations and type references (no body = not a definition)
|
||||
// — EXCEPT C# positional records (`record struct M(decimal Amount);`),
|
||||
// complete definitions with no body block. (#831)
|
||||
const body = getChildByField(node, this.extractor.bodyField);
|
||||
if (!body) return;
|
||||
if (!body && node.type !== 'record_declaration') return;
|
||||
|
||||
const name = extractName(node, this.source, this.extractor);
|
||||
const docstring = getPrecedingDocstring(node, this.source);
|
||||
@@ -1260,15 +1262,18 @@ export class TreeSitterExtractor {
|
||||
// `record struct M(decimal Amount)` which the grammar nests here).
|
||||
this.extractCsharpPrimaryCtorParamRefs(node, structNode.id);
|
||||
|
||||
// Push to stack for field extraction
|
||||
this.nodeStack.push(structNode.id);
|
||||
for (let i = 0; i < body.namedChildCount; i++) {
|
||||
const child = body.namedChild(i);
|
||||
if (child) {
|
||||
this.visitNode(child);
|
||||
// Push to stack for field extraction (bodiless positional records have
|
||||
// no members to visit)
|
||||
if (body) {
|
||||
this.nodeStack.push(structNode.id);
|
||||
for (let i = 0; i < body.namedChildCount; i++) {
|
||||
const child = body.namedChild(i);
|
||||
if (child) {
|
||||
this.visitNode(child);
|
||||
}
|
||||
}
|
||||
this.nodeStack.pop();
|
||||
}
|
||||
this.nodeStack.pop();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user