From 2bc0bed6285a53053c00186296c7f4050c1c48a5 Mon Sep 17 00:00:00 2001 From: rgb-vgx Date: Sun, 4 Oct 2026 17:02:49 +0700 Subject: [PATCH] fix(group): require in-file-unique resolution for ambiguous Go handler names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Selector handlers emit the field name only (`h.List`, `o.List` → `List`), and the non-strict resolver takes the first same-named row in the registration file. When that file declares the name more than once (methods on different receivers), mark the detection strictHandlerResolution so it resolves only to an in-file-unique match and otherwise keeps a file-level anchor instead of a wrong handler. Names declared once, or defined in other files, keep the existing resolution. Co-Authored-By: Claude Opus 5.5 --- .../core/group/extractors/http-patterns/go.ts | 17 +++++++++ .../unit/group/go-gin-route-groups.test.ts | 35 +++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/gitnexus/src/core/group/extractors/http-patterns/go.ts b/gitnexus/src/core/group/extractors/http-patterns/go.ts index e4a89ff37..0504c8003 100644 --- a/gitnexus/src/core/group/extractors/http-patterns/go.ts +++ b/gitnexus/src/core/group/extractors/http-patterns/go.ts @@ -465,6 +465,17 @@ export const GO_HTTP_PLUGIN: HttpLanguagePlugin = { const imports = readFrameworkImports(tree.rootNode); const echoOnly = imports.echo.size > 0 && imports.gin.size === 0; const mixed = imports.echo.size > 0 && imports.gin.size > 0; + // Handler names declared more than once in this file (`(h *A) List` and + // `(o *B) List`): the emitted name is field-only, so these must resolve + // only when unique in the file instead of taking the first same-named row. + const declaredNames = new Map(); + for (const decl of tree.rootNode.descendantsOfType([ + 'function_declaration', + 'method_declaration', + ])) { + const declName = decl.childForFieldName('name')?.text; + if (declName) declaredNames.set(declName, (declaredNames.get(declName) ?? 0) + 1); + } for (const match of runCompiledPatterns(FRAMEWORK_ROUTE_PATTERNS, tree)) { const methodNode = match.captures.http_method; const pathNode = match.captures.path; @@ -517,6 +528,12 @@ export const GO_HTTP_PLUGIN: HttpLanguagePlugin = { name: isInlineHandler ? null : handlerName, line: (handlerNode ?? pathNode).startPosition.row + 1, confidence: 0.8, + // An ambiguous in-file name resolves only when the file holds exactly + // one match (otherwise the route keeps a file-level anchor) rather than + // binding to whichever same-named method the graph lists first. + ...(!isInlineHandler && handlerName && (declaredNames.get(handlerName) ?? 0) > 1 + ? { strictHandlerResolution: true } + : {}), }); } diff --git a/gitnexus/test/unit/group/go-gin-route-groups.test.ts b/gitnexus/test/unit/group/go-gin-route-groups.test.ts index e6fe6b658..478aca60a 100644 --- a/gitnexus/test/unit/group/go-gin-route-groups.test.ts +++ b/gitnexus/test/unit/group/go-gin-route-groups.test.ts @@ -671,6 +671,41 @@ func routes() { ]); }); + it('requests in-file-unique resolution when the handler name is declared twice', () => { + // `h.List` and `o.List` both emit the field name `List`; with two `List` + // methods in this file, first-match resolution could bind either route to + // the wrong one, so both are marked strict. A unique name (`Show`) and a + // name defined elsewhere (`Remote`) keep the default resolution. + parser.setLanguage(Go); + const flags = GO_HTTP_PLUGIN.scan( + parser.parse(`package main +import "github.com/gin-gonic/gin" + +type A struct{} +type B struct{} + +func (a *A) List(c *gin.Context) {} +func (b *B) List(c *gin.Context) {} +func (a *A) Show(c *gin.Context) {} + +func routes(r *gin.Engine, h *A, o *B) { + r.GET("/a", h.List) + r.GET("/b", o.List) + r.GET("/s", h.Show) + r.GET("/r", other.Remote) +} +`), + ) + .filter((d) => d.role === 'provider') + .map((d) => [d.path, d.name, d.strictHandlerResolution ?? false]); + expect(flags).toEqual([ + ['/a', 'List', true], + ['/b', 'List', true], + ['/s', 'Show', false], + ['/r', 'Remote', false], + ]); + }); + it('applies the same group logic to echo', () => { expect( providers(`package main