feat: detect page/websocket kinds across 9 framework extractors#9
feat: detect page/websocket kinds across 9 framework extractors#9
Conversation
Sets up vitest as the test runner and adds the first per-extractor test using the FastAPI fixture (which already emits kind: "websocket" for @app.websocket routes). Establishes the fixture-driven pattern that subsequent per-framework tests will follow.
…ls) kinds Class-based views deriving from TemplateView/ListView/DetailView and FBVs calling render() are now emitted as kind: "page". websocket_urlpatterns entries (Django Channels) emit kind: "websocket". Strips .as_view/.as_asgi from handler names so view classes can be looked up by name.
Replaced the lazy single-line route regex with a balanced-paren walker so
multi-line arrow handler bodies can be inspected. Page detection looks for
res.render(...) or res.sendFile(...) in the handler body. Websocket
detection covers app.ws() (express-ws), io.on('connection')/io.of()
(socket.io), and new WebSocketServer (ws library).
A route is page-shaped when its decorator args set response_class=HTMLResponse or its function body returns an HTMLResponse(...) or .TemplateResponse(...). Body window is bounded to the next top-level decorator/def to avoid bleeding between adjacent routes.
A handler whose body contains render_template() or render_template_string() emits kind: "page". Body window is bounded to the next top-level def/class/ decorator at column 0 so adjacent handlers do not leak into each other's classification.
A route handler emits kind: "websocket" when its file imports github.com/gorilla/websocket and the handler body contains an .Upgrade( call. Applies uniformly across gin, echo, fiber, and net/http via a shared findWebsocketHandlers helper. Also tightened the handler-name regex so trailing handler args resolve correctly.
Routes defined in routes/web.php emit kind: "page" (session/CSRF/Blade view convention); routes from routes/api.php remain kind: "api". The file-of-origin distinction is reused from the existing /api prefix logic.
… kinds
Methods carrying @Render('view') emit kind: "page". Classes annotated
with @WebSocketGateway() emit each @SubscribeMessage('event') method as a
websocket endpoint with method WS. Methods are now associated with their
owning class via a per-class scan so controller vs gateway is disambiguated.
Routes are classified by walking the controller's inheritance chain on disk: ActionController::Base ancestor → kind: "page"; ActionController::API ancestor → kind: "api". Resolution memoizes per file with a cycle guard. Also fixes a pre-existing bug where namespace blocks leaked across the whole routes.rb file — namespaces are now tracked block-by-block.
…g) kinds Methods on a class annotated with @controller (without @RestController) emit kind: "page"; @RestController stays kind: "api". @MessageMapping and @SubscribeMapping methods emit a separate websocket endpoint regardless of class kind. Class context is resolved via a back-scan from each mapping match, so inner classes and multi-class files are handled.
Fixture directories under src/extractors/__fixtures__/ contain synthetic test inputs that mimic external frameworks (e.g. @nestjs/common imports without the package being installed). They're scanned as raw source text by the extractors, never compiled or imported. Excluding them from both the TypeScript program and the ESLint config keeps tooling clean without needing per-file @ts-nocheck escape hatches.
The rest of the test suite uses vitest (introduced for the per-extractor fixture tests). Unify on one runner so 'bun run test' covers all suites. Replaces import.meta.dir (bun-only) with the standard ESM fileURLToPath(import.meta.url) pattern.
…r stack The previous heuristic checked a 400-char backward window and a 400-char forward window for @Render. In a controller with multiple methods, that easily included @Render decorators belonging to a sibling method, so a plain @get sitting near a @Render-decorated handler was misclassified as a page. Replace both windows with a precise line-by-line walk that captures only the contiguous decorator stack of the current method — stopping at any line that ends a previous statement (}, ;) or otherwise looks like code. Add a regression test where a @get("/data") sibling sits directly below a @Render-decorated method and asserts its kind is "api".
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Bugbot Autofix is ON. A cloud agent has been kicked off to fix the reported issues.
Reviewed by Cursor Bugbot for commit 712b724. Configure here.
| // Patch the previous class's `end` to be this class's start | ||
| if (classes.length > 0) classes[classes.length - 1]!.end = start; | ||
| classes.push({ start, end: content.length, name, decorators }); | ||
| lastEnd = start; |
There was a problem hiding this comment.
Class decorator attribution includes previous class body
Low Severity
In findClasses, lastEnd = start records the START of each class. For the second class onward, decorators = content.slice(lastEnd, start) captures the entire body of the preceding class (not just the decorators preceding the current class). If a preceding class body contains @Controller("...") or @WebSocketGateway(...) in a string literal, comment, or meta-programming call, the controllerRe / wsGatewayRe exec on c.decorators would falsely match, assigning an incorrect prefix or gateway status to the subsequent class.
Reviewed by Cursor Bugbot for commit 712b724. Configure here.
| ) { | ||
| nsStack.pop(); | ||
| } | ||
| } |
There was a problem hiding this comment.
Rails depth tracking order wrong for combined lines
Low Severity
The depth tracking processes do (increment) before end (decrement) on the same line. If a line contains both keywords (e.g. end; scope do), the namespace popping logic inside the end handler uses a depth that was already incremented by the do on that same line, causing the wrong namespaces to be popped. Ruby semantics process end (closing) before any new block opening.
Reviewed by Cursor Bugbot for commit 712b724. Configure here.


Closes #7. Adds page/websocket kind detection across 9 framework extractors (everything in the issue checklist).
Validated against 60 real-world benchmark apps (argus-validation-benchmarks):
Each framework lands as its own commit with a fixture-driven vitest test under src/extractors/fixtures//. Also ports nextjs.test.ts from bun:test to vitest so the suite runs through one runner.
Note
Medium Risk
Touches core route-extraction heuristics across many frameworks, so misclassification or missed endpoints is possible despite added fixture coverage. No auth/data-handling logic is changed, but parsing changes (especially in
express.tsandrails.ts) may affect endpoint detection accuracy.Overview
Adds
EndpointInfo.kindclassification improvements so extractors can label endpoints asapi,page, orwebsocket(not just defaulting everything to API) across Django, Express, FastAPI, Flask, Go (gin +net/http), Laravel, NestJS, Rails, and Spring.Implements new heuristics per framework (e.g., template-rendering signals, websocket route registries, and framework-specific constructs like Django Channels
websocket_urlpatterns, NestJS@WebSocketGateway/@SubscribeMessage, Spring@MessageMapping, Rails controller inheritance), and tightens route parsing where needed (notably Express route call parsing via balanced-paren scanning).Introduces a fixture-driven Vitest suite (
src/extractors/__fixtures__/**+ new*.test.tsfiles) and wires Vitest intopackage.json/vitest.config.ts; updatesnextjs.test.tsto use Vitest. Tooling is adjusted to ignore/exclude fixtures in ESLint and TypeScript configs.Reviewed by Cursor Bugbot for commit 712b724. Bugbot is set up for automated code reviews on this repo. Configure here.