fix(extraction): a Go method carries the exportedness of its name

Every Go method was indexed as unexported, whatever the case of its
name, in both engines: extractMethod never set isExported, and the
kernel's go.rs mirrored the omission. Plain functions carried the flag,
so dead-code results listed exported methods like Close as dead.

A method that is a top-level declaration (methodsAreTopLevel, only Go)
now gets the extractor's isExported, as a function does, and go.rs sets
is_exported from the same name-case rule in extract_method.

Added on top of the contributor's change: findGoPackageMember, which
resolves pkg.Name through a project import, matched any exported node
of that name in the package's directory, and methods stayed out only
because none was exported. With the flag set, a package-level call
like harbor's log.Errorf(...) went to the method Logger::Errorf
declared beside the function, and prometheus's embedded
storage.Appender resolved to a method named Appender, which dropped
its implements edges. A method is no member of its package, so it is
now skipped there. Two comments in name-matcher.ts that said the flag
is unset on every Go method now say why the name's case is still read.

Validation: the contributor's test fails on main and passes here, and
the new package-call test fails with the PR as first written and
passes here and on main, in both engines. A/B against main on harbor
296f304, prometheus a2fda0e, gin 0f09c3a and cobra adbc881: nodes
differ only in is_exported 0 -> 1 on uppercase Go methods (5,704,
3,880, 391 and 141). Edges are identical on prometheus and cobra; 22
retargets remain, all name-only guesses between an interface's method
declaration and the struct method implementing it, which now win: gin's
engine().GET and its siblings reach RouterGroup's methods, and harbor's
jobservice routes reach DefaultHandler's. The PR as first written had
retargeted 1,384 harbor calls and removed 60 prometheus implements and
dispatch sites. The dead-code report lists 3 Go symbols on harbor
instead of 167, and 0 on prometheus instead of 11. Kernel parity: 2,493
of 2,493 Go files byte-identical. The controls express and flask are
byte-identical. Index CPU on harbor, median of 5 interleaved: 40.8 s on
main, 39.8 s here. EXTRACTION_VERSION stays: main already moved it to
28 after 1.6.2, so 1.6.2 indexes get the re-index hint anyway.

Original PR #1734 by @danusha2345.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
danusha2345
2026-10-23 19:38:20 +00:00
committed by Colby Mchenry
co-authored by Claude Opus 5.5
parent 8f8f04b7fc
commit 90c5dc6fc9
6 changed files with 75 additions and 5 deletions
+10
View File
@@ -2790,10 +2790,20 @@ export class TreeSitterExtractor {
// on every ordinary method (breaks kernel↔wasm parity JSON equality).
const isAbstract = this.extractor.isAbstract?.(node) ? true : undefined;
const returnType = this.extractor.getReturnType?.(node, this.source);
// A method that is a top-level declaration (Go: `func (r *T) Name()`) has
// the same exportedness rule as a function — the name's case — and a
// consumer asking "can another package name this?" needs it on methods
// too. Class members keep the flag unset: their reachability is the
// class's, and the languages whose isExported walks the parent chain
// (JS/TS) would otherwise re-mark every member of an exported class.
const isExported = this.extractor.methodsAreTopLevel
? this.extractor.isExported?.(node, this.source)
: undefined;
const extraProps: Partial<Node> = {
docstring,
signature,
visibility,
isExported,
isAsync,
isStatic,
isAbstract,
+3 -1
View File
@@ -3935,7 +3935,9 @@ function findGoPackageMember(
if (pkgDir == null) return null;
for (const node of context.getNodesByName(memberName)) {
if (node.language !== 'go') continue;
if (!node.isExported) continue;
// A method is no member of its package: `pkg.Infof` is the package's
// function, never a method `(*Logger).Infof` declared beside it.
if (!node.isExported || node.kind === 'method') continue;
if (path.posix.dirname(node.filePath.replace(/\\/g, '/')) === pkgDir) {
return {
original: ref,
+3 -3
View File
@@ -3370,7 +3370,7 @@ function isSfcPrivate(n: Node, context: ResolutionContext): boolean {
* Whether Go lets `ref`'s file name the Go declaration `candidate`: an
* exported name from anywhere, an unexported one only inside its own package,
* which is a directory. By the name's first letter, not the extractor's flag:
* the flag is unset for every Go method, exported or not.
* an index built before Go methods carried the flag has it unset on every one.
*
* A package's external tests (`package a_test` beside `package a`) share its
* directory but are a package of their own: a name one of them declares at
@@ -3412,8 +3412,8 @@ function isGoNameVisible(candidate: Node, ref: UnresolvedRef, context: Resolutio
* files its `part` directives name — so another file of the library names
* it, and a file of another library never does (#2466).
* - **Go**: an unexported (lowercase) identifier is package-local, and a
* package is a directory. Judged by the name's case: the extractor's
* `isExported` is unset for every Go method.
* package is a directory. Judged by the name's case, which an older index's
* Go methods don't carry in `isExported`.
* - **Rust**: a non-`pub` item is visible to its module and that module's
* descendants, never to a sibling module or another crate — `.count()` on
* an iterator resolved onto a `fn count` in a different crate. A method in