fix(resolve): exclude empty optional arguments from params#2434
Conversation
✅ Deploy Preview for vue-router canceled.
|
| params = matcher.parse(path)! | ||
| name = matcher.record.name | ||
|
|
||
| matcher?.keys.forEach(key => { |
There was a problem hiding this comment.
Can the ?. be changed to . instead?
There was a problem hiding this comment.
@skirtles-code
Thank you for your comment.
Oh, it was a remnant from writing outside of this block.
Fixed!
66f824f
|
I was wondering about some other edge cases. I'm not sure whether these necessarily impact this PR, but I thought they were worth considered before it's merged. I put together this Playground to demonstrate: These are are
All of them lead to the same path. Clicking around between them, it seems that 2, 3 and 4 are considered equivalent, which makes sense. It's 5 that I found most interesting. It seems to behave like it's a child of 2, 3 and 4. I also tried with Not sure if that's intentional or not, but it doesn't seem to match what's described at https://router.vuejs.org/guide/essentials/active-links.html. I wrote that page, so I may be to blame for describing these classes incorrectly. It seems that there's some special handling for optional |
|
FYI in the upcoming custom resolvers I'm trying to introduce a consistent behaviour. Right now I'm going for this: optional params are always |
66f824f to
012ddc7
Compare
✅ Deploy Preview for vue-router canceled.
|
📝 WalkthroughWalkthroughAdds a catch-all Changes
Sequence Diagram(s)sequenceDiagram
participant Client as RouterLink
participant Router as Router
participant Matcher as Matcher.resolve()
participant View as User
Client->>Router: navigate(to: name or path)
Router->>Matcher: resolve(location)
Matcher-->>Router: matched route + params (omit falsy optional / drop undefined keys)
Router->>View: render component with sanitized params
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
commit: |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
packages/playground/src/router.ts (1)
162-166: Nit:Usercomponent expects anidprop, notpathMatch.
User.vuedestructuresdefineProps<{ id: string }>()and rendersUser: {{ id }}. With this route, the view will always showUser: undefined, which makes the playground less useful for eyeballing the#2419repro. Consider a small inline component (or theGeneric/NotFoundcomponent already imported) that rendersroute.params.pathMatchso the active-link behavior is easier to verify.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/playground/src/router.ts` around lines 162 - 166, The route for path '/features/:pathMatch(.*)*' is using the User component which expects an id prop (defineProps<{ id: string }>()), causing the view to show undefined; update the route definition (the object with name 'features' and component: User) to instead use a component that reads and renders route.params.pathMatch (either replace with the already-imported Generic or NotFound component, or provide a small inline/view component that returns route.params.pathMatch) so the page displays the pathMatch value and preserves active-link verification.packages/router/__tests__/matcher/resolve.spec.ts (1)
697-714: Consider tightening coverage around the new behavior.Two additions would make the regression harder to undo silently:
- A case where the same URL is resolved both ways (by path
/featuresand byname: 'features'with no params or withparams: { pathMatch: '' }) and asserts that the resultingparamsobjects are deep-equal — that is ultimately whatRouterLinkactive-class comparison relies on for#2419.- A repeatable non-splat optional (e.g.
/a/:p*) resolved at/a, to confirm theparams[key.name] === ''check also drops repeatable optionals and doesn’t leak through as''or[].🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/router/__tests__/matcher/resolve.spec.ts` around lines 697 - 714, Add two tighter test cases using assertRecordMatch: (1) resolve the same URL both by path '/features' and by name 'features' with no params and also with params: { pathMatch: '' }, and assert the resulting route objects (especially params) are deep-equal to ensure RouterLink active-class comparisons remain consistent; (2) add a case for a repeatable optional route like { path: '/a/:p*', name: 'a' } resolved at '/a' and assert that params.p is omitted (not '' or []) — use the existing assertRecordMatch helper and the same-style test block as the 'drops missing optional splat when resolving by path' spec to add both assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/playground/src/App.vue`:
- Around line 161-169: The CI failure is due to formatting issues in the
router-link JSX/HTML in App.vue (the <router-link> elements for "Go to Features
(name)", "Go to Features (string)" and "Go to Feature one"); run the formatter
(oxfmt --write) or your project format script (e.g., pnpm format), re-check with
oxfmt --check, and commit the updated formatting changes so the <router-link>
blocks are properly formatted.
In `@packages/router/src/matcher/index.ts`:
- Around line 328-334: The name-based resolution branch is not stripping empty
optional params like the path-based branch does (see matcher.keys.forEach check
that deletes params[key.name] when key.optional and params[key.name] === ''),
causing divergence between path vs name resolution; update the name-based branch
(the code around the existing name-resolution logic) to run the same
empty-optional stripping over its params — iterate matcher.keys and delete
params[key.name] when key.optional && params[key.name] === '' — so both
resolution paths normalize optional empty-string params the same way and keep
isActive comparisons consistent.
---
Nitpick comments:
In `@packages/playground/src/router.ts`:
- Around line 162-166: The route for path '/features/:pathMatch(.*)*' is using
the User component which expects an id prop (defineProps<{ id: string }>()),
causing the view to show undefined; update the route definition (the object with
name 'features' and component: User) to instead use a component that reads and
renders route.params.pathMatch (either replace with the already-imported Generic
or NotFound component, or provide a small inline/view component that returns
route.params.pathMatch) so the page displays the pathMatch value and preserves
active-link verification.
In `@packages/router/__tests__/matcher/resolve.spec.ts`:
- Around line 697-714: Add two tighter test cases using assertRecordMatch: (1)
resolve the same URL both by path '/features' and by name 'features' with no
params and also with params: { pathMatch: '' }, and assert the resulting route
objects (especially params) are deep-equal to ensure RouterLink active-class
comparisons remain consistent; (2) add a case for a repeatable optional route
like { path: '/a/:p*', name: 'a' } resolved at '/a' and assert that params.p is
omitted (not '' or []) — use the existing assertRecordMatch helper and the
same-style test block as the 'drops missing optional splat when resolving by
path' spec to add both assertions.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 3c0c4051-e11f-4077-bdcf-1ac3c6db1847
📒 Files selected for processing (4)
packages/playground/src/App.vuepackages/playground/src/router.tspackages/router/__tests__/matcher/resolve.spec.tspackages/router/src/matcher/index.ts
Document current behavior for the edge cases raised in vuejs#2434 (comment).
Previously passing `{ params: { id: undefined } }` to a route with an
optional param produced a malformed path containing the literal string
"undefined". Align the behavior with omitting the key entirely.
Covers vuejs#2434 (comment).
8f7df00 to
2f1b929
Compare
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/router/__tests__/matcher/resolve.spec.ts (1)
864-877:⚠️ Potential issue | 🟠 MajorUpdate tests to treat empty strings and null as absent optional params, consistent with undefined behavior.
The test "keeps optional params passed as empty strings" (line 864) expects empty string params to be retained in the resolved location, but this conflicts with the path-based resolution logic which explicitly deletes empty string optional params (see matcher resolve line 294-299). To align with undefined behavior and the PR's intent, empty strings should be removed from resolved params.
Additionally,
nullis currently commented out as unsupported despite being allowed byRouteParamValueRawtypes and handled byencodeParam(which returns an empty string for null, identical to undefined behavior). Either test that null is supported and works like undefined, or add an explicit test asserting it's rejected at runtime.Suggested test alignment
- it('keeps optional params passed as empty strings', () => { + it('treats optional params passed as empty strings as absent', () => { assertRecordMatch( { path: '/:a/:b?', name: 'p', components }, { name: 'p', params: { a: 'b', b: '' } }, - { name: 'p', path: '/b', params: { a: 'b', b: '' } }, + { name: 'p', path: '/b', params: { a: 'b' } }, { - params: { a: 'a', b: '' }, + params: { a: 'a' }, path: '/a', matched: [], meta: {}, @@ -906,12 +906,12 @@ describe('LocationAsName', () => { // `#2419` (skirtles-code's comment): `{ params: { id: undefined } }` // should resolve the same as omitting the key entirely so active-link // comparisons stay consistent. it('treats explicit undefined optional params as absent', () => { const record = { path: '/features/:id?', name: 'features', components, } assertRecordMatch( record, { name: 'features', params: { id: undefined } }, { name: 'features', path: '/features', params: {} } ) - // null is rejected by the types and not supported at runtime: - // assertRecordMatch( - // record, - // { name: 'features', params: { id: null } }, - // { name: 'features', path: '/features', params: {} } - // ) + assertRecordMatch( + record, + { name: 'features', params: { id: null } }, + { name: 'features', path: '/features', params: {} } + ) })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/router/__tests__/matcher/resolve.spec.ts` around lines 864 - 877, The test "keeps optional params passed as empty strings" contradicts the matcher resolve behavior that treats empty strings (and encodeParam(null)) as absent optional params; update the test in resolve.spec.ts to expect empty string and null to be removed from resolved params (i.e., optional params treated as absent/undefined), or alternatively add a new test that asserts null is handled like undefined via encodeParam/RouteParamValueRaw; specifically adjust assertions referencing the route record for '/:a/:b?' and the resolved locations so that params { b: '' } (and null) are not preserved, matching the deletion logic in matcher.resolve (and encodeParam behavior).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@packages/router/__tests__/matcher/resolve.spec.ts`:
- Around line 864-877: The test "keeps optional params passed as empty strings"
contradicts the matcher resolve behavior that treats empty strings (and
encodeParam(null)) as absent optional params; update the test in resolve.spec.ts
to expect empty string and null to be removed from resolved params (i.e.,
optional params treated as absent/undefined), or alternatively add a new test
that asserts null is handled like undefined via encodeParam/RouteParamValueRaw;
specifically adjust assertions referencing the route record for '/:a/:b?' and
the resolved locations so that params { b: '' } (and null) are not preserved,
matching the deletion logic in matcher.resolve (and encodeParam behavior).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: bb1ddc13-96ab-4f3c-bd8d-42f919532bbb
📒 Files selected for processing (2)
packages/router/__tests__/matcher/resolve.spec.tspackages/router/src/matcher/index.ts
✅ Files skipped from review due to trivial changes (1)
- packages/router/src/matcher/index.ts
| assertRecordMatch( | ||
| { path: '/features/:pathMatch(.*)*', components, name: 'features' }, | ||
| { path: '/features' }, | ||
| { path: '/features', params: {}, name: 'features' } |
There was a problem hiding this comment.
still unsure if params should be { pathMatch: [] } for convenience since it's repeatable. This is probably not worth changing in the current version but worth changing in the upcoming route resolvers
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #2434 +/- ##
=======================================
Coverage 85.92% 85.93%
=======================================
Files 88 88
Lines 10076 10081 +5
Branches 2315 2317 +2
=======================================
+ Hits 8658 8663 +5
Misses 1407 1407
Partials 11 11 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/router/src/matcher/index.ts`:
- Around line 328-336: Fix the spelling typo in the comment inside the
matcher.keys.forEach block: change "noramlizes" to "normalizes" in the comment
that explains deleting optional params for falsy values; keep the existing logic
referencing params[key.name], key.optional, and parse() unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: fc75b79c-939c-48d5-a0d3-26cf2a09dfd5
📒 Files selected for processing (3)
packages/playground/src/App.vuepackages/router/__tests__/matcher/resolve.spec.tspackages/router/src/matcher/index.ts
✅ Files skipped from review due to trivial changes (1)
- packages/playground/src/App.vue
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/router/tests/matcher/resolve.spec.ts
|
|
||
| // delete all optional params that have falsy values | ||
| // noramlizes '', null, and undefined into deleting the key | ||
| matcher.keys.forEach(key => { | ||
| if (key.optional && !params[key.name]) { | ||
| delete params[key.name] | ||
| } | ||
| }) | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Confirm what parse() returns for a missing repeatable optional segment,
# to validate whether `!params[key.name]` is sufficient today.
sed -n '220,285p' packages/router/src/matcher/pathParserRanker.tsRepository: vuejs/router
Length of output: 2351
Fix typo in comment.
Line 330 has a typo: noramlizes should be normalizes.
The falsy check !params[key.name] is correct as-is. The parse() function returns an empty string '' for missing optional repeatable parameters (never an empty array), so the check safely deletes all three cases ('', null, undefined).
The asymmetry between the path-based and name-based branches for empty optional params (where name-based would keep pathMatch: '') remains a separate concern already noted in prior feedback.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/router/src/matcher/index.ts` around lines 328 - 336, Fix the
spelling typo in the comment inside the matcher.keys.forEach block: change
"noramlizes" to "normalizes" in the comment that explains deleting optional
params for falsy values; keep the existing logic referencing params[key.name],
key.optional, and parse() unchanged.
fix #2419
Removed if optional parameter is empty
Now the param will match if :to is string and if it is object
In the case of string path, it should not be an empty string parameter(Right?)
, so I thought there would be no problem with excluding it.
However, this may be a breaking change if you have users who were using this
In addition, I added a router-link to Playgound to check the operation.
Summary by CodeRabbit
New Features
Bug Fixes
Tests