test(app-router): harden RSC route matching delegation assertion#954
Conversation
commit: |
|
/bigbonk review |
There was a problem hiding this comment.
Clean extraction. The generated entry sheds ~170 lines of inline route-matching logic and delegates to two focused, testable modules. The split between build-time codegen (app-rsc-route-manifest.ts) and runtime matching (app-rsc-route-matching.ts) is well-drawn. Tests cover the key behaviors.
Two minor observations below — neither is blocking.
| type RscRouteManifestCode = { | ||
| readonly routeEntries: readonly string[]; | ||
| rootNotFoundVar: string | null; | ||
| rootForbiddenVar: string | null; |
There was a problem hiding this comment.
Nit: rootNotFoundVar, rootForbiddenVar, rootUnauthorizedVar, and globalErrorVar are mutable here but the sibling array properties (routeEntries, rootLayoutVars) are readonly. Since emitRscRouteManifest returns a fresh object and no caller mutates it, these could be readonly too for consistency.
Not blocking — just a consistency nit.
| }; | ||
|
|
||
| type AppRscMatchableRoute<TPage = unknown, TLayout = unknown> = { | ||
| readonly patternParts: string[]; |
There was a problem hiding this comment.
Worth noting for a future PR: the generated entry still carries its own copy of matchPattern for dynamic metadata route matching (line 819 of the generated output). Both copies are identical in logic. A follow-up could export matchPattern from this module and import it in the generated entry to eliminate the duplication. Not worth doing in this PR since metadata route handling is explicitly out of scope.
|
Review submitted. Here's a summary of what I reviewed and the feedback: Approved — this is a clean refactoring that follows the repo's guidance on keeping generated entries thin. What the PR does well:
Two minor observations (non-blocking):
|
cdcd9aa to
c732b2b
Compare
There was a problem hiding this comment.
Pull request overview
Updates App Router RSC route matching to be delegated to a shared runtime helper and hardens the associated regression tests around “no double decode” behavior.
Changes:
- Add
createAppRscRouteMatcher/matchAppRscRoutePatternruntime helper for RSC route + intercept matching. - Update
generateRscEntryoutput to delegate route/intercept/metadata pattern matching to the helper (and refresh snapshots). - Strengthen tests to assert delegation and ensure the helper does not introduce
decodeURIComponent(...)calls.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/shims.test.ts | Updates the “no double decode” assertion to check delegation + helper source. |
| tests/entry-templates.test.ts | Adds an assertion that generated RSC entry delegates matching to the shared helper. |
| tests/app-rsc-route-matching.test.ts | Adds unit tests for the new shared RSC route matching helper. |
| tests/snapshots/entry-templates.test.ts.snap | Updates snapshots for the new generated entry output. |
| packages/vinext/src/server/app-rsc-route-matching.ts | Introduces the shared runtime helper for RSC route + intercept matching. |
| packages/vinext/src/entries/app-rsc-entry.ts | Switches generated code to use the shared helper for matching. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Verify it does NOT call decodeURIComponent (the comment mentions it but | ||
| // should not have an actual call like `decodeURIComponent(...)`) |
There was a problem hiding this comment.
This test comment says the helper’s comment mentions decodeURIComponent, but app-rsc-route-matching.ts no longer contains that mention. Updating the comment to reflect what’s actually being asserted (no decodeURIComponent( call in the helper source) will avoid confusion for future maintainers.
| // Verify it does NOT call decodeURIComponent (the comment mentions it but | |
| // should not have an actual call like `decodeURIComponent(...)`) | |
| // Verify the helper source does NOT contain a `decodeURIComponent(` call. |
| if (patternPart.endsWith("+")) { | ||
| if (i !== patternParts.length - 1) return null; | ||
| const paramName = patternPart.slice(1, -1); | ||
| const remaining = urlParts.slice(i); | ||
| if (remaining.length === 0) return null; |
There was a problem hiding this comment.
The + catch-all branch triggers on any patternPart ending with +, even if it’s a static segment (e.g. a literal directory name like c++). This makes matchAppRscRoutePattern inconsistent with trie route parsing (see routing/route-trie.ts, which only treats + as special when the segment starts with :) and can cause incorrect intercept/metadata matching for routes whose static segments end in +. Restrict the +/* handling to patternPart.startsWith(":") (otherwise fall through to static equality).
| if (patternPart.endsWith("*")) { | ||
| if (i !== patternParts.length - 1) return null; | ||
| const paramName = patternPart.slice(1, -1); | ||
| params[paramName] = urlParts.slice(i); | ||
| return params; |
There was a problem hiding this comment.
Same as the + case above: the * branch treats any static segment ending in * as an optional catch-all. This can mis-match literal segments (e.g. star*) and diverges from the trie’s :name* convention. Guard this branch with patternPart.startsWith(":") so only :param* is treated as optional catch-all.
c732b2b to
c9c39cd
Compare
|
/bigbonk review |
There was a problem hiding this comment.
Clean follow-up. The test correctly shifts from regex-extracting matchRoute out of a generated string to asserting delegation (createAppRscRouteMatcher import + __routeMatcher.matchRoute(url) call) and then checking the actual helper source for decodeURIComponent. One stale comment nit below — not blocking.
| "utf8", | ||
| ); | ||
| // Verify it does NOT call decodeURIComponent (the comment mentions it but | ||
| // should not have an actual call like `decodeURIComponent(...)`) |
There was a problem hiding this comment.
Nit: this parenthetical is left over from when the generated code had an inline comment mentioning decodeURIComponent. The helper source (app-rsc-route-matching.ts) has no such comment — it just has a plain note about preserving normalized segment bytes. The assertion is still correct (checking for no decodeURIComponent( call), but the explanation is stale.
| // should not have an actual call like `decodeURIComponent(...)`) | |
| // The helper must NOT call decodeURIComponent — the request entry point | |
| // owns decoding; matching preserves already-normalized segment bytes. |
|
Approved with one minor nit. Summary: This is a small, focused follow-up to PR #953. The test previously regex-extracted
The one nit: the comment on line 3609–3610 ("the comment mentions it but should not have an actual call") is stale — the helper source has no such comment. Suggested a clearer replacement that explains the invariant directly. |
What this changes
This updates the RSC route matching regression test now that the route/intercept matching runtime lives in
createAppRscRouteMatcher.Approach
createAppRscRouteMatcher.decodeURIComponent(...)calls so the no-double-decode invariant follows the new ownership boundary.Validation
vp check tests/shims.test.tsvp test run tests/shims.test.ts -t "RSC route matching does not double-decode pathnames"vp test run tests/shims.test.ts tests/app-rsc-route-matching.test.ts tests/entry-templates.test.ts