feat(graph): add NestJS controller route resolver - #102
Conversation
a652f92 to
99de109
Compare
|
Hi @theDakshJaitly @Yashasvi2229 — this PR has been open since mid-July with no reviews, so I gave it some maintenance:
The implementation targets the frozen Is anything blocking this from review — or would you like changes first? Happy to iterate. (Tracking issue: #98, which this resolves.) |
|
Yeah our bad, got caught up in the new release, sorry for the delay in review We will review asap, thanks for the rebase as well |
99de109 to
972c49c
Compare
|
Thanks for this one @abhinav-phi, and for the rebase. The resolver is additive, detection is solid, and the happy path works end to end: I ran real I then probed the extractor against the decorator forms real NestJS code uses. Three things need fixing before merge. Must fix1. Two routes with the same method + path fail the whole graph buildThe route id is @Controller('users')
export class UsersController {
@Version('1') @Get() findAllV1() {}
@Version('2') @Get() findAllV2() {}
}
2. Non-string
|
| source | derived | expected |
|---|---|---|
@Controller({ path: 'users', version: '1' }) + @Get(':id') |
GET /:id |
GET /users/:id |
same, after an @Controller('admin') class in the file |
GET /admin/:id |
GET /users/:id |
@Controller(USERS_PATH) |
GET /:id |
unknown |
@Controller(['users', 'people']) + @Get() |
GET / |
GET /users, GET /people |
The object form is common (it's how controller versioning and host routing are declared), so please parse { path: '…' }. For the forms that can't be read statically (a constant, an array), skip that controller's routes rather than emitting a wrong path. Either way, every @Controller(...) should reset the prefix so nothing carries over.
3. Commented-out decorators create phantom routes
The regex runs over the raw file text, comments included:
@Controller('users')
export class UsersController {
// @Get('legacy')
@Get(':id')
findOne() {}
}This produces both GET /users/:id and a phantom GET /users/legacy, both pointing at findOne. Blanking comments out before scanning (with spaces, so offsets and line numbers stay correct) fixes this and the block-comment case in #1.
Should fix
4. Two controllers in one file lose their links
Resolution matches the handler by name within the file, so two controllers in one file that both have findAll leave both routes unresolved. I confirmed this in a real build. #98 asks to prefer the class context. The TypeScript extractor already names methods UsersController::findAll, so tracking the class that follows each @Controller and matching on qualifiedName resolves both.
Smaller things
- Parenthesis skipping isn't string-aware.
@ApiOperation({ summary: 'List users :)' })between@Get()and the method silently drops the route. Rare, but it fails without any signal. - Array method paths.
@Get(['list', 'all'])produces no route. - Integration test. The unit tests use a fake context. One test through
rebuildGraph(like the Next.js resolver's in feat(graph): add Next.js App Router route resolver #179) would have caught 1 and 4. - Edge label.
resolvedBy: "framework"/ confidence1doesn't identify the resolver; Express usesexpress-route-handler/0.8for the same evidence. Something likenestjs-route-handlerwould match. - Fixture and header comments. The fixture has leftover notes ("Missing handler name (anonymous function)… let's just make it a normal one"), and the file header mentions a single-controller-per-file assumption the code doesn't make. Worth tidying.
- CHANGELOG. Please add an entry under
## [Unreleased]→### Added, as feat(graph): add Next.js App Router route resolver #179 does.
972c49c to
ad61598
Compare
Review-driven rework of the NestJS route resolver, addressing three must-fix findings from real-build probing: - Duplicate node ids killed whole builds: NestJS versioning (@Version('1') @get() findAllV1 / @Version('2') @get() findAllV2) emits two routes with the same method+path. The handler now rides in the signature (GET /users -> findAllV1) and an ordinal joins the role, mirroring Express, so same-named routes keep distinct ids. - @controller arguments beyond string literals were skipped entirely, so the prefix was lost or inherited from the previous controller. Arguments are now read string-aware (a ')' inside 'List users :)' no longer breaks anything): string form, object form ({ path: ... }), and empty are handled; constants and arrays skip that controller's routes rather than guessing. Every @controller resets the prefix and binds to the next class, so two controllers in one file each keep their own prefix. - Commented-out decorators invented phantom routes. Comments are blanked with spaces before scanning — offsets and line numbers survive, comments stop being code. Resolution now records the owning controller class in the reference candidates and prefers a same-file qualified-name match, so two controllers that both declare findAll no longer leave both routes unresolved. Edge label moves to nestjs-route-handler at confidence 0.8, matching Express's evidence class. Fixture notes tidied; a rebuildGraph integration test covers the versioned-route and comment-decorator cases end to end. Addresses review on mex-memory#102
|
All findings addressed — thanks for probing the decorator forms; every one of these reproduced locally. Must fixes
Should fix 4 — the reference now carries Smaller things — paren skipping is string-aware ( The one deliberate non-change: |
|
Thanks @abhinav-phi, I checked out A few things before merge: 1. Inline decorators now link the wrong handler (regression)The line-based rewrite looks for the handler after the end of the decorator's line, so a decorator written on the same line as its method picks up the next method instead. Real build: @Controller('users')
export class UsersController {
@Get() list() { return []; }
@Get(':id') findOne(@Param('id') id: string) { return { id }; }
}This produces one route, 2.
|
|
Okay... on it... on your orders ma'am 🫡 |
Review-driven rework of the NestJS route resolver, addressing three must-fix findings from real-build probing: - Duplicate node ids killed whole builds: NestJS versioning (@Version('1') @get() findAllV1 / @Version('2') @get() findAllV2) emits two routes with the same method+path. The handler now rides in the signature (GET /users -> findAllV1) and an ordinal joins the role, mirroring Express, so same-named routes keep distinct ids. - @controller arguments beyond string literals were skipped entirely, so the prefix was lost or inherited from the previous controller. Arguments are now read string-aware (a ')' inside 'List users :)' no longer breaks anything): string form, object form ({ path: ... }), and empty are handled; constants and arrays skip that controller's routes rather than guessing. Every @controller resets the prefix and binds to the next class, so two controllers in one file each keep their own prefix. - Commented-out decorators invented phantom routes. Comments are blanked with spaces before scanning — offsets and line numbers survive, comments stop being code. Resolution now records the owning controller class in the reference candidates and prefers a same-file qualified-name match, so two controllers that both declare findAll no longer leave both routes unresolved. Edge label moves to nestjs-route-handler at confidence 0.8, matching Express's evidence class. Fixture notes tidied; a rebuildGraph integration test covers the versioned-route and comment-decorator cases end to end. Addresses review on mex-memory#102
- Inline decorators bound the wrong handler: scanning started at the
decorator line's end, so `@Get() list() {}` picked the NEXT method.
Extraction now starts at the decorator's closing paren (the span
returned by the balanced-args reader), which also keeps the
next-line form working. Repro snippet is now a test.
- `{ path: CONSTANT }` skipped like the bare-constant form (was
emitting an unprefixed route); an object without a `path` key stays
unprefixed.
- Prefix trailing slashes are trimmed before joining, so
`@Controller('/users/')` + `@Get('/:id/')` gives `GET /users/:id`
instead of `GET /users//:id`.
- Backtick paths with `${` interpolation are unreadable → the route is
skipped instead of emitting `GET /users/${BASE}/x`.
- Registry rebased with main's nextjsResolver ([express, nextjs,
nestjs]); CHANGELOG entry added under [Unreleased] → Added.
Resolves review items on mex-memory#102; multi-line controller objects and
array method paths remain documented follow-ups.
ad61598 to
57ebdcb
Compare
|
Round-2 follow-up complete — every item from your review is addressed in 1. Inline decorators linked the wrong handler (regression) — fixed properly: the balanced-args reader now returns the decorator's closing-paren offset, and the handler scan starts there, so 2. 3. Rebase + CHANGELOG — rebased onto current main; the registry keeps all three resolvers ( The smaller items, since they were one-liners: trailing slash on the controller prefix is trimmed before joining ( CI: all checks green on the rebased head (check 22.22.0/24, hub-browser, release-performance, storage-portability macos+windows). Ready for re-review when you get a moment — thanks for the precise probes, the inline-decorator catch in particular was a real regression I introduced. |
Resolve the CHANGELOG conflict by keeping main's 0.8.2 entries and listing the NestJS resolver right after the Next.js one under Added.
What
FrameworkResolverfor decorator-defined HTTP controller routes.@nestjs/coreor@nestjs/commondependencies.@Controller()prefixes and HTTP method decorators such as@Get(),@Post(),@Put(),@Patch(),@Delete(),@Options(),@Head(), and@All().function_refreferences to controller methods.Why
Closes #98.
This teaches the code graph about NestJS controller routing without changing the frozen
FrameworkResolverinterface or graph-core semantics.Scope boundaries
This PR is limited to statically recognizable HTTP controller routes. Dependency-injection edges, guards, pipes, interceptors, middleware, gateways, GraphQL, microservices, and runtime decorator evaluation remain out of scope.
How to test
npm run typecheck npm test npm run buildFocused review should verify: