fix(extraction): skip bodiless C++ forward declarations (#1093) (#1095)

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:
Colby Mchenry
2026-07-01 06:23:50 -05:00
committed by GitHub
co-authored by Claude Opus 4.8
parent ad03d24fb9
commit f856f7ae49
5 changed files with 81 additions and 3 deletions
+8
View File
@@ -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;