fix(go): require URL-shaped paths for route detection (#1308)

cache.Put("a", 1), store.Get("config", out), bus.Handle("user.created",
h) — any verb-named method with a string first arg — were indexed as
HTTP routes (38 of 82 route nodes were false positives on the
reporter's 200 KLOC Go codebase). A registration's first argument must
now start with "/" (every router style), or be a Go 1.22
"METHOD /path" mux pattern on Handle/HandleFunc — which now also
extracts the real method instead of ANY.

Validated on go-chi/chi (212 real routes retained, all path-shaped)
and golang/groupcache (0 route nodes).

Fixes #1259

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-07-16 14:59:00 -05:00
committed by GitHub
co-authored by Claude Fable 5
parent 30421953ac
commit e1f339f732
3 changed files with 60 additions and 5 deletions
+28 -5
View File
@@ -96,17 +96,29 @@ export const goResolver: FrameworkResolver = {
let match: RegExpExecArray | null;
while ((match = routeRegex.exec(safe)) !== null) {
const [, rawMethod, routePath, handlerExpr] = match;
// The first argument must be URL-shaped, or this is just a method that
// happens to share a verb name — `cache.Put("key", val)`, `store.Get(...)`,
// `bus.Handle("user.created", h)` all polluted the route index (#1259).
// Real registrations use "/path" (every router), or net/http's Go 1.22
// "METHOD /path" patterns on Handle/HandleFunc.
const methodPrefix = matchGo122MethodPattern(routePath!, rawMethod!);
if (!routePath!.startsWith('/') && !methodPrefix) continue;
const line = safe.slice(0, match.index).split('\n').length;
const method =
rawMethod === 'Handle' || rawMethod === 'HandleFunc'
// "GET /users/{id}" -> method GET, path /users/{id}
const path = methodPrefix ? routePath!.slice(methodPrefix.length).trimStart() : routePath!;
const method = methodPrefix
? methodPrefix
: rawMethod === 'Handle' || rawMethod === 'HandleFunc'
? 'ANY'
: rawMethod!.toUpperCase();
const routeNode: Node = {
id: `route:${filePath}:${line}:${method}:${routePath}`,
id: `route:${filePath}:${line}:${method}:${path}`,
kind: 'route',
name: `${method} ${routePath}`,
qualifiedName: `${filePath}::route:${routePath}`,
name: `${method} ${path}`,
qualifiedName: `${filePath}::route:${path}`,
filePath,
startLine: line,
endLine: line,
@@ -135,6 +147,17 @@ export const goResolver: FrameworkResolver = {
},
};
/**
* Go 1.22 net/http mux patterns: `mux.HandleFunc("GET /users/{id}", h)`.
* Returns the HTTP method when the pattern starts with one, null otherwise.
* Only Handle/HandleFunc take these — Gin/Chi verb methods take a bare path.
*/
function matchGo122MethodPattern(routePath: string, rawMethod: string): string | null {
if (rawMethod !== 'Handle' && rawMethod !== 'HandleFunc') return null;
const m = routePath.match(/^(GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD|CONNECT|TRACE)\s+\S/);
return m ? m[1]! : null;
}
/** Extract the last identifier from an expression like `pkg.Sub.handler` or `handler`. */
function extractGoTailIdent(expr: string): string | null {
const cleaned = expr.trim().replace(/\s+/g, '').replace(/\(\)$/, '');