Skip to content

Conversation

@mdm317
Copy link

@mdm317 mdm317 commented Nov 2, 2025

fixes: #3011

  • Added edit and preview routes to improve testing coverage.
  • Replaced union with intersection type for more accurate type inference.

Summary by CodeRabbit

  • New Features

    • Added post edit and preview routes with corresponding path variants.
  • Tests

    • Expanded coverage to include edit/preview paths, trailing-slash variants, and parent-relative resolution across unions.
    • Added a test for resolving parent-relative paths when routes are combined.
  • Refactor

    • Improved internal type utilities for more robust relative-path resolution in the router.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 2, 2025

Walkthrough

Adds two new test-only routes (postEditRoute, postPreviewRoute) and updates route trees and many type assertions; introduces type utilities ToObject/UnObject and changes link type logic to intersect relative to paths when from is a union.

Changes

Cohort / File(s) Change Summary
Test route definitions and assertions
packages/react-router/tests/link.test-d.tsx
Added postEditRoute and postPreviewRoute, nested them under postRoute in both tuple/object route trees, expanded expected path variants (including /posts/$postId/edit and /posts/$postId/preview and trailing-slash forms), and added a test for parent-relative resolution when from is a union.
Link path resolution types
packages/router-core/src/link.ts
Added WrapRelativeToPathForIntersection and changed RelativeToParentPath to use UnionToIntersection + UnObject of wrapped objects to enforce intersection semantics for relative to paths when from is a union; added imports for ToObject, UnObject, UnionToIntersection.
Type utilities
packages/router-core/src/utils.ts
Added UnObject<T> (unwraps object value types) and ToObject<T> (wraps a type as a string-indexed object) to support the intersection-based transformation.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Pay attention to: correctness of UnionToIntersection usage in RelativeToParentPath, edge cases for ResolveRelativePath interactions, and the new tests exercising union→intersection behavior.

Possibly related PRs

Suggested reviewers

  • schiller-manuel

Poem

🐰 A rabbit hops through routes anew,
Edit and preview in the route-tree view.
Wrapping types, intersecting the ways,
Links now resolve in safer arrays.
Cheers — a tiny hop for type-safety today!

Pre-merge checks and finishing touches

✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "fix(types): enforce intersection of relative to paths when from is a union" is concise, specific, and directly aligns with the main change in the PR. It clearly communicates that this is a type fix focused on enforcing intersection behavior for relative paths in union scenarios. The title uses appropriate conventional format ("fix(types)") and accurately conveys the core objective without being vague or overly broad.
Linked Issues Check ✅ Passed The code changes directly address the requirements of issue #3011. The PR implements the core objective by modifying the RelativeToParentPath type to use UnionToIntersection along with new utility types (WrapRelativeToPathForIntersection, ToObject, UnObject) to enforce that relative to paths are limited to those valid for all members of a from union. The test file expansion validates this intersection behavior with new route definitions and type expectations covering parent-relative path resolution scenarios, confirming that the type-level solution prevents invalid links that would be allowed by some from alternatives but not all.
Out of Scope Changes Check ✅ Passed All changes in the PR are directly related to resolving issue #3011. The utility type additions in utils.ts (UnObject and ToObject) are necessary infrastructure for the intersection-based type logic. The type modifications in link.ts implement the core fix using UnionToIntersection. The test file changes add comprehensive coverage for the new behavior with route definitions and expanded type assertions. No extraneous or unrelated modifications are present.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 294f4a0 and b866bed.

📒 Files selected for processing (1)
  • packages/router-core/src/utils.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/router-core/src/utils.ts

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.

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: 0

🧹 Nitpick comments (2)
packages/router-core/src/utils.ts (1)

148-150: Consider adding JSDoc comments to clarify the purpose of these utility types.

These types implement a clever pattern to convert unions to intersections (when combined with UnionToIntersection), but their purpose isn't immediately obvious from the names alone. A brief comment explaining the usage pattern would help maintainability.

Example:

/**
 * Wraps a type T in an object with property 'foo'.
 * Used with UnObject and UnionToIntersection for union-to-intersection conversion.
 */
export type ToObject<T> = { foo: T }

/**
 * Unwraps a type from an object with property 'foo'.
 * Used with ToObject and UnionToIntersection for union-to-intersection conversion.
 */
export type UnObject<T> = T extends { foo: any } ? T["foo"] : never
packages/router-core/src/link.ts (1)

236-254: Implementation correctly enforces intersection semantics for union-based navigation.

The type-level logic properly converts union-typed from paths into intersection-typed to paths, ensuring relative navigation is valid for all route alternatives.

Consider removing the commented-out code on line 255:

-  // RelativeToPath<TRouter, TTo, TResolvedPath>

While it provides context during review, commented-out code is generally avoided in production to reduce noise.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c4d01dd and 294f4a0.

📒 Files selected for processing (3)
  • packages/react-router/tests/link.test-d.tsx (43 hunks)
  • packages/router-core/src/link.ts (2 hunks)
  • packages/router-core/src/utils.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use TypeScript in strict mode with extensive type safety across the codebase

Files:

  • packages/react-router/tests/link.test-d.tsx
  • packages/router-core/src/utils.ts
  • packages/router-core/src/link.ts
packages/{react-router,solid-router}/**

📄 CodeRabbit inference engine (AGENTS.md)

Implement React and Solid bindings/components only in packages/react-router/ and packages/solid-router/

Files:

  • packages/react-router/tests/link.test-d.tsx
packages/router-core/**

📄 CodeRabbit inference engine (AGENTS.md)

Keep framework-agnostic core router logic in packages/router-core/

Files:

  • packages/router-core/src/utils.ts
  • packages/router-core/src/link.ts
🧠 Learnings (7)
📓 Common learnings
Learnt from: nlynzaad
Repo: TanStack/router PR: 5182
File: e2e/react-router/basic-file-based/src/routes/non-nested/named/$baz_.bar.tsx:3-5
Timestamp: 2025-09-22T00:56:49.237Z
Learning: In TanStack Router, underscores are intentionally stripped from route segments (e.g., `$baz_` becomes `baz` in generated types) but should be preserved in base path segments. This is the correct behavior as of the fix in PR #5182.
📚 Learning: 2025-10-08T08:11:47.088Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5402
File: packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts:19-21
Timestamp: 2025-10-08T08:11:47.088Z
Learning: Test snapshot files in the router-generator tests directory (e.g., files matching the pattern `packages/router-generator/tests/generator/**/routeTree*.snapshot.ts` or `routeTree*.snapshot.js`) should not be modified or have issues flagged, as they are fixtures used to verify the generator's output and are intentionally preserved as-is.

Applied to files:

  • packages/react-router/tests/link.test-d.tsx
📚 Learning: 2025-10-14T18:59:33.990Z
Learnt from: FatahChan
Repo: TanStack/router PR: 5475
File: e2e/react-start/basic-prerendering/src/routes/redirect/$target/via-beforeLoad.tsx:8-0
Timestamp: 2025-10-14T18:59:33.990Z
Learning: In TanStack Router e2e test files, when a route parameter is validated at the route level (e.g., using zod in validateSearch or param validation), switch statements on that parameter do not require a default case, as the validation ensures only expected values will reach the switch.

Applied to files:

  • packages/react-router/tests/link.test-d.tsx
📚 Learning: 2025-09-23T17:36:12.598Z
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-23T17:36:12.598Z
Learning: Applies to packages/{react-router,solid-router}/** : Implement React and Solid bindings/components only in packages/react-router/ and packages/solid-router/

Applied to files:

  • packages/react-router/tests/link.test-d.tsx
📚 Learning: 2025-10-01T18:31:35.420Z
Learnt from: schiller-manuel
Repo: TanStack/router PR: 5330
File: e2e/react-start/custom-basepath/src/routeTree.gen.ts:58-61
Timestamp: 2025-10-01T18:31:35.420Z
Learning: Do not review files named `routeTree.gen.ts` in TanStack Router repositories, as these are autogenerated files that should not be manually modified.

Applied to files:

  • packages/react-router/tests/link.test-d.tsx
📚 Learning: 2025-10-01T18:30:26.591Z
Learnt from: schiller-manuel
Repo: TanStack/router PR: 5330
File: packages/router-core/src/router.ts:2231-2245
Timestamp: 2025-10-01T18:30:26.591Z
Learning: In `packages/router-core/src/router.ts`, the `resolveRedirect` method intentionally strips the router's origin from redirect URLs when they match (e.g., `https://foo.com/bar` → `/bar` for same-origin redirects) while preserving the full URL for cross-origin redirects. This logic should not be removed or simplified to use `location.publicHref` directly.

Applied to files:

  • packages/router-core/src/link.ts
📚 Learning: 2025-09-23T17:36:12.598Z
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-23T17:36:12.598Z
Learning: Applies to packages/router-core/** : Keep framework-agnostic core router logic in packages/router-core/

Applied to files:

  • packages/router-core/src/link.ts
🧬 Code graph analysis (2)
packages/react-router/tests/link.test-d.tsx (1)
packages/react-router/src/link.tsx (1)
  • Link (567-598)
packages/router-core/src/link.ts (1)
packages/router-core/src/utils.ts (3)
  • ToObject (149-149)
  • UnObject (148-148)
  • UnionToIntersection (142-146)
🔇 Additional comments (4)
packages/router-core/src/link.ts (1)

26-28: LGTM! Imports are correct for the intersection-based type construction.

These utilities are properly used in the new WrapRelativeToPathForIntersection type and the updated RelativeToParentPath type.

packages/react-router/tests/link.test-d.tsx (3)

43-52: LGTM! New test routes properly expand coverage for the intersection fix.

The postEditRoute and postPreviewRoute provide good test cases for validating that relative navigation from a union of routes enforces intersection semantics.


113-117: Route tree structures correctly updated.

Both routeTreeTuples and routeTreeObjects properly include the new routes with correct parent-child relationships.

Also applies to: 134-141


2165-2177: Excellent test case! This directly validates the fix for issue #3011.

This test ensures that when navigating with a relative path from a union of routes, only paths valid for ALL union members are allowed. The expected type '..' | '../..' | '../edit' | undefined represents the intersection of valid navigation targets from both /invoices/$invoiceId/details and /posts/$postId/preview.

? T
: never

export type UnObject<T> = T extends { foo: any } ? T["foo"] : never
Copy link
Contributor

Choose a reason for hiding this comment

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

foo?

Copy link
Author

Choose a reason for hiding this comment

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

Thanks for the review!
I’ve removed the use of the foo property.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A from union should result in a intersection of relative to paths

2 participants