Sfoglia il codice sorgente

docs(playbook): record frontier pass; test(go): gorilla/mux subrouter coverage

Frontier triage after the main sweep — tractable partials closed (React object
data-router, Next.js false-positive fix, Flask-RESTful add_resource, Flask
tuple methods + detection, gorilla/mux confirmed), and the genuinely
hard/low-precision ones (C callback fan-out, metaprogramming finders, reactive
runtimes, Akka, anonymous closures, lazy data-router, C++ pure-virtual) left
documented with rationale. Adds a gorilla/mux subrouter-var HandleFunc test
(confirms the any-receiver handling already covers it).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Colby McHenry 1 mese fa
parent
commit
42a0178

+ 8 - 0
__tests__/frameworks.test.ts

@@ -669,6 +669,14 @@ describe('goResolver.extract', () => {
     const { nodes, references } = goResolver.extract!('main.go', src);
     expect(references[0].referenceName).toBe('createItem');
   });
+
+  it('extracts gorilla/mux HandleFunc on a subrouter var, ignoring chained .Methods()', () => {
+    // `s` is a PathPrefix().Subrouter() var — any receiver is matched; the
+    // trailing .Methods("GET") doesn't break the handler capture.
+    const src = `s.HandleFunc("/users/{id}", listUsers).Methods("GET")\n`;
+    const { references } = goResolver.extract!('routes.go', src);
+    expect(references[0].referenceName).toBe('listUsers');
+  });
 });
 
 import { rustResolver } from '../src/resolution/frameworks/rust';

+ 16 - 3
docs/design/dynamic-dispatch-coverage-playbook.md

@@ -173,7 +173,7 @@ Status legend: ✅ done+validated · 🔬 hole identified · ⬜ not started.
 
 | Language | Framework(s) | Canonical flow to test | Mechanism | Status |
 |---|---|---|---|---|
-| TypeScript/JS | React / observer / EventEmitter / React Router | state→render; dispatch→callback; route→component | S + X | ✅ rendering+dispatch (excalidraw); **React Router JSX routing** `<Route path component={C}/>` (v5) + `element={<C/>}` (v6) → component (react-realworld **0→10, 10/10**). 🔬 object data-router `createBrowserRouter([{path,element}])` (modern v6) + a pre-existing Next.js false-positive (config files in a `pages/` app dir treated as routes) |
+| TypeScript/JS | React / observer / EventEmitter / React Router | state→render; dispatch→callback; route→component | S + X | ✅ rendering+dispatch (excalidraw); **React Router JSX routing** `<Route path component={C}/>` (v5) + `element={<C/>}` (v6) → component (react-realworld **0→10, 10/10**). + **object data-router** `createBrowserRouter([{path, element/Component}])` (literal form); Next.js config/`nextjs-pages` false-positives FIXED. 🔬 lazy data-router (`path: paths.x.path, lazy: () => import()` — variable paths + lazy modules) |
 | TypeScript/JS | Vue / Nuxt | template events (@click→handler); component composition; reactive→render | S + X | ✅ events + composition (vitepress S / vben M / element-plus L); 🔬 reactive→render (vue-core Proxy runtime — frontier, deferred) |
 | TypeScript/JS | Svelte / SvelteKit | template calls/composition; SvelteKit action→api; store→DOM | X | ✅ already strong (realworld S / skeleton M / shadcn L): template `{fn()}` calls, `<Pascal/>` composition, `import * as api` namespace, `load`→api all work out of the box. + exported-const object-of-functions extraction (SvelteKit `actions`). 🔬 `$lib`-namespace-from-action + store/reactive frontier |
 | TypeScript/JS | Express / Koa | request → route → handler → service | R + X | ✅ named handlers + middleware + controller/service (resolver) + **inline arrow handlers → service body calls** (realworld S 19 / parse M / ghost L 65 edges). 🔬 custom routers (payload had 0 routes — not `app.get`-style) |
