fix(go): an interface's methods link the types their signatures name (#2659)

A Go interface's methods are `method` nodes, but their signatures named
no types: etcd's `KV.Put(ctx context.Context, key, val string, opts
...OpOption) (*PutResponse, error)` had no edge to `OpOption` or
`PutResponse`. A call through the interface reaches that method, so
impact on those types missed the interface method and the callers that go
through it.

TS extractor and Rust kernel, mirrored:
- extractGoInterfaceMethods / extract_go_interface_methods give each
  method a `references` ref per type name in its parameters and results
  (goInterfaceMethodTypeNames / interface_method_type_names, on #2655's
  collectGoTypeNames).
- Skipped: the predeclared types and, written bare, the interface's own
  type parameters (goTypeParameterNames / type_parameter_names, split out
  of goDeclaredNames). The interface's own name is a type its method names
  (`Children() []Node`), so it is kept.

A/B on main 6692e051: gin +34, etcd +775, prometheus +294, kubernetes
+11079 edges; every one judged against the real package clause (bare →
own package, `pkg.T` → the import's package, kubernetes' one dot import
included); 0 dropped/retargeted/metadata-only. Kernel and wasm indexes
are identical. Explore keeps every named body on 29 flow queries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-10-19 10:38:55 +00:00
committed by GitHub
parent 6692e05107
commit 383c2cd9e3
6 changed files with 383 additions and 10 deletions
+1
View File
@@ -139,6 +139,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- In Go, a type defined from other types, like etcd's `type WatchChan <-chan WatchResponse` or gin's `type HandlerFunc func(*Context)` and `type HandlersChain []HandlerFunc`, now links to the types it is built from, so impact on `WatchResponse` or gin's `Context` reaches these types and the code that uses them. Before, such a type linked to nothing. One written through another package, like etcd's `type PutResponse pb.PutResponse`, links to that package's type, and a recursive one like `type stateFn func(*Lexer) stateFn` doesn't link to itself. Re-index Go projects after upgrading.
- In Go, a struct now links to the types its fields are declared with, like etcd's `PutResponse` holding a `*ResponseHeader` or gin's `Context` holding a `HandlersChain`, so impact on `ResponseHeader` or `HandlersChain` reaches every struct that holds one and the code that uses those structs, and `codegraph_node` lists a struct's field types. Before, a struct linked only to the types it embeds. A field written through another package, like `*pb.ResponseHeader`, links to that package's type, one from outside your project, like `context.Context`, links to nothing, the types an embedded generic is filled in with, like the `Pod` in client-go's `listers.ResourceIndexer[*v1.Pod]`, link too, and a struct that holds itself, like a list node's `next *node`, doesn't link to itself. `codegraph_explore` still answers a question about a flow with the code that runs it, not with the structs that merely hold one of its types. Re-index Go projects after upgrading.
- In Go, an unexported name used in another file of its package, like prometheus's `memSeries` in `tsdb/head_append.go`, now links to that package's own declaration when a package nested inside it, like `tsdb/agent`, declares the same name. Before, such a parameter, result, struct field, composite literal or call linked to nothing. A call or a function value that the surrounding function binds itself, like a test's `init := func(…)` followed by `init(t)`, or a local `volumeSpec` variable passed on, no longer links to the package's declaration of that name. Re-index Go projects after upgrading.
- In Go, a method an interface declares, like etcd's `KV.Put(ctx context.Context, key, val string, opts ...OpOption) (*PutResponse, error)`, now links to the types its parameters and results name, so impact on `PutResponse` or `OpOption` reaches the interface method and every caller that goes through the interface. Before, an interface's methods linked to no types at all. Re-index Go projects after upgrading.
- In Go, a struct or defined type now counts as implementing an interface only when its methods take as many parameters and return as many results as the interface's. Before, matching method names was enough, so etcd's watch cache, whose `Get` takes a context, a key and options, was listed as a `peerGetter`, whose `Get` takes a peer ID, and a call through an interface could be shown reaching methods it can never reach. Look-alikes could also crowd out the real implementations, so `codegraph_explore` stopped at grpc-go's xDS `TransportBuilder` instead of reaching the transport it builds. A server still counts for the gRPC client interface of the service it serves, like etcd's `kvServer` for `KVClient`, so a client's call keeps leading to the handler that serves it. Re-index Go projects after upgrading.
- In Angular templates, a property binding, an interpolation, a structural directive or a control-flow block that calls one of the component's own members, like `[name]="icon()"`, `{{ label() }}`, `*ngIf="isOpen()"` or `@if (loading()) {`, now links the component to that member, and so does reading a getter, like `[disabled]="!canSave"`, or handing a method to a child component, like `[displayWith]="displayFn"` or `trackBy: trackById`. Before, only event bindings such as `(click)="save()"` were read, so a signal, a `computed` value, a getter or a method used only from a template had no callers and looked unused. A pipe, a template variable, a call on another object such as `form.reset()`, and a plain field read like `[value]="title"` still link nothing. Re-index Angular projects after upgrading.
- React Router routes kept in a table of their own are now indexed: the ASP.NET Core React template's `AppRoutes` array that `App.js` maps into `<Route {...rest}>`, a `routes` array imported into `useRoutes(routes)` or `createBrowserRouter(routes)` or returned by a function as in `useRoutes(routes(isLoggedIn))`, route objects kept one per file and listed in `createBrowserRouter([MainRoutes, LoginRoutes])`, and routes written in place in `useRoutes([…])`. Before, route objects were read only from a file that itself creates a data router, so these apps had no routes, and their `navigate('/login')` calls and `<Link to="/counter">` links led nowhere. Each route links to the page its `element` renders, past a guard like `<ProtectedRoute>` or `<AdminWrapper>`, and through `lazy(() => import(…))` to the page it loads rather than a same-named page elsewhere in the repository. An `index: true` route is the page at its parent's address, and a parent route around others counts as their layout. A `{ path, element }` list that nothing hands to the router, such as a menu, still makes no routes. Re-index React projects after upgrading.
@@ -78,6 +78,14 @@ type Core interface {
Unmarshal(data []byte) error
}
// An interface method references the types its signature names, but the
// interface's own type parameters and the predeclared types.
type Repo[T any] interface {
Find(ctx pkga.Context, id Dur) (*Widget, error)
Each(fn func(T, *Stack[int]) bool) []pkga.Item
Self() Repo[T]
}
type Number interface {
~int | ~float64
}
+297
View File
@@ -0,0 +1,297 @@
/**
* A Go interface's methods are `method` nodes, and their signatures named no
* types: etcd's `KV.Put(ctx context.Context, key, val string, opts
* ...OpOption) (*PutResponse, error)` had no edge to `OpOption` or
* `PutResponse`. A call through the interface reaches that method, so impact
* on `PutResponse` missed the interface method and every caller that goes
* through it — the usual way Go code calls a dependency.
*
* An interface method now references each type its parameters and results
* name, as a function does, on the name. Not references: Go's predeclared
* types, and the interface's own type parameters written bare (`T` in
* `type Store[T any] interface { Get(string) (T, error) }`). The interface's
* own name is a type its method names (`Children() []Node`).
*/
import { describe, it, expect, beforeAll, beforeEach, afterEach, afterAll } from 'vitest';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import CodeGraph from '../src/index';
import { extractFromSource } from '../src/extraction';
import { initGrammars, loadGrammarsForLanguages } from '../src/extraction/grammars';
import { tryKernelExtract, resetKernelForTests } from '../src/extraction/kernel';
import type { ExtractionResult } from '../src/types';
const KERNEL_PATH = path.join(
__dirname,
'..',
'codegraph-kernel',
'prebuilds',
`${process.platform}-${process.arch}`,
'codegraph-kernel.node'
);
const kernelAvailable = fs.existsSync(KERNEL_PATH) || process.env.CODEGRAPH_KERNEL_EXPECT === '1';
/** Every shape an interface method's signature can take. */
const IFACE_SOURCE = `package clientv3
import (
"context"
pb "go.etcd.io/etcd/api/v3/etcdserverpb"
)
type KV interface {
// Put puts a key-value pair into etcd.
Put(ctx context.Context, key, val string, opts ...OpOption) (*PutResponse, error)
Get(ctx context.Context, key string, opts ...OpOption) (*GetResponse, error)
Do(ctx context.Context, op Op) (OpResponse, error)
Txn(ctx context.Context) Txn
Close() error
Header() *pb.ResponseHeader
Watch(fn func(Event) bool, ch chan<- []Event, filters map[string]Filter) (<-chan WatchResponse, int64)
}
type Store[T any, K comparable] interface {
Get(key K) (T, bool)
Put(key K, value T) Store[T, K]
Fetch(item Item[T]) any
}
type Node interface {
Children() []Node
Name() string
}
type Embedding interface {
KV
Lease
}
`;
const ENV_KEYS = ['CODEGRAPH_KERNEL', 'CODEGRAPH_KERNEL_LANGS'] as const;
/** `<Interface::method> <name>` for every `references` ref out of a method, sorted. */
function methodRefs(result: ExtractionResult): string[] {
const byId = new Map(result.nodes.map((n) => [n.id, n]));
return result.unresolvedReferences
.filter((r) => r.referenceKind === 'references' && byId.get(r.fromNodeId)?.kind === 'method')
.map((r) => `${byId.get(r.fromNodeId)!.qualifiedName} ${r.referenceName}`)
.sort();
}
describe('a Go interface method references the types its signature names', () => {
let savedEnv: Record<string, string | undefined> = {};
beforeAll(async () => {
await initGrammars();
await loadGrammarsForLanguages(['go']);
});
beforeEach(() => {
savedEnv = Object.fromEntries(ENV_KEYS.map((k) => [k, process.env[k]]));
resetKernelForTests();
});
afterEach(() => {
for (const k of ENV_KEYS) {
if (savedEnv[k] === undefined) delete process.env[k];
else process.env[k] = savedEnv[k];
}
resetKernelForTests();
});
function extract(backend: 'kernel' | 'wasm', file: string, source: string): ExtractionResult {
if (backend === 'wasm') {
process.env.CODEGRAPH_KERNEL = '0';
return extractFromSource(file, source, 'go');
}
delete process.env.CODEGRAPH_KERNEL;
process.env.CODEGRAPH_KERNEL_LANGS = 'all';
const result = tryKernelExtract(file, source, 'go');
expect(result, `kernel extraction of ${file}`).not.toBeNull();
return result!;
}
const backends = kernelAvailable ? (['kernel', 'wasm'] as const) : (['wasm'] as const);
for (const crlf of [false, true]) {
const eol = (s: string) => (crlf ? s.replace(/\n/g, '\r\n') : s);
const label = crlf ? ' (CRLF)' : '';
it.each(backends)(`each method references the types of its parameters and results: %s${label}`, (backend) => {
const source = eol(IFACE_SOURCE);
const result = extract(backend, 'kv.go', source);
// Not references: string, error, bool, int64, any; Store's own type
// parameters T and K; the embedded KV and Lease, which are its supertypes.
expect(methodRefs(result)).toEqual([
'KV::Do Context',
'KV::Do Op',
'KV::Do OpResponse',
'KV::Get Context',
'KV::Get GetResponse',
'KV::Get OpOption',
'KV::Header ResponseHeader',
'KV::Put Context',
'KV::Put OpOption',
'KV::Put PutResponse',
'KV::Txn Context',
'KV::Txn Txn',
'KV::Watch Event',
'KV::Watch Event',
'KV::Watch Filter',
'KV::Watch WatchResponse',
'Node::Children Node',
'Store::Fetch Item',
'Store::Put Store',
]);
// Each sits on its name, where resolution reads a package qualifier back.
const lines = source.split(/\r?\n/);
const byId = new Map(result.nodes.map((n) => [n.id, n]));
for (const r of result.unresolvedReferences.filter((u) => u.referenceKind === 'references' && byId.get(u.fromNodeId)?.kind === 'method')) {
expect(lines[r.line - 1]!.startsWith(r.referenceName, r.column), `${r.referenceName} at ${r.line}:${r.column}`).toBe(true);
}
});
it.each(backends)(`the methods are still the interface's, and embeddings still its supertypes: %s${label}`, (backend) => {
const result = extract(backend, 'kv.go', eol(IFACE_SOURCE));
expect(result.nodes.filter((n) => n.kind === 'method').map((n) => n.qualifiedName)).toEqual([
'KV::Put', 'KV::Get', 'KV::Do', 'KV::Txn', 'KV::Close', 'KV::Header', 'KV::Watch',
'Store::Get', 'Store::Put', 'Store::Fetch',
'Node::Children', 'Node::Name',
]);
const embedding = result.nodes.find((n) => n.name === 'Embedding')!;
expect(result.unresolvedReferences.filter((r) => r.fromNodeId === embedding.id)
.map((r) => `${r.referenceKind} ${r.referenceName}`)).toEqual(['extends KV', 'extends Lease']);
});
}
});
/** etcd's shape, with namesakes in a package that sorts first. */
const MODULE: Record<string, string> = {
'go.mod': 'module go.etcd.io/etcd\n\ngo 1.24\n',
'alpha/names.go': [
'package alpha',
'',
'type OpOption struct{}',
'',
'type PutResponse struct{}',
'',
'type T struct{}',
'',
].join('\n'),
'api/v3/etcdserverpb/rpc.pb.go': [
'package etcdserverpb',
'',
'type ResponseHeader struct{}',
'',
].join('\n'),
'client/v3/kv.go': [
'package clientv3',
'',
'import (',
'\t"context"',
'',
'\tpb "go.etcd.io/etcd/api/v3/etcdserverpb"',
')',
'',
'type PutResponse struct {',
'\tHeader *pb.ResponseHeader',
'}',
'',
'type Op struct{}',
'',
'type OpOption func(*Op)',
'',
'type KV interface {',
'\tPut(ctx context.Context, key, val string, opts ...OpOption) (*PutResponse, error)',
'}',
'',
'type T struct{}',
'',
'type Getter[T any] interface {',
'\tGet(key string) (T, error)',
'}',
'',
].join('\n'),
'server/store.go': [
'package server',
'',
'import (',
'\t"context"',
'',
'\tclientv3 "go.etcd.io/etcd/client/v3"',
')',
'',
'func save(ctx context.Context, kv clientv3.KV) {',
'\tkv.Put(ctx, "k", "v")',
'}',
'',
].join('\n'),
};
describe.each([
['default', 'LF'],
['wasm', 'LF'],
['default', 'CRLF'],
['wasm', 'CRLF'],
])('an indexed Go module links its interface methods’ types (%s, %s)', (backend, eol) => {
let root = '';
let cg: CodeGraph | undefined;
let kernel: string | undefined;
beforeAll(async () => {
kernel = process.env.CODEGRAPH_KERNEL;
if (backend === 'wasm') process.env.CODEGRAPH_KERNEL = '0';
else delete process.env.CODEGRAPH_KERNEL;
root = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-go-iface-'));
for (const [rel, content] of Object.entries(MODULE)) {
fs.mkdirSync(path.dirname(path.join(root, rel)), { recursive: true });
fs.writeFileSync(path.join(root, rel), eol === 'CRLF' ? content.replace(/\n/g, '\r\n') : content);
}
cg = await CodeGraph.init(root, { index: true });
}, 60_000);
afterAll(() => {
cg?.destroy();
if (root) fs.rmSync(root, { recursive: true, force: true });
if (kernel === undefined) delete process.env.CODEGRAPH_KERNEL;
else process.env.CODEGRAPH_KERNEL = kernel;
});
const nodeNamed = (file: string, qualifiedName: string) => {
const nodes = cg!.getNodesInFile(file).filter((n) => n.qualifiedName === qualifiedName && n.kind !== 'import');
expect(nodes, `${qualifiedName} in ${file}`).toHaveLength(1);
return nodes[0]!;
};
const linksFrom = (file: string, qualifiedName: string) =>
cg!.getOutgoingEdgesFrom([nodeNamed(file, qualifiedName).id])
.filter((e) => e.kind !== 'contains')
.map((e) => `${e.kind} ${cg!.getNode(e.target)!.filePath}:${cg!.getNode(e.target)!.name}`)
.sort();
const impactOf = (file: string, qualifiedName: string) =>
[...cg!.getImpactRadius(nodeNamed(file, qualifiedName).id, 3).nodes.values()].map((n) => `${n.filePath}:${n.qualifiedName}`).sort();
it('an interface method references its own package’s types, and nothing for an outside package', () => {
// `context.Context` is outside the project; alpha's namesakes sort first.
expect(linksFrom('client/v3/kv.go', 'KV::Put')).toEqual([
'references client/v3/kv.go:OpOption',
'references client/v3/kv.go:PutResponse',
]);
});
it('a generic interface’s own type parameter is no type its method references', () => {
expect(linksFrom('client/v3/kv.go', 'Getter::Get')).toEqual([]);
expect(cg!.getUnresolvedReferencesFrom(nodeNamed('client/v3/kv.go', 'Getter::Get').id)).toEqual([]);
});
it('impact on a type reaches the interface methods that name it, and the callers that go through them', () => {
expect(impactOf('client/v3/kv.go', 'OpOption')).toEqual([
'client/v3/kv.go:KV::Put',
'client/v3/kv.go:OpOption',
'server/store.go:save',
]);
});
});
+33 -5
View File
@@ -610,7 +610,7 @@ impl<'t> Walker<'t> {
};
if let Some(type_child) = type_child {
self.extract_inheritance(type_child, row);
self.extract_go_interface_methods(type_child, row, &name);
self.extract_go_interface_methods(type_child, row, &name, node);
}
return true;
}
@@ -634,9 +634,12 @@ impl<'t> Walker<'t> {
false
}
/// extractGoInterfaceMethods: method_elem/method_spec → method nodes.
fn extract_go_interface_methods(&mut self, interface_type: Node<'t>, iface_row: u32, iface_name: &str) {
/// extractGoInterfaceMethods: method_elem/method_spec → method nodes, each
/// referencing the types its signature names (`decl` declares the
/// interface).
fn extract_go_interface_methods(&mut self, interface_type: Node<'t>, iface_row: u32, iface_name: &str, decl: Node<'t>) {
self.stack.push(Scope { row: iface_row, kind: "interface", name: iface_name.to_string() });
let references = edge_kind_index("references").unwrap();
for i in 0..interface_type.named_child_count() {
let Some(m) = interface_type.named_child(i) else { continue };
if !matches!(m.kind(), "method_elem" | "method_spec") {
@@ -647,12 +650,30 @@ impl<'t> Walker<'t> {
let mname = self.text(name_node).to_string();
if !mname.is_empty() {
let signature = self.signature_of(m);
self.create_node("method", &mname, m, Extra { signature, ..Extra::default() });
let Some(row) = self.create_node("method", &mname, m, Extra { signature, ..Extra::default() }) else { continue };
for ty in self.interface_method_type_names(m, decl) {
let text = self.text(ty).to_string();
self.push_ref_at(row, &text, references, ty);
}
}
}
self.stack.pop();
}
/// goInterfaceMethodTypeNames (languages/go.ts): the name nodes of the
/// types an interface method's parameters and results name, but the
/// predeclared types and the interface's own type parameters.
fn interface_method_type_names(&self, method: Node<'t>, decl: Node<'t>) -> Vec<Node<'t>> {
let mut names = Vec::new();
let params = self.type_parameter_names(decl);
for field in ["parameters", "result"] {
if let Some(part) = method.child_by_field_name(field) {
self.collect_type_names(part, &params, false, &mut names);
}
}
names
}
/// extractVariable's Go branch: var/const specs + short_var_declaration.
fn extract_variable(&mut self, node: Node<'t>) {
let docstring = self.docstring_of(node);
@@ -909,10 +930,17 @@ impl<'t> Walker<'t> {
/// declaration's own type parameters, and its own name, which in a
/// recursive type is the declaration itself.
fn declared_names(&self, decl: Node<'t>) -> HashSet<&'t str> {
let mut names = HashSet::new();
let mut names = self.type_parameter_names(decl);
if let Some(name) = decl.child_by_field_name("name") {
names.insert(self.text(name));
}
names
}
/// goTypeParameterNames (languages/go.ts): the type parameters a type
/// declaration declares.
fn type_parameter_names(&self, decl: Node<'t>) -> HashSet<&'t str> {
let mut names = HashSet::new();
if let Some(list) = decl.child_by_field_name("type_parameters") {
for param in (0..list.named_child_count()).filter_map(|i| list.named_child(i)) {
for c in (0..param.named_child_count()).filter_map(|j| param.named_child(j)) {
+26 -1
View File
@@ -70,9 +70,15 @@ export function goEmbeddedTypeName(type: SyntaxNode | null | undefined, source:
* its own name, which in a recursive type is the declaration itself.
*/
function goDeclaredNames(decl: SyntaxNode, source: string): Set<string> {
const names = new Set<string>();
const names = goTypeParameterNames(decl, source);
const nameNode = getChildByField(decl, 'name');
if (nameNode) names.add(getNodeText(nameNode, source));
return names;
}
/** The type parameters a Go type declaration declares: `T` and `K` in `type Store[T any, K comparable] …`. */
function goTypeParameterNames(decl: SyntaxNode, source: string): Set<string> {
const names = new Set<string>();
for (const param of getChildByField(decl, 'type_parameters')?.namedChildren ?? []) {
for (const c of param.namedChildren) {
if (c.type === 'identifier') names.add(getNodeText(c, source));
@@ -150,6 +156,25 @@ export function goStructFieldTypeNames(node: SyntaxNode, source: string): Syntax
return names;
}
/**
* The name nodes of the types a Go interface method's signature names, in
* source order: `OpOption` and `PutResponse` in etcd's `Put(ctx
* context.Context, key, val string, opts ...OpOption) (*PutResponse, error)`,
* and `Context`, whose package stays in the source. `decl` declares the
* interface. Skipped: Go's predeclared types and, written bare, the
* interface's own type parameters (`T` in `type Store[T any] interface`). Its
* own name is a type the method names (`Children() []Node`).
*/
export function goInterfaceMethodTypeNames(method: SyntaxNode, decl: SyntaxNode, source: string): SyntaxNode[] {
const params = goTypeParameterNames(decl, source);
const names: SyntaxNode[] = [];
for (const field of ['parameters', 'result']) {
const part = getChildByField(method, field);
if (part) collectGoTypeNames(part, params, source, names);
}
return names;
}
export const goExtractor: LanguageExtractor = {
functionTypes: ['function_declaration'],
classTypes: [], // Go doesn't have classes
+18 -4
View File
@@ -25,7 +25,7 @@ import { stripCppTemplateArgs, isCppConstructorDeclaration, cDeclaratorIdentifie
import { NestedIntervals, scanCppBraceScopes, type CppBraceScopes } from './languages/cpp-brace-scopes';
import { appendCppMacroMethods } from './languages/cpp-macro-methods';
import { rustImplTypeName } from './languages/rust';
import { goAliasTypeNames, goEmbeddedTypeName, goStructFieldTypeNames } from './languages/go';
import { goAliasTypeNames, goEmbeddedTypeName, goInterfaceMethodTypeNames, goStructFieldTypeNames } from './languages/go';
import { dartMisparsedGenericCall, dartReceiverOf, isDartTypeName, pushDartTypeRefs } from './languages/dart';
import { LiquidExtractor } from './liquid-extractor';
import { RazorExtractor } from './razor-extractor';
@@ -3909,7 +3909,7 @@ export class TreeSitterExtractor {
// impl-navigation can see the contract. Go has no `implements` keyword, so
// without the interface's method set there's nothing to match against.
if (this.language === 'go' && typeChild) {
this.extractGoInterfaceMethods(typeChild, interfaceNode.id);
this.extractGoInterfaceMethods(typeChild, interfaceNode.id, node);
}
return true;
}
@@ -3959,8 +3959,11 @@ export class TreeSitterExtractor {
* interface). tree-sitter-go names these `method_elem` (newer) or
* `method_spec` (older). Embedded interfaces (`Reader` inside `ReadWriter`)
* are `type_elem`s, not methods, and are left to inheritance extraction.
* Each method references the types its signature names, as a function does
* (`decl` declares the interface): a call through the interface reaches the
* method, so impact on a type reaches its callers.
*/
private extractGoInterfaceMethods(interfaceType: SyntaxNode, ifaceId: string): void {
private extractGoInterfaceMethods(interfaceType: SyntaxNode, ifaceId: string, decl: SyntaxNode): void {
this.nodeStack.push(ifaceId);
for (let i = 0; i < interfaceType.namedChildCount; i++) {
const m = interfaceType.namedChild(i);
@@ -3969,9 +3972,20 @@ export class TreeSitterExtractor {
if (!nameNode) continue;
const mname = getNodeText(nameNode, this.source);
if (mname) {
this.createNode('method', mname, m, {
const methodNode = this.createNode('method', mname, m, {
signature: this.extractor?.getSignature?.(m, this.source),
});
if (methodNode) {
for (const type of goInterfaceMethodTypeNames(m, decl, this.source)) {
this.unresolvedReferences.push({
fromNodeId: methodNode.id,
referenceName: getNodeText(type, this.source),
referenceKind: 'references',
line: type.startPosition.row + 1,
column: type.startPosition.column,
});
}
}
}
}
this.nodeStack.pop();