fix(group): require in-file-unique resolution for ambiguous Go handler names

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 <noreply@anthropic.com>
This commit is contained in:
rgb-vgx 2026-10-04 17:02:49 +07:00
parent 39f16b6111
commit 2bc0bed628
2 changed files with 52 additions and 0 deletions

View file

@ -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<string, number>();
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 }
: {}),
});
}

View file

@ -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