@@ -181,8 +181,8 @@ Status legend: ✅ done+validated · 🔬 hole identified · ⬜ not started.
 | TypeScript/JS | RxJS / signals | subscribe → operator → observer | S | ⬜ |
 | Python | Django ORM | QuerySet → SQL compiler | R | ✅ |
 | Python | Django / DRF (views) | url → view → model | R + X | ✅ url→view (`path`/`url`/`as_view`) + **DRF `router.register`→ViewSet** (realworld S / wagtail M / saleor L); ORM QuerySet→SQL (prior work). 🔬 signals (`post_save`→receiver), DRF viewset CRUD actions (inherited), saleor GraphQL resolvers |
-| Python | Flask / FastAPI | request → route → handler → dependency | R + X | ✅ **Flask: handler resolved across intervening decorators (`@login_required`) + stacked `@x.route` lines** (microblog S 6→27, redash L decorator routes 6/6); **FastAPI: empty-path router-root routes `@router.get("")` incl. multi-line** (realworld S 12→20 / Netflix dispatch L **290/290 100%**) + **bare-name builtin guard** — a handler named after a Python builtin method (`index`/`get`/`update`/`count`…) was filtered as a builtin and lost its route→handler edge. 🔬 Flask-RESTful class-based `add_resource(Resource, '/x')` (redash — separate mechanism, not the README decorator/blueprint shape); FastAPI `Depends()` dependency edges (resolver exists, light validation) |
-| Go | Gin / chi / net-http | request → route → handler → service | X | ✅ **routes on ANY group var** (`v1.GET`, `PublicGroup.GET`) not just `r/router` (gin-vue-admin S→M 4→259 / realworld S / gitness L) — was missing all group-routed apps; named handlers resolve precisely. 🔬 inline `func(c){}` handlers (anonymous, body lost), gitness chi custom (26/321) |
+| Python | Flask / FastAPI | request → route → handler → dependency | R + X | ✅ **Flask: handler resolved across intervening decorators (`@login_required`) + stacked `@x.route` lines** (microblog S 6→27, redash L decorator routes 6/6); **FastAPI: empty-path router-root routes `@router.get("")` incl. multi-line** (realworld S 12→20 / Netflix dispatch L **290/290 100%**) + **bare-name builtin guard** — a handler named after a Python builtin method (`index`/`get`/`update`/`count`…) was filtered as a builtin and lost its route→handler edge. + **Flask-RESTful `add_resource(Resource,'/x')` → Resource class** (redash 6→**77**) + **tuple `methods=('GET',)`** (was mislabeled GET) + **broadened detection** (requirements/Pipfile/setup + subdir app-factory entrypoints — flask-realworld 0→**19**). 🔬 FastAPI `Depends()` dependency edges (light validation) |
+| Go | Gin / chi / gorilla/mux / net-http | request → route → handler → service | X | ✅ **routes on ANY group var** (`v1.GET`, `PublicGroup.GET`) not just `r/router` (gin-vue-admin S→M 4→259 / realworld S / gitness L) — was missing all group-routed apps; named handlers resolve precisely. **gorilla/mux confirmed covered** by the any-receiver `HandleFunc`/`Handle` handling (subrouter-var `s.HandleFunc(...)` + namespaced handlers; `.Methods()` chain ignored). 🔬 inline `func(c){}` handlers (anonymous, body lost); subrouter/`PathPrefix` path-prefix not prepended (label only); gitness chi custom (26/321) |
 | Rust | Axum / actix / Rocket | request → route → handler | R + X | ✅ **Axum chained methods + namespaced handlers** — `.route("/x", get(h1).post(h2))` emitted only the first method+handler, and `get(mod::handler)` captured the module not the fn (realworld-axum S **12→19, 19/19**); balanced-paren scan + per-method nodes + last-`::`-segment handler. **Rocket attribute macros 550/556 (99%)** (Rocket repo L) — already strong. crates.io named axum routes resolve (6/8; rest are closures/var handlers; its API is mostly the utoipa `routes!` macro = frontier). Cargo-workspace module resolution (prior work). **actix builder API** `web::resource("/x").route(web::get().to(h))` / `.to(h)` / App `.route("/x", web::get().to(h))` (actix-examples **51→128 routes, 35→112 resolved**) — was the dominant actix style and fully missed (the handler is in `.to(h)`, not `get(h)`). 🔬 actix `web::scope("/api")` prefix (not prepended to nested resource paths) + anonymous `.to` closure handlers |
 | Java | Spring | request → @RestController → @Autowired service → repo | R + X | ✅ **bare `@GetMapping`/`@PostMapping` + class `@RequestMapping` prefix join → route→method** (realworld S / mall M / halo L) — was missing all path-less method mappings; DI controller→service resolves (name + dir). 🔬 Spring Data JPA derived queries (`findByEmail`) — metaprogramming frontier |
 | Kotlin | Spring Boot / Jetpack Compose | request → @RestController → service; @Composable → child | R + X | ✅ **Spring Boot Kotlin** — the Spring resolver was `['java']`-only with a Java-syntax method regex (`public X name()`); extended to `.kt` + Kotlin `fun name(` handler matching (petclinic-kotlin **0→18, 18/18**; class-prefix joins; DI controller→repo resolves — `showOwner ← GET /owners/{ownerId}` → `OwnerRepository.findById`). **Compose composition already static** (@Composable→child are plain function calls — Jetcaster `PodcastInformation→HtmlTextContainer`). Java Spring unchanged (realworld 19/19). 🔬 Ktor `routing { get("/x"){…} }` lambda handlers (anonymous) + Compose recomposition (implicit `mutableStateOf`, no setState gate) + coroutines/Flow |
