fix(go): a package-level var or const named in uppercase is exported (#3043)

Both extractors created a Go var/const node from its var_spec or
const_spec without exportedness: the TS Go branch passed no isExported
(the declaration-level value is read off the var_declaration, which has
no name), and the kernel's extract_variable set none. Every such node was
unexported, so findGoPackageMember, which looks up pkg.Name among the
package's exported declarations, skipped it: a call through
`var Hook = func...` from another package linked to nothing, though the
same call inside the package linked to the variable.

Both engines now mark the node exported by its own name's case, read
from the spec through goExtractor.isExported / is_exported, as
functions, methods and types already were.

Validation:
- __tests__/go-package-value-exported.test.ts, 16 tests (extraction on
  the kernel and wasm, LF and CRLF; cross-package calls on both paths):
  12 fail on the base main, 6 with only the TS half (the kernel
  checks), all pass with the fix.
- A/B, main vs fix, kernel path: gin 108 var/const flags, 0 edges;
  prometheus 625 flags, 0 edges; etcd 806 flags and 12 new calls,
  all right (e2e.GetVersionFromBinary x11 and
  model.NonDeterministicModel, both `var X = func(...)`); flask and
  express byte-identical. The fix's kernel and wasm graphs are
  byte-identical on all three Go repos, and the wasm A/B matches.
- kernel-parity --lang go: gin 99/99, prometheus 755/755, etcd
  1065/1105 with 40 deferred on main too; 0 diffs.
- etcd index time, median of 3 interleaved: 13.4 s main, 12.6 s fix.

Reimplements PR #1521 by ericquan8.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-10-30 06:01:18 +00:00
committed by GitHub
co-authored by Claude Opus 5.5
parent c12a7b6a74
commit 37052b1825
5 changed files with 227 additions and 4 deletions
+4 -2
View File
@@ -216,8 +216,10 @@ export const goExtractor: LanguageExtractor = {
},
isExported: (node, source) => {
// Go: a symbol is exported when its identifier starts with an uppercase letter.
// Look at the `name` field directly (works for function_declaration,
// method_declaration, type_spec, and var_spec / const_spec via extractor flow).
// Look at the `name` field directly: function_declaration,
// method_declaration, type_spec, and a var_spec / const_spec, whose first
// name is the one the extractor gives a node (never the declaration
// around a spec, which has no name).
const nameNode = getChildByField(node, 'name');
if (nameNode) {
const text = getNodeText(nameNode, source);
+5
View File
@@ -4149,9 +4149,14 @@ export class TreeSitterExtractor {
// A member of a `const ( … )` or `var ( … )` group has its own doc
// comment beside it in the parentheses; one without keeps the group's.
// Exported by its own name's case, read from the spec: the
// declaration around it has no name, so `var Hook = …` and
// `const Limit = 3` read as unexported and `a.Hook()` from another
// package linked to nothing.
varNode = this.createNode(node.type === 'const_declaration' ? 'constant' : 'variable', name, spec, {
docstring: this.docstringFor(spec) ?? docstring,
signature: initSignature,
isExported: this.extractor.isExported?.(spec, this.source),
});
}
// Walk the initializer so composite literals and calls in a