Skip to content

types(router): add | undefined to optional properties for exactOptionalPropertyTypes#2634

Open
YevheniiKotyrlo wants to merge 1 commit intovuejs:mainfrom
YevheniiKotyrlo:types/exact-optional-property-types
Open

types(router): add | undefined to optional properties for exactOptionalPropertyTypes#2634
YevheniiKotyrlo wants to merge 1 commit intovuejs:mainfrom
YevheniiKotyrlo:types/exact-optional-property-types

Conversation

@YevheniiKotyrlo
Copy link
Contributor

@YevheniiKotyrlo YevheniiKotyrlo commented Feb 24, 2026

Summary

Add | undefined to all optional interface properties across published and internal types. This is a no-op for the default TypeScript configuration but fixes TS2375 errors for consumers with exactOptionalPropertyTypes: true.

Follows the same approach as vuejs/core#12771 which was merged for @vue/runtime-dom.

Zero runtime changes. Zero type assertions.

Changes

  • Add | undefined to optional properties in published interfaces (RouteLocationOptions, _RouteRecordBase, PathParserOptions, etc.)
  • Add | undefined to optional properties in internal/experimental types (unplugin, data-loaders, route-resolver)
  • Fix TrackedRoute.params type from Partial<LocationQuery> to Partial<RouteParamsGeneric> (bug: params are not query values)
  • Add exactOptionalPropertyTypes.test-d.ts with type-level tests

Not in this PR (proposed as separate follow-up)

Enabling exactOptionalPropertyTypes in the tsconfig also requires RouteParamsGeneric to include | undefined for optional repeatable params (ParamValueZeroOrMore<false> = string[] | undefined). This cascades into the matcher and RouterLink — the proper type-level fix is narrowing MatcherLocation.params from RouteParamsGeneric to PathParams after resolution (~8-10 files). I'll propose that as a separate PR.

Not a breaking change

Consumer config Impact
exactOptionalPropertyTypes: false (default) ZeroT | undefined is identical to T for optional properties
exactOptionalPropertyTypes: true Positive — fixes TS2375 errors

Verification

  • build
  • test:types ✓ (0 errors)
  • test:unit ✓ (1490 passed, 19 skipped, 11 todo)

@netlify
Copy link

netlify bot commented Feb 24, 2026

Deploy Preview for vue-router canceled.

Name Link
🔨 Latest commit c1e16a0
🔍 Latest deploy log https://app.netlify.com/projects/vue-router/deploys/69a077e89665c90008f2d965

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 24, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The changes make optional properties throughout the router package explicitly allow undefined as a value, aligning with TypeScript's exactOptionalPropertyTypes configuration. Multiple interfaces and type definitions across routing, typing, and experimental APIs are updated to include | undefined unions on optional fields.

Changes

Cohort / File(s) Summary
Test Updates
packages/router/__tests__/guards/onBeforeRouteUpdate.spec.ts, packages/router/__tests__/matcher/resolve.spec.ts, packages/router/__tests__/utils.ts
Minor logic adjustments (component name assignment, path nullability check) and type signature updates to allow undefined in optional properties (children, redirectedFrom, name).
Experimental Data-Loaders
packages/router/src/experimental/data-loaders/auto-exports.ts, defineColadaLoader.ts, utils.ts
Type widening to allow undefined in optional fields (root option, params property); generalized isSubsetOf function to accept arbitrary record types instead of LocationQuery.
Experimental Route-Resolver
packages/router/src/experimental/route-resolver/matcher-pattern.ts, resolver-fixed.ts
Optional fields (parser, name, path, query, hash) in interfaces widened to explicitly allow undefined type value.
Core Location and Matcher Types
packages/router/src/location.ts, packages/router/src/matcher/index.ts, pathParserRanker.ts
Optional properties (query, hash, aliasOf, sensitive, strict, start, end) updated to allow undefined in type unions.
Scroll Behavior
packages/router/src/scrollBehavior.ts
Type definitions for ScrollPositionCoordinates and _ScrollPositionNormalized updated to allow undefined for behavior property.
Typed Routes
packages/router/src/typed-routes/route-location.ts
RouteLocationAsRelativeGeneric properties (name, params) now explicitly allow undefined as optional property values.
Main Type Definitions
packages/router/src/types/index.ts
RouteLocationOptions and _RouteRecordBase fields (force, state, redirect, beforeEnter) expanded to allow undefined in type unions.
Unplugin Core
packages/router/src/unplugin/core/customBlock.ts, definePage.ts, treeNodeValue.ts
Path property refactored from inherited to explicit top-level property; optional fields (alias, params) widened to allow undefined; nested query param options made explicitly undefined-safe.
Unplugin Options
packages/router/src/unplugin/options.ts
Public option signatures (filePatterns, exclude, autoExportsDataLoaders, paramParsers) updated to allow undefined in type unions.
Type Declaration Tests
packages/router/src/exactOptionalPropertyTypes.test-d.ts
New TypeScript declaration test file validating exactOptionalPropertyTypes constraints across ScrollPositionCoordinates, _ScrollPositionNormalized, _RouteRecordBase, and ResolvedOptions.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Suggested reviewers

  • posva