@@ -506,6 +506,19 @@ Status legend: ✅ done+validated · 🔬 hole identified · ⬜ not started.
   be bridged (only bases with a real method node — an inline default or non-pure virtual); plus the C
   callback-struct fan-out. Relied on deterministic validation (no A/B): the cross-file-call counts + precise
   override spot-check are conclusive.
+- **Frontier pass (2026-05-23) — tractable partials closed, noise/hard ones deliberately left.** After the main
+  sweep, swept the documented frontiers and triaged by precision/value. **DONE:** React Router object
+  data-router (literal `createBrowserRouter([{path, element}])`); Next.js route false-positives (config files +
+  `nextjs-pages/` substring → require a real page ext + path-segment match; bulletproof 4→0); Flask-RESTful
+  `add_resource`→Resource class (redash 6→**77**); Flask tuple `methods=(…)`; Flask detection broadened to
+  subdir/app-factory entrypoints (flask-realworld 0→**19**); gorilla/mux confirmed already covered (any-receiver
+  HandleFunc) + a test. **LEFT (with rationale, not punts):** C callback-struct dispatch (`cmd->proc()` →
+  422-way field fan-out = noise); metaprogramming finders (ActiveRecord/Eloquent/Spring-Data-JPA/EF — dynamic
+  naming, no static target); reactive runtimes (Vue Proxy / Compose recomposition — deep internals, no
+  setState-style gate); Akka actor message dispatch (untyped); pure anonymous inline closures (the def-use
+  frontier — no named target); React lazy data-router (variable paths + lazy imports); C++ pure-virtual base
+  methods (extracting bodyless decls risks duplicate decl/def nodes for modest gain). Forcing these would add
+  noise, violating "partial coverage worse than none."
 - **Difficulty gradient is real:** named-ref dispatch (resolver) is cheap; anonymous
   callback dispatch (synthesizer) is medium; **anonymous-arrow handlers are the hard
   remaining gap** (no identity → need synthesizer link-through-body, not yet built).