From c60af8247ba5eaf47f97baa9a62b708a291008c3 Mon Sep 17 00:00:00 2001 From: rgb-vgx Date: Sun, 4 Oct 2026 17:16:44 +0700 Subject: [PATCH] fix(group): accept raw-string Go route paths in the framework query The framework route query anchored the path on interpreted_string_literal only, so `GET(`/health`, h)` produced no group-layer contract although ingestion (Strategy A) decodes both Go string forms via the shared stringLiteral. Match raw_string_literal too so both strategies emit the same contract id. Co-Authored-By: Claude Opus 5.5 --- .../core/group/extractors/http-patterns/go.ts | 5 +++-- .../unit/group/go-gin-route-groups.test.ts | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/gitnexus/src/core/group/extractors/http-patterns/go.ts b/gitnexus/src/core/group/extractors/http-patterns/go.ts index 0504c8003..d61f70ef4 100644 --- a/gitnexus/src/core/group/extractors/http-patterns/go.ts +++ b/gitnexus/src/core/group/extractors/http-patterns/go.ts @@ -21,7 +21,8 @@ import type { HttpDetection, HttpLanguagePlugin } from './types.js'; // ─── Provider: framework routing ────────────────────────────────────── // Matches `\w+\.GET(...)` etc. (gin and echo share this shape). // Captures the receiver, the HTTP method (field name), and the path literal -// — anchored as the FIRST argument so the code can pick the handler out of +// — anchored as the FIRST argument (either Go string form; stringLiteral +// decodes both, as ingestion does) so the code can pick the handler out of // the remaining arguments. Which argument that is depends on the framework: // gin is `GET(path, middleware..., handler)` (last), echo is // `GET(path, handler, middleware...)` (first) — see readFrameworkImports and @@ -43,7 +44,7 @@ const FRAMEWORK_ROUTE_PATTERNS = compilePatterns({ field: (field_identifier) @http_method (#match? @http_method "^(GET|POST|PUT|DELETE|PATCH)$")) arguments: (argument_list . - (interpreted_string_literal) @path)) + [(interpreted_string_literal) (raw_string_literal)] @path)) `, }, ], 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 478aca60a..412743d15 100644 --- a/gitnexus/test/unit/group/go-gin-route-groups.test.ts +++ b/gitnexus/test/unit/group/go-gin-route-groups.test.ts @@ -430,6 +430,24 @@ func routes(r *gin.Engine) { ).toEqual([{ method: 'GET', path: '/api/x', name: 'X' }]); }); + it('accepts a raw-string (backtick) route path, as ingestion does', () => { + // Ingestion (Strategy A) decodes both Go string forms for the route path; + // the group layer must emit the same contract for a backtick path, both + // on a bound group and on a chained `Group(...).GET(...)` receiver. + expect( + providers(`package main +func routes(r *gin.Engine) { + g := r.Group("/api") + g.GET(\`/health\`, h.Health) + r.Group("/v1").POST(\`/raw\\x2fy\`, h.Raw) +} +`), + ).toEqual([ + { method: 'GET', path: '/api/health', name: 'Health' }, + { method: 'POST', path: '/v1/raw\\x2fy', name: 'Raw' }, + ]); + }); + it('decodes Go string escapes in group prefixes and route paths', () => { // "/api\x2fv1" and "/health\x2fcheck" are `/api/v1` and `/health/check` // once Go processes the escapes — the id must match the registered URL.