A `class Foo;` forward declaration parses as a bodiless class_specifier. extractStruct (#831) and extractEnum already skip their bodiless forms, but extractClass did not — so every forward decl across dozens of headers minted a phantom bodiless `class` node that competed with, and could be picked as the blast-radius representative over, the single real definition. Add an opt-in `skipBodilessClass` extractor flag (set only on cppExtractor) and skip a bodiless class node when it's set, mirroring the struct/enum skip. The flag keeps this C/C++-scoped: languages where a bodiless class is a complete definition (Kotlin `class Empty`, Scala `case object`/`trait`) leave it unset and are unaffected. The body is now resolved once at the top of extractClass and reused for the member walk. Regression tests cover the collapse to a single definition, elaborated-type references creating no phantom, and Kotlin/Scala staying indexed. 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
ad03d24fb9
commit
f856f7ae49
@@ -245,6 +245,12 @@ export const cppExtractor: LanguageExtractor = {
|
||||
preParse: blankCppExportMacros,
|
||||
functionTypes: ['function_definition'],
|
||||
classTypes: ['class_specifier'],
|
||||
// A bodiless `class_specifier` is a forward declaration (`class Foo;`) or an
|
||||
// elaborated type reference, not a definition. Skip it so dozens of forward
|
||||
// decls across headers don't mint phantom `class` nodes that crowd out — and
|
||||
// get picked as the blast-radius representative over — the single real
|
||||
// definition, exactly as bodiless struct/enum specifiers are already skipped. (#1093)
|
||||
skipBodilessClass: true,
|
||||
methodTypes: ['function_definition'],
|
||||
interfaceTypes: [],
|
||||
structTypes: ['struct_specifier'],
|
||||
|
||||
@@ -163,6 +163,14 @@ export interface LanguageExtractor {
|
||||
extraClassNodeTypes?: string[];
|
||||
/** Whether methods can be top-level without enclosing class (Go: true) */
|
||||
methodsAreTopLevel?: boolean;
|
||||
/**
|
||||
* Skip a bodiless class node as a forward declaration / elaborated type,
|
||||
* mirroring the bodiless-struct/enum skip. Set only for languages where a
|
||||
* bodiless `class` specifier is NOT a complete definition — C/C++
|
||||
* (`class Foo;` is a forward decl). Leave unset for languages where a
|
||||
* bodiless class IS complete (Kotlin `class Empty`, Scala `case object`). (#1093)
|
||||
*/
|
||||
skipBodilessClass?: boolean;
|
||||
/** NodeKind to use for interface-like declarations (Rust: 'trait'). Default: 'interface' */
|
||||
interfaceKind?: NodeKind;
|
||||
|
||||
|
||||
@@ -1528,6 +1528,15 @@ export class TreeSitterExtractor {
|
||||
private extractClass(node: SyntaxNode, kind: NodeKind = 'class'): void {
|
||||
if (!this.extractor) return;
|
||||
|
||||
// Skip forward declarations / elaborated type references (`class Foo;`) in
|
||||
// languages that opt in — bodiless there means "not a definition", so it
|
||||
// would otherwise mint a phantom node competing with the real definition
|
||||
// (#1093). Languages where a bodiless class is complete (Kotlin, Scala)
|
||||
// leave the flag unset. Resolved once here and reused for the body walk.
|
||||
const resolvedBody = this.extractor.resolveBody?.(node, this.extractor.bodyField)
|
||||
?? getChildByField(node, this.extractor.bodyField);
|
||||
if (this.extractor.skipBodilessClass && !resolvedBody) return;
|
||||
|
||||
const name = extractName(node, this.source, this.extractor);
|
||||
const docstring = getPrecedingDocstring(node, this.source);
|
||||
const visibility = this.extractor.getVisibility?.(node);
|
||||
@@ -1551,9 +1560,7 @@ export class TreeSitterExtractor {
|
||||
|
||||
// Push to stack and visit body
|
||||
this.nodeStack.push(classNode.id);
|
||||
let body = this.extractor.resolveBody?.(node, this.extractor.bodyField)
|
||||
?? getChildByField(node, this.extractor.bodyField);
|
||||
if (!body) body = node;
|
||||
const body = resolvedBody ?? node;
|
||||
|
||||
// Visit all children for methods and properties
|
||||
for (let i = 0; i < body.namedChildCount; i++) {
|
||||
|
||||
Reference in New Issue
Block a user