Poem

🐰 Undefined types now take their flight,
Each optional field shines crystal bright!
With exactOptional's caring hand,
TypeScript stricter, as we planned! ✨
Properties dancing—some, or none—
Type safety conquered, all is done! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding | undefined to optional properties to enable exactOptionalPropertyTypes support in the router package.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 24, 2026

Open in StackBlitz

npm i https://pkg.pr.new/vue-router@2634

commit: c1e16a0

@codecov
Copy link

codecov bot commented Feb 24, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 85.57%. Comparing base (4713069) to head (c1e16a0).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2634      +/-   ##
==========================================
- Coverage   85.58%   85.57%   -0.01%     
==========================================
  Files          86       86              
  Lines        9960     9958       -2     
  Branches     2285     2285              
==========================================
- Hits         8524     8522       -2     
  Misses       1423     1423              
  Partials       13       13              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/router/src/unplugin/core/treeNodeValue.ts (1)

41-44: Redundant field re-declarations — safe to remove.

default and required are already declared in CustomRouteBlockQueryParamOptions with identical types (string | undefined and boolean | undefined respectively). Since RouteRecordOverrideQueryParamOptions does not narrow or widen them, the re-declarations here are purely redundant.

♻️ Proposed cleanup
 export interface RouteRecordOverrideQueryParamOptions extends CustomRouteBlockQueryParamOptions {
-  default?: string | undefined
-  required?: boolean | undefined
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/router/src/unplugin/core/treeNodeValue.ts` around lines 41 - 44,
RouteRecordOverrideQueryParamOptions redundantly re-declares default and
required which are already defined in CustomRouteBlockQueryParamOptions; remove
the duplicate properties from the RouteRecordOverrideQueryParamOptions interface
so it simply extends CustomRouteBlockQueryParamOptions without re-declaring
default or required (locate the RouteRecordOverrideQueryParamOptions interface
in treeNodeValue.ts and delete the two property lines).
packages/router/src/matcher/index.ts (1)

377-400: toPathParams duplicates pickParams logic — consider consolidation.

toPathParams is functionally equivalent to calling pickParams(params, Object.keys(params)). Both filter undefined from MatcherLocation['params'] into PathParams. The only structural difference is that pickParams pre-checks key in params, which is equivalent to for...in on a plain object like the one produced by assign({}, ...).

♻️ Optional consolidation
-function toPathParams(params: MatcherLocation['params']): PathParams {
-  const newParams: PathParams = {}
-  for (const key in params) {
-    const value = params[key]
-    if (value !== undefined) newParams[key] = value
-  }
-  return newParams
-}

Then at the call site (line 331):

-params = toPathParams(assign({}, currentLocation.params, location.params))
+params = pickParams(
+  assign({}, currentLocation.params, location.params),
+  Object.keys(assign({}, currentLocation.params, location.params))
+)

Alternatively, make keys optional in pickParams and default to Object.keys(params) — whichever is cleaner for the codebase.

🤖 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 377 - 400, toPathParams
duplicates pickParams logic; consolidate by removing the duplicate: either
delete toPathParams and replace its callers with pickParams(params,
Object.keys(params)), or change pickParams(params, keys) to accept an optional
keys parameter (keys?: string[]) and default to Object.keys(params) so callers
can simply call pickParams(params). Update all references to toPathParams to use
pickParams and keep the behavior of filtering out undefined values intact
(preserve the existing check for value !== undefined).
🤖 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/experimental/data-loaders/auto-exports.ts`:
- Around line 79-81: The `@ts-expect-error` above the id: transformFilter
assignment can become a CI-breaking error if upstream types converge; update the
comment block immediately above the id: transformFilter line to keep the
existing explanation but append a clear FIXME/TODO that documents why the
directive is present, the expected condition for removal (e.g., when
vite/unplugin StringFilter types align), and optionally a link or issue
reference to track removal; ensure the comment mentions transformFilter and that
the `@ts-expect-error` should be removed once the type gap is closed so future
maintainers know to revisit it.

---

Nitpick comments:
In `@packages/router/src/matcher/index.ts`:
- Around line 377-400: toPathParams duplicates pickParams logic; consolidate by
removing the duplicate: either delete toPathParams and replace its callers with
pickParams(params, Object.keys(params)), or change pickParams(params, keys) to
accept an optional keys parameter (keys?: string[]) and default to
Object.keys(params) so callers can simply call pickParams(params). Update all
references to toPathParams to use pickParams and keep the behavior of filtering
out undefined values intact (preserve the existing check for value !==
undefined).

In `@packages/router/src/unplugin/core/treeNodeValue.ts`:
- Around line 41-44: RouteRecordOverrideQueryParamOptions redundantly
re-declares default and required which are already defined in
CustomRouteBlockQueryParamOptions; remove the duplicate properties from the
RouteRecordOverrideQueryParamOptions interface so it simply extends
CustomRouteBlockQueryParamOptions without re-declaring default or required
(locate the RouteRecordOverrideQueryParamOptions interface in treeNodeValue.ts
and delete the two property lines).

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5f7bbf1 and 81258e8.

📒 Files selected for processing (26)
  • packages/router/__tests__/guards/onBeforeRouteUpdate.spec.ts
  • packages/router/__tests__/matcher/resolve.spec.ts
  • packages/router/__tests__/utils.ts
  • packages/router/src/RouterLink.ts
  • packages/router/src/experimental/data-loaders/auto-exports.ts
  • packages/router/src/experimental/data-loaders/defineColadaLoader.ts
  • packages/router/src/experimental/data-loaders/utils.ts
  • packages/router/src/experimental/route-resolver/matchers/matcher-pattern.ts
  • packages/router/src/experimental/route-resolver/resolver-abstract.ts
  • packages/router/src/experimental/route-resolver/resolver-fixed.ts
  • packages/router/src/experimental/router.ts
  • packages/router/src/location.ts
  • packages/router/src/matcher/index.ts
  • packages/router/src/matcher/pathParserRanker.ts
  • packages/router/src/scrollBehavior.ts
  • packages/router/src/typed-routes/route-location.ts
  • packages/router/src/types/index.ts
  • packages/router/src/unplugin/core/RoutesFolderWatcher.ts
  • packages/router/src/unplugin/core/context.ts
  • packages/router/src/unplugin/core/customBlock.ts
  • packages/router/src/unplugin/core/definePage.ts
  • packages/router/src/unplugin/core/tree.ts
  • packages/router/src/unplugin/core/treeNodeValue.ts
  • packages/router/src/unplugin/core/utils.ts
  • packages/router/src/unplugin/options.ts
  • packages/router/tsconfig.json

@posva
Copy link
Member

posva commented Feb 24, 2026

Thanks a lot! I will have to take a proper look, but ideally there should be no runtime changes. The meta property shouldn't allow undefined as a value, that would make the spread work

@YevheniiKotyrlo YevheniiKotyrlo force-pushed the types/exact-optional-property-types branch 2 times, most recently from 1133bcf to d288538 Compare February 24, 2026 21:11
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/router/src/experimental/route-resolver/resolver-fixed.ts (1)

46-46: ⚠️ Potential issue | 🟡 Minor

query is missing | undefined — inconsistent with the other updated properties.

name, path, and hash all received | undefined to satisfy exactOptionalPropertyTypes: true, but query was not updated. A consumer with that flag enabled who writes record.query = undefined would still get a type error, and subtypes overriding this property to MatcherPatternQuery[] | undefined would fail to extend the base interface.

🛠️ Proposed fix
-  query?: MatcherPatternQuery[]
+  query?: MatcherPatternQuery[] | undefined
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/router/src/experimental/route-resolver/resolver-fixed.ts` at line
46, The optional property "query" is missing an explicit "| undefined" causing
inconsistency with "name", "path", and "hash" under exactOptionalPropertyTypes;
update the property declaration for query (the one currently typed as query?:
MatcherPatternQuery[]) to include undefined (query?: MatcherPatternQuery[] |
undefined) so consumers can assign undefined and subtypes with
MatcherPatternQuery[] | undefined will correctly extend the base type.
packages/router/src/types/index.ts (1)

284-297: ⚠️ Potential issue | 🟡 Minor

component?: RawRouteComponent | null — should also allow undefined for consistency.

In the exactOptionalPropertyTypes changes (commit d288538), this field was expanded to | null, but it's missing | undefined. Other optional properties in this file use only | undefined (e.g., force, state, redirect), while the parallel components field in RouteRecordMultipleViewsWithChildren explicitly includes both | null | undefined. Since normalizeRouteRecord line 429 treats both null and undefined identically via the falsy check record.component && { ... }, this should be:

component?: RawRouteComponent | null | undefined
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/router/src/types/index.ts` around lines 284 - 297, The component
property in RouteRecordSingleViewWithChildren currently allows RawRouteComponent
| null but not undefined; update the type to component?: RawRouteComponent |
null | undefined to match other optional fields and the parallel components
field in RouteRecordMultipleViewsWithChildren, and ensure callers like
normalizeRouteRecord treat null/undefined the same (no behavioral change
required beyond the type widening).
♻️ Duplicate comments (1)
packages/router/src/experimental/data-loaders/auto-exports.ts (1)

79-81: Previously reviewed and resolved.

The @ts-expect-error directive and its accompanying explanation were discussed in a prior review. The approach is intentional: the directive is self-enforcing — if upstream StringFilter types converge, the build will fail and signal its removal. No further action needed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/router/src/experimental/data-loaders/auto-exports.ts` around lines
79 - 81, No change required: retain the `@ts-expect-error` and explanatory comment
above the id: transformFilter assignment in auto-exports.ts — the directive is
intentional and self-enforcing so leave the transformFilter binding and its
comment as-is (it will surface a build failure if upstream StringFilter types
converge and then can be removed).
🧹 Nitpick comments (1)
packages/router/src/experimental/route-resolver/matchers/matcher-pattern.ts (1)

233-239: Type widening for parser is correct; consider a type alias to reduce verbosity.

The explicit | undefined is required by exactOptionalPropertyTypes since MatcherPatternPathDynamic_ParamOptions's first tuple element is parser?: ParamParser<...>. The usage at lines 252 and 272 already guards with parser?.set, so undefined is handled safely.

The annotation is quite unwieldy. A local type alias could improve readability:

♻️ Optional: extract a local type alias
+  type ParserType =
+    | (TParamsOptions & Record<string, MatcherPatternPathDynamic_ParamOptions<any, any>>)[keyof TParamsOptions][0]
+    | undefined
+
   build(
     params: Simplify<ExtractLocationParamTypeFromOptions<TParamsOptions>>
   ): string {
     let paramIndex = 0
     let paramName: keyof TParamsOptions
-    let parser:
-      | (TParamsOptions &
-          Record<
-            string,
-            MatcherPatternPathDynamic_ParamOptions<any, any>
-          >)[keyof TParamsOptions][0]
-      | undefined
+    let parser: ParserType
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/router/src/experimental/route-resolver/matchers/matcher-pattern.ts`
around lines 233 - 239, The current explicit type for the local variable parser
(the union over TParamsOptions & Record<...>[keyof TParamsOptions][0] |
undefined) is correct and must keep the | undefined due to
exactOptionalPropertyTypes and MatcherPatternPathDynamic_ParamOptions' optional
parser, but it's verbose: introduce a local type alias (e.g., type ParamOption =
(TParamsOptions & Record<string, MatcherPatternPathDynamic_ParamOptions<any,
any>>)[keyof TParamsOptions][0] | undefined) and annotate parser with that
alias, leaving the existing parser?.set uses unchanged; this keeps correctness
while improving readability.
🤖 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/types/index.ts`:
- Around line 42-45: The change to RouteParamsGeneric intentionally widens the
value type to include undefined to support exactOptionalPropertyTypes (see
RouteParamsGeneric and exactOptionalPropertyTypes.test-d.ts); add a clear
changelog entry and release notes explaining this breaking change, describe
migration guidance for consumers (e.g., show patterns: filter out undefined
values, use type narrowing or non-null assertions before calling string methods,
or map only defined values), and update any docs/tests that assumed
non-undefined param values to demonstrate the correct handling for optional
repeatable params like [[files]]+.

---

Outside diff comments:
In `@packages/router/src/experimental/route-resolver/resolver-fixed.ts`:
- Line 46: The optional property "query" is missing an explicit "| undefined"
causing inconsistency with "name", "path", and "hash" under
exactOptionalPropertyTypes; update the property declaration for query (the one
currently typed as query?: MatcherPatternQuery[]) to include undefined (query?:
MatcherPatternQuery[] | undefined) so consumers can assign undefined and
subtypes with MatcherPatternQuery[] | undefined will correctly extend the base
type.

In `@packages/router/src/types/index.ts`:
- Around line 284-297: The component property in
RouteRecordSingleViewWithChildren currently allows RawRouteComponent | null but
not undefined; update the type to component?: RawRouteComponent | null |
undefined to match other optional fields and the parallel components field in
RouteRecordMultipleViewsWithChildren, and ensure callers like
normalizeRouteRecord treat null/undefined the same (no behavioral change
required beyond the type widening).

---

Duplicate comments:
In `@packages/router/src/experimental/data-loaders/auto-exports.ts`:
- Around line 79-81: No change required: retain the `@ts-expect-error` and
explanatory comment above the id: transformFilter assignment in auto-exports.ts
— the directive is intentional and self-enforcing so leave the transformFilter
binding and its comment as-is (it will surface a build failure if upstream
StringFilter types converge and then can be removed).

---

Nitpick comments:
In `@packages/router/src/experimental/route-resolver/matchers/matcher-pattern.ts`:
- Around line 233-239: The current explicit type for the local variable parser
(the union over TParamsOptions & Record<...>[keyof TParamsOptions][0] |
undefined) is correct and must keep the | undefined due to
exactOptionalPropertyTypes and MatcherPatternPathDynamic_ParamOptions' optional
parser, but it's verbose: introduce a local type alias (e.g., type ParamOption =
(TParamsOptions & Record<string, MatcherPatternPathDynamic_ParamOptions<any,
any>>)[keyof TParamsOptions][0] | undefined) and annotate parser with that
alias, leaving the existing parser?.set uses unchanged; this keeps correctness
while improving readability.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 81258e8 and 1133bcf.

📒 Files selected for processing (22)
  • packages/router/__tests__/guards/extractComponentsGuards.spec.ts
  • packages/router/__tests__/guards/onBeforeRouteUpdate.spec.ts
  • packages/router/__tests__/matcher/resolve.spec.ts
  • packages/router/__tests__/utils.ts
  • packages/router/src/RouterLink.ts
  • packages/router/src/exactOptionalPropertyTypes.test-d.ts
  • packages/router/src/experimental/data-loaders/auto-exports.ts
  • packages/router/src/experimental/data-loaders/defineColadaLoader.ts
  • packages/router/src/experimental/data-loaders/utils.ts
  • packages/router/src/experimental/route-resolver/matchers/matcher-pattern.ts
  • packages/router/src/experimental/route-resolver/resolver-fixed.ts
  • packages/router/src/location.ts
  • packages/router/src/matcher/index.ts
  • packages/router/src/matcher/pathParserRanker.ts
  • packages/router/src/scrollBehavior.ts
  • packages/router/src/typed-routes/route-location.ts
  • packages/router/src/types/index.ts
  • packages/router/src/unplugin/core/customBlock.ts
  • packages/router/src/unplugin/core/definePage.ts
  • packages/router/src/unplugin/core/treeNodeValue.ts
  • packages/router/src/unplugin/options.ts
  • packages/router/tsconfig.json
💤 Files with no reviewable changes (1)
  • packages/router/tests/guards/extractComponentsGuards.spec.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/router/src/exactOptionalPropertyTypes.test-d.ts
🚧 Files skipped from review as they are similar to previous changes (10)
  • packages/router/tsconfig.json
  • packages/router/src/matcher/pathParserRanker.ts
  • packages/router/tests/utils.ts
  • packages/router/src/unplugin/options.ts
  • packages/router/tests/guards/onBeforeRouteUpdate.spec.ts
  • packages/router/src/experimental/data-loaders/utils.ts
  • packages/router/src/typed-routes/route-location.ts
  • packages/router/src/unplugin/core/customBlock.ts
  • packages/router/src/scrollBehavior.ts
  • packages/router/src/unplugin/core/treeNodeValue.ts

@YevheniiKotyrlo YevheniiKotyrlo force-pushed the types/exact-optional-property-types branch from d288538 to f7afa57 Compare February 26, 2026 12:49
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/router/src/matcher/index.ts (1)

377-400: Consider consolidating the undefined-filtering logic.

Both pickParams and toPathParams have similar logic for filtering out undefined values. While the PR author noted these serve different intents (key selection vs full conversion), the filtering logic is duplicated. An optional refactor could extract the filtering to a shared utility:

function filterUndefined<T>(obj: Record<string, T | undefined>): Record<string, T> {
  const result: Record<string, T> = {}
  for (const key in obj) {
    const value = obj[key]
    if (value !== undefined) result[key] = value
  }
  return result
}

However, given the author's stated intent to keep them separate for call-site clarity and the small code footprint, this is acceptable as-is.

🤖 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 377 - 400, Extract the
duplicated undefined-filtering loop into a shared utility (e.g.,
filterUndefined) and use it inside both pickParams and toPathParams: implement a
function that accepts a Record<string, T | undefined> and returns a
Record<string, T> with keys whose values are not undefined, then have pickParams
call filterUndefined on an object built from the provided keys and params (or
map the selected keys to an object first) and have toPathParams simply return
filterUndefined(params); update the references to use filterUndefined while
preserving the original function names and behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/router/src/matcher/index.ts`:
- Around line 377-400: Extract the duplicated undefined-filtering loop into a
shared utility (e.g., filterUndefined) and use it inside both pickParams and
toPathParams: implement a function that accepts a Record<string, T | undefined>
and returns a Record<string, T> with keys whose values are not undefined, then
have pickParams call filterUndefined on an object built from the provided keys
and params (or map the selected keys to an object first) and have toPathParams
simply return filterUndefined(params); update the references to use
filterUndefined while preserving the original function names and behavior.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d288538 and f7afa57.

