From 09f87189cb2ee0bbef7cadf51bd4fe88a563df57 Mon Sep 17 00:00:00 2001 From: evolution Date: Thu, 23 Apr 2026 15:08:56 +0800 Subject: [PATCH] fix(go): align worker CALLS source IDs for receiver methods (#1043) --- .../core/ingestion/workers/parse-worker.ts | 56 ++++++++++++------- .../go-receiver-method-free-call/example.go | 7 +++ .../go-receiver-method-free-call/go.mod | 3 + .../go-receiver-method-free-call/util.go | 3 + .../test/integration/resolvers/go.test.ts | 20 +++++++ 5 files changed, 69 insertions(+), 20 deletions(-) create mode 100644 gitnexus/test/fixtures/lang-resolution/go-receiver-method-free-call/example.go create mode 100644 gitnexus/test/fixtures/lang-resolution/go-receiver-method-free-call/go.mod create mode 100644 gitnexus/test/fixtures/lang-resolution/go-receiver-method-free-call/util.go diff --git a/gitnexus/src/core/ingestion/workers/parse-worker.ts b/gitnexus/src/core/ingestion/workers/parse-worker.ts index 8b56c1c160..82ae57d6c1 100644 --- a/gitnexus/src/core/ingestion/workers/parse-worker.ts +++ b/gitnexus/src/core/ingestion/workers/parse-worker.ts @@ -568,32 +568,48 @@ const findEnclosingFunctionId = ( filePath, provider.resolveEnclosingOwner, ); - const qualifiedName = classInfo ? `${classInfo.className}.${funcName}` : funcName; + const encLang = getLanguageFromFilename(filePath); + const standaloneMethodInfo = + (finalLabel === 'Method' || finalLabel === 'Constructor') && + encLang === SupportedLanguages.Go && + provider.methodExtractor?.extractFromNode + ? provider.methodExtractor.extractFromNode(current, { + filePath, + language: encLang, + }) + : null; + const ownerName = classInfo?.className ?? standaloneMethodInfo?.receiverType ?? undefined; + const qualifiedName = ownerName ? `${ownerName}.${funcName}` : funcName; // Include # suffix to match definition-phase Method/Constructor IDs. // Use the same MethodExtractor (getMethodInfo) as the definition phase. // When same-arity collisions exist, also append ~type1,type2. let arity: number | undefined; let encTypeTag = ''; if (finalLabel === 'Method' || finalLabel === 'Constructor') { - const encLang = getLanguageFromFilename(filePath); - const classNode = - findEnclosingClassNode(current) ?? findClassNodeByQualifiedName(current); - if (classNode && encLang) { - const methodMap = getMethodInfo(classNode, provider, { - filePath, - language: encLang, - }); - const defLine = current.startPosition.row + 1; - const info = methodMap?.get(`${funcName}:${defLine}`); - if (info) { - arity = info.parameters.some((p) => p.isVariadic) - ? undefined - : info.parameters.length; - if (methodMap && arity !== undefined) { - const g = buildCollisionGroups(methodMap); - encTypeTag = - typeTagForId(methodMap, funcName, arity, info, encLang, g) + - constTagForId(methodMap, funcName, arity, info, g); + if (standaloneMethodInfo) { + arity = standaloneMethodInfo.parameters.some((p) => p.isVariadic) + ? undefined + : standaloneMethodInfo.parameters.length; + } else { + const classNode = + findEnclosingClassNode(current) ?? findClassNodeByQualifiedName(current); + if (classNode && encLang) { + const methodMap = getMethodInfo(classNode, provider, { + filePath, + language: encLang, + }); + const defLine = current.startPosition.row + 1; + const info = methodMap?.get(`${funcName}:${defLine}`); + if (info) { + arity = info.parameters.some((p) => p.isVariadic) + ? undefined + : info.parameters.length; + if (methodMap && arity !== undefined) { + const g = buildCollisionGroups(methodMap); + encTypeTag = + typeTagForId(methodMap, funcName, arity, info, encLang, g) + + constTagForId(methodMap, funcName, arity, info, g); + } } } } diff --git a/gitnexus/test/fixtures/lang-resolution/go-receiver-method-free-call/example.go b/gitnexus/test/fixtures/lang-resolution/go-receiver-method-free-call/example.go new file mode 100644 index 0000000000..78bd37f541 --- /dev/null +++ b/gitnexus/test/fixtures/lang-resolution/go-receiver-method-free-call/example.go @@ -0,0 +1,7 @@ +package example + +type Example struct{} + +func (e *Example) Caller() { + callee() +} diff --git a/gitnexus/test/fixtures/lang-resolution/go-receiver-method-free-call/go.mod b/gitnexus/test/fixtures/lang-resolution/go-receiver-method-free-call/go.mod new file mode 100644 index 0000000000..23fa47fa85 --- /dev/null +++ b/gitnexus/test/fixtures/lang-resolution/go-receiver-method-free-call/go.mod @@ -0,0 +1,3 @@ +module example.com/go-receiver-method-free-call + +go 1.22 diff --git a/gitnexus/test/fixtures/lang-resolution/go-receiver-method-free-call/util.go b/gitnexus/test/fixtures/lang-resolution/go-receiver-method-free-call/util.go new file mode 100644 index 0000000000..b3181fb86a --- /dev/null +++ b/gitnexus/test/fixtures/lang-resolution/go-receiver-method-free-call/util.go @@ -0,0 +1,3 @@ +package example + +func callee() {} diff --git a/gitnexus/test/integration/resolvers/go.test.ts b/gitnexus/test/integration/resolvers/go.test.ts index 69761f889c..f9a57d660b 100644 --- a/gitnexus/test/integration/resolvers/go.test.ts +++ b/gitnexus/test/integration/resolvers/go.test.ts @@ -172,6 +172,26 @@ describe('Go member-call resolution', () => { }); }); +describe('Go receiver method free-call resolution', () => { + let result: PipelineResult; + + beforeAll(async () => { + result = await runPipelineFromRepo( + path.join(FIXTURES, 'go-receiver-method-free-call'), + () => {}, + { workerThresholdsForTest: { minFiles: 1, minBytes: 0 } }, + ); + }, 60000); + + it('resolves Caller -> callee when a receiver method calls a package-level function', () => { + const calls = getRelationships(result, 'CALLS'); + const calleeCall = calls.find((c) => c.source === 'Caller' && c.target === 'callee'); + expect(calleeCall).toBeDefined(); + expect(calleeCall!.targetLabel).toBe('Function'); + expect(calleeCall!.targetFilePath).toBe('util.go'); + }); +}); + // --------------------------------------------------------------------------- // Struct literal resolution: User{...} resolves to Struct node // ---------------------------------------------------------------------------