`pkga.FuncX(...)` cross-package calls in Go monorepos were dropping through the import resolver — `isExternalImport(go)` flagged any non-`/internal/` import as third-party because the resolver had no idea what the project's own module path was. Resolution fell back to name matching with path-proximity scoring, which on a layered codebase picks one accidental candidate per call site (~<1% recall per #388's 5,303-vs-1 figure). - `src/resolution/go-module.ts` (new) parses the `module ...` directive from project-root `go.mod`, exposed via `getGoModule()` on `ResolutionContext`. - `isExternalImport(go)` treats `<module-path>/...` imports as in-module; the existing `/internal/` escape hatch is preserved for repos without a parsed go.mod. - `resolveViaImport` gets a Go cross-package branch that strips the module prefix to a project-relative directory, then resolves the qualified member via `getNodesByName(member)` filtered to that exact directory and `isExported=true`. Sub-packages don't collide with their parents; same-name funcs in different packages don't false-merge. - Go extractor sets `isExported` from the identifier's first character (Go's universal uppercase=exported convention). The resolver depends on this to filter candidates. Validation on gRPC-Go (1,031 .go files, layered package tree): total `calls` edges: 23,803 -> 34,105 (+43%) cross-pkg `calls`: 10,880 -> 19,929 (+83%) fmt/strconv/etc. stdlib calls: stay external (no false positives) Tests cover in-module disambiguation with same-name funcs in two packages, aliased imports, and stdlib calls not being false-resolved to in-project nodes. Closes #388.
64 lines
2.7 KiB
TypeScript
64 lines
2.7 KiB
TypeScript
import { getNodeText, getChildByField } from '../tree-sitter-helpers';
|
|
import type { LanguageExtractor } from '../tree-sitter-types';
|
|
|
|
export const goExtractor: LanguageExtractor = {
|
|
functionTypes: ['function_declaration'],
|
|
classTypes: [], // Go doesn't have classes
|
|
methodTypes: ['method_declaration'],
|
|
interfaceTypes: [], // Handled via type_spec → resolveTypeAliasKind
|
|
structTypes: [], // Handled via type_spec → resolveTypeAliasKind
|
|
enumTypes: [],
|
|
typeAliasTypes: ['type_spec'], // Go type declarations
|
|
importTypes: ['import_declaration'],
|
|
callTypes: ['call_expression'],
|
|
variableTypes: ['var_declaration', 'short_var_declaration', 'const_declaration'],
|
|
methodsAreTopLevel: true,
|
|
nameField: 'name',
|
|
bodyField: 'body',
|
|
paramsField: 'parameters',
|
|
returnField: 'result',
|
|
getSignature: (node, source) => {
|
|
const params = getChildByField(node, 'parameters');
|
|
const result = getChildByField(node, 'result');
|
|
if (!params) return undefined;
|
|
let sig = getNodeText(params, source);
|
|
if (result) {
|
|
sig += ' ' + getNodeText(result, source);
|
|
}
|
|
return sig;
|
|
},
|
|
resolveTypeAliasKind: (node, _source) => {
|
|
// Go type_spec: `type Foo struct { ... }` or `type Bar interface { ... }`
|
|
// The inner type is in the 'type' field of the type_spec node
|
|
const typeChild = getChildByField(node, 'type');
|
|
if (!typeChild) return undefined;
|
|
if (typeChild.type === 'struct_type') return 'struct';
|
|
if (typeChild.type === 'interface_type') return 'interface';
|
|
return undefined;
|
|
},
|
|
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).
|
|
const nameNode = getChildByField(node, 'name');
|
|
if (nameNode) {
|
|
const text = getNodeText(nameNode, source);
|
|
const first = text.charCodeAt(0);
|
|
return first >= 65 && first <= 90; // A-Z
|
|
}
|
|
return false;
|
|
},
|
|
getReceiverType: (node, source) => {
|
|
// Go method_declaration has a "receiver" field: func (sl *scrapeLoop) run(...)
|
|
// The receiver is a parameter_list containing a parameter_declaration
|
|
// with a type that may be a pointer_type (*scrapeLoop) or plain type (scrapeLoop)
|
|
const receiver = getChildByField(node, 'receiver');
|
|
if (!receiver) return undefined;
|
|
// Find the type identifier inside the receiver
|
|
const text = getNodeText(receiver, source);
|
|
// Extract type name from patterns like "(sl *Type)", "(sl Type)", "(*Type)", "(Type)"
|
|
const match = text.match(/\*?\s*([A-Za-z_][A-Za-z0-9_]*)\s*\)/);
|
|
return match?.[1];
|
|
},
|
|
};
|