📒 Files selected for processing (22)
  • packages/router/__tests__/guards/extractComponentsGuards.spec.ts
  • packages/router/__tests__/guards/onBeforeRouteUpdate.spec.ts
  • packages/router/__tests__/matcher/resolve.spec.ts
  • packages/router/__tests__/utils.ts
  • packages/router/src/RouterLink.ts
  • packages/router/src/exactOptionalPropertyTypes.test-d.ts
  • packages/router/src/experimental/data-loaders/auto-exports.ts
  • packages/router/src/experimental/data-loaders/defineColadaLoader.ts
  • packages/router/src/experimental/data-loaders/utils.ts
  • packages/router/src/experimental/route-resolver/matchers/matcher-pattern.ts
  • packages/router/src/experimental/route-resolver/resolver-fixed.ts
  • packages/router/src/location.ts
  • packages/router/src/matcher/index.ts
  • packages/router/src/matcher/pathParserRanker.ts
  • packages/router/src/scrollBehavior.ts
  • packages/router/src/typed-routes/route-location.ts
  • packages/router/src/types/index.ts
  • packages/router/src/unplugin/core/customBlock.ts
  • packages/router/src/unplugin/core/definePage.ts
  • packages/router/src/unplugin/core/treeNodeValue.ts
  • packages/router/src/unplugin/options.ts
  • packages/router/tsconfig.json
💤 Files with no reviewable changes (1)
  • packages/router/tests/guards/extractComponentsGuards.spec.ts
🚧 Files skipped from review as they are similar to previous changes (10)
  • packages/router/src/experimental/data-loaders/auto-exports.ts
  • packages/router/src/typed-routes/route-location.ts
  • packages/router/src/matcher/pathParserRanker.ts
  • packages/router/tests/matcher/resolve.spec.ts
  • packages/router/tests/utils.ts
  • packages/router/src/unplugin/core/definePage.ts
  • packages/router/src/types/index.ts
  • packages/router/src/unplugin/options.ts
  • packages/router/tsconfig.json
  • packages/router/src/unplugin/core/treeNodeValue.ts

Copy link
Member

@posva posva left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few other places where the runtime is still affected

const spy = vi.fn()
const Component = defineComponent({
name,
...(name != null ? { name } : {}),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this work in Vue?

In this case, I think it's better to have the fallback

Suggested change
...(name != null ? { name } : {}),
name: name || 'no-name',

return newParams
}

function toPathParams(params: MatcherLocation['params']): PathParams {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change to the matcher shouldn't be runtime


return {
behavior: offset.behavior,
...(offset.behavior != null && { behavior: offset.behavior }),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this one shouldn't be needed either

Comment on lines 441 to 449
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't be needed either, the type should be stricter

@YevheniiKotyrlo YevheniiKotyrlo force-pushed the types/exact-optional-property-types branch from f7afa57 to e65e258 Compare February 26, 2026 16:08
@YevheniiKotyrlo YevheniiKotyrlo changed the title types(router): enable exactOptionalPropertyTypes types(router): add | undefined to optional properties for exactOptionalPropertyTypes Feb 26, 2026
@YevheniiKotyrlo YevheniiKotyrlo force-pushed the types/exact-optional-property-types branch from e65e258 to c1e16a0 Compare February 26, 2026 16:42
@YevheniiKotyrlo
Copy link
Contributor Author

YevheniiKotyrlo commented Feb 26, 2026

Thanks for the detailed review! Rebased onto latest main and squashed into a single commit.

Comment 1 — onBeforeRouteUpdate.spec.ts

The test file no longer needs changes in this PR (the name parameter compiles fine without exactOptionalPropertyTypes in tsconfig), so I reverted the test entirely.

Comments 2, 3, 4 — matcher, scrollBehavior, RouterLink

Agreed — removed all runtime changes and type assertions. The current push is strictly | undefined on optional interface properties. Zero runtime impact.

The deeper issue: enabling exactOptionalPropertyTypes in the tsconfig requires RouteParamsGeneric to include | undefined (because ParamValueZeroOrMore<false> = string[] | undefined — optional repeatable params like /:files* produce { files?: string[] | undefined } which must extend RouteParamsGeneric). That cascades into stringify() (expects PathParams without undefined) and includesParams (reads values that could be undefined).

The proper fix is narrowing MatcherLocation.params from RouteParamsGeneric to PathParams after resolution (the path parser never produces undefinedpathParserRanker.ts:233 uses match[i] || ''). I'll propose that as a separate PR since it touches ~8-10 files.

Additional fix

While auditing the types, I noticed TrackedRoute.params in defineColadaLoader.ts was typed as Partial<LocationQuery> instead of Partial<RouteParamsGeneric> — route params are string | string[], not query values (string | null | (string|null)[]). Fixed along with making isSubsetOf generic to support both types.


All quality gates pass:

  • build
  • test:types ✓ (0 errors)
  • test:unit ✓ (1490 passed)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants