Skip to content

feat: cleaner git diffs #698

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open

Conversation

Anoesj
Copy link
Contributor

@Anoesj Anoesj commented Aug 13, 2025

Every RouteRecordInfo entry is now multiline. The child routes are always set and written over multiple lines using pipes (|), and I now explicitly set | never too to prevent annoying Git diffs when for example this...

export interface RouteNamedMap {
  '/[a]': RouteRecordInfo<
    '/[a]',
    '/:a',
    { a: ParamValue<true> },
    { a: ParamValue<false> }
  >,
}

...is changed to this in any commit

export interface RouteNamedMap {
  '/[a]': RouteRecordInfo<
    '/[a]',
    '/:a',
    { a: ParamValue<true> },
    { a: ParamValue<false> },
    | '/[a]/[b]'
  >,
}

With | never explicitly set as the fifth generic, only one line is changed in the Git diff instead of two.

In _RouteFileInfoMap, both views and routes are now multiline unions with pipes:

export interface _RouteFileInfoMap {
  'src/pages/parent.vue': {
    routes:
      | '/parent'
      | '/parent/child'
    views:
      | 'default'
      | 'test'
  }
  'src/pages/parent/child.vue': {
    routes:
      | '/parent/child'
    views:
      | never
  }
  'src/pages/parent/[email protected]': {
    routes:
      | '/parent/child'
    views:
      | never
  }
}
Full example output
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// Generated by unplugin-vue-router. ‼️ DO NOT MODIFY THIS FILE ‼️
// It's recommended to commit this file.
// Make sure to add this file to your tsconfig.json file as an "includes" or "files" entry.

declare module 'vue-router/auto-routes' {
  import type {
    RouteRecordInfo,
    ParamValue,
    ParamValueOneOrMore,
    ParamValueZeroOrMore,
    ParamValueZeroOrOne,
  } from 'vue-router'

  /**
   * Route name map generated by unplugin-vue-router
   */
  export interface RouteNamedMap {
    '/': RouteRecordInfo<
      '/',
      '/',
      Record<never, never>,
      Record<never, never>,
      | never
    >,
    '/suppliers/': RouteRecordInfo<
      '/suppliers/',
      '/suppliers',
      Record<never, never>,
      Record<never, never>,
      | never
    >,
    '/suppliers/[id]': RouteRecordInfo<
      '/suppliers/[id]',
      '/suppliers/:id',
      { id: ParamValue<true> },
      { id: ParamValue<false> },
      | '/suppliers/[id]/'
      | '/suppliers/[id]/products/[[product]]+'
      | '/suppliers/[id]/results/[[version]]'
    >,
    '/suppliers/[id]/': RouteRecordInfo<
      '/suppliers/[id]/',
      '/suppliers/:id',
      { id: ParamValue<true> },
      { id: ParamValue<false> },
      | never
    >,
    '/suppliers/[id]/products/[[product]]+': RouteRecordInfo<
      '/suppliers/[id]/products/[[product]]+',
      '/suppliers/:id/products/:product*',
      { id: ParamValue<true>, product?: ParamValueZeroOrMore<true> },
      { id: ParamValue<false>, product?: ParamValueZeroOrMore<false> },
      | never
    >,
    '/suppliers/[id]/results/[[version]]': RouteRecordInfo<
      '/suppliers/[id]/results/[[version]]',
      '/suppliers/:id/results/:version?',
      { id: ParamValue<true>, version?: ParamValueZeroOrOne<true> },
      { id: ParamValue<false>, version?: ParamValueZeroOrOne<false> },
      | never
    >,
  }

  /**
   * Route file to route info map by unplugin-vue-router.
   * Used by the volar plugin to automatically type useRoute()
   *
   * Each key is a file path relative to the project root with 2 properties:
   * - routes: union of route names of the possible routes when in this page (passed to useRoute<...>())
   * - views: names of nested views (can be passed to <RouterView name="...">)
   *
   * @internal
   */
  export interface _RouteFileInfoMap {
    'src/pages/index.vue': {
      routes:
        | '/'
      views:
        | never
    }
    'src/pages/suppliers/index.vue': {
      routes:
        | '/suppliers/'
      views:
        | never
    }
    'src/pages/suppliers/[id].vue': {
      routes:
        | '/suppliers/[id]'
        | '/suppliers/[id]/'
        | '/suppliers/[id]/products/[[product]]+'
        | '/suppliers/[id]/results/[[version]]'
      views:
        | 'default'
    }
    'src/pages/suppliers/[id]/index.vue': {
      routes:
        | '/suppliers/[id]/'
      views:
        | never
    }
    'src/pages/suppliers/[id]/products/[[product]]+.vue': {
      routes:
        | '/suppliers/[id]/products/[[product]]+'
      views:
        | never
    }
    'src/pages/suppliers/[id]/results/[[version]].vue': {
      routes:
        | '/suppliers/[id]/results/[[version]]'
      views:
        | never
    }
  }

  /**
   * Get a union of possible route names in a certain route component file.
   * Used by the volar plugin to automatically type useRoute()
   *
   * @internal
   */
  export type _RouteNamesForFilePath<FilePath extends string> =
    _RouteFileInfoMap extends Record<FilePath, infer Info>
      ? Info['routes']
      : keyof RouteNamedMap
}

To do

  • Determine definitive notation
  • Rebase/revisit related PRs if this is merged first (todo for myself)

Summary by CodeRabbit

  • New Features
    • Route typings now include a fifth parameter encoding child/nested routes, improving editor hints and type safety (no runtime change).
  • Documentation
    • Docs and examples updated to reflect the revised route typings.
  • Style
    • Generated type unions rendered as multiline unions for improved readability.
  • Tests
    • Snapshots and expectations updated to match the new 5-arity typings and multiline formatting.
  • Chores
    • Small utility helpers and a naming/type-guard addition support safer name handling and formatting.

Copy link

coderabbitai bot commented Aug 13, 2025

Walkthrough

Adds a fifth generic parameter to RouteRecordInfo to represent child-route unions and switches emitted unions to multiline (leading-pipe) formatting across codegen, docs, playground, tests, and utils. Introduces TreeNodeNamed/isNamed and helper formatting utilities. No runtime behavior changes. (≤50 words)

Changes

Cohort / File(s) Summary
Docs: typed route examples
docs/.vitepress/twoslash-files.ts, docs/.vitepress/code/typed-router.ts, docs/guide/typescript.md, docs/introduction.md
Update example/augmented RouteNamedMap entries to include the new fifth RouteRecordInfo generic; child-route literals shown as union forms with a leading `
Playground declarations
playground/typed-router.d.ts, playground/src/router.ts
Regenerated d.ts and manual augmentation: RouteRecordInfo entries now include the fifth generic; children expressed as multiline unions (e.g., `
Codegen: generators
src/codegen/generateRouteMap.ts, src/codegen/generateRouteFileInfoMap.ts, src/codegen/generateRouteRecords.ts, src/codegen/generateDTS.ts
Emit fifth generic (children) in RouteRecordInfo, switch routes/views/children unions to multiline via helpers, replace inline indentation with pad, and adjust named-route detection to use isNamed()/TreeNodeNamed. generateDTS adds vueRouterModule param to signature typing.
Codegen: tests & snapshots
src/codegen/generateRouteMap.spec.ts, src/codegen/generateRouteFileInfoMap.spec.ts
Update inline snapshots and assertions to expect 5-arity RouteRecordInfo, multiline union formatting, and apply normalization for stable union ordering.
Core tree types & tests
src/core/tree.ts, src/core/tree.spec.ts
Add exported TreeNodeNamed type alias and isNamed(): this is TreeNodeNamed type guard; extend tests to assert naming-state behavior.
Utilities added/changed
src/utils/index.ts
Add and export pad(spaces, str), formatMultilineUnion(items, spaces), and stringToStringType(str) used by codegen.
Docs/site twoslash examples
docs/.vitepress/twoslash-files.ts
Updated emitted sample typings to reflect new fifth generic and multiline union formatting.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • posva

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

pkg-pr-new bot commented Aug 13, 2025

Open in StackBlitz

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

commit: c9552a5

Copy link

codecov bot commented Aug 13, 2025

Codecov Report

❌ Patch coverage is 96.42857% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 61.24%. Comparing base (44f833b) to head (c9552a5).

Files with missing lines Patch % Lines
src/codegen/generateDTS.ts 0.00% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #698      +/-   ##
==========================================
+ Coverage   60.90%   61.24%   +0.33%     
==========================================
  Files          36       36              
  Lines        3379     3406      +27     
  Branches      618      623       +5     
==========================================
+ Hits         2058     2086      +28     
+ Misses       1314     1313       -1     
  Partials        7        7              

☔ 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

@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 (5)
playground/typed-router.d.ts (3)

287-293: Use plain never for the 5th generic; it keeps intent clear

For RouteRecordInfo’s 5th generic, using plain never for leaves is idiomatic and still yields a one-line diff when a child is later added (switching to a piped union on that single line). I’d keep it as is.


462-465: Consider piping never in multiline union fields to stabilize future diffs

For _RouteFileInfoMap’s union-shaped fields (routes/views), using a piped never maintains the same block shape when going from no entries to one entry (only one line changes). Example:

-      views: never
+      views:
+        | never

If you agree, apply this pattern to all views (and any similar multiline union fields) in the generator.


454-461: Generator alignment: use the same block style for views everywhere

There are many views: never occurrences. If you switch to a piped never format in the generator, all these spots will benefit from cleaner future diffs. No need to hand-edit this file; just adjust the codegen once.

Also applies to: 466-471, 472-477, 478-483, 484-488, 489-493, 494-498, 499-503, 504-508, 509-513, 514-518, 519-523, 532-536, 537-541, 542-546, 547-551, 552-556, 557-562, 563-567, 568-572, 573-577, 578-582, 583-587, 588-592, 593-597, 598-602, 603-607, 608-612, 613-617, 618-622, 623-627, 628-632, 633-637, 638-642, 643-647, 648-652, 653-657, 658-666, 667-671, 672-676, 677-681, 682-686, 687-691, 692-696, 697-701, 702-706, 707-711, 712-716, 717-721, 722-726, 727-731, 732-736, 737-741, 742-746, 747-751, 752-756, 757-761, 762-776, 777-781

src/codegen/generateRouteMap.spec.ts (2)

700-704: Remove debug logging from tests

console.log in snapshots adds noise to CI logs. Remove it.

-    console.log(result1.replaceAll(/\n\s+\|/g, ' |'))
     // Verify the union type is alphabetically sorted
     expect(result1.replaceAll(/\n\s+\|/g, ' |')).toContain(
       "| '/parent/alpha' | '/parent/beta' | '/parent/zebra'"
     )

696-704: Optional: factor the union flattener into a small helper

If you keep normalizing unions in more tests, extract the replaceAll to a helper for clarity.

   const result1 = createTree(order1)
   const result2 = createTree(order2)
   const result3 = createTree(order3)

   // All should be identical due to stable sorting
   expect(result1).toBe(result2)
   expect(result2).toBe(result3)

-  // Verify the union type is alphabetically sorted
-  expect(result1.replaceAll(/\n\s+\|/g, ' |')).toContain(
+  const flat = (s: string) => s.replaceAll(/\n\s+\|/g, ' |')
+  // Verify the union type is alphabetically sorted
+  expect(flat(result1)).toContain(
     "| '/parent/alpha' | '/parent/beta' | '/parent/zebra'"
   )
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 44f833b and fe29fc6.

📒 Files selected for processing (7)
  • docs/.vitepress/twoslash-files.ts (1 hunks)
  • docs/guide/typescript.md (1 hunks)
  • playground/typed-router.d.ts (2 hunks)
  • src/codegen/generateRouteFileInfoMap.spec.ts (4 hunks)
  • src/codegen/generateRouteFileInfoMap.ts (1 hunks)
  • src/codegen/generateRouteMap.spec.ts (20 hunks)
  • src/codegen/generateRouteMap.ts (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
playground/typed-router.d.ts (1)
src/codegen/generateRouteParams.ts (4)
  • ParamValue (55-57)
  • ParamValueOneOrMore (30-33)
  • ParamValueZeroOrMore (39-41)
  • ParamValueZeroOrOne (47-49)
src/codegen/generateRouteFileInfoMap.ts (3)
client.d.ts (1)
  • routes (7-7)
src/core/extendRoutes.ts (2)
  • name (88-90)
  • name (95-97)
src/core/tree.ts (1)
  • name (238-245)
🔇 Additional comments (14)
src/codegen/generateRouteFileInfoMap.ts (1)

45-46: Consistent multiline formatting for routes and views.

The new multiline union formatting with pipe-prefixed entries improves diff readability. The conditional handling correctly keeps never on a single line while formatting multiple values with leading pipes.

src/codegen/generateRouteMap.ts (4)

30-31: Initialize child routes variable.

Clean initialization of childRouteNames with never as the default value.


43-46: Generate multiline union for child routes.

The formatting correctly creates pipe-prefixed multiline unions for child routes, consistent with the PR's objective of cleaner Git diffs.


49-49: Add fifth type parameter for child routes.

Correctly appends the child routes as the fifth generic parameter to RouteRecordInfo.


51-51: Format type parameters on separate lines.

The multiline formatting with proper indentation produces cleaner, more readable TypeScript output that will generate better Git diffs.

src/codegen/generateRouteFileInfoMap.spec.ts (2)

26-27: Test snapshots updated for single-route multiline format.

The snapshots correctly expect even single routes to be formatted as multiline unions with pipe prefixes, maintaining consistency with the new formatting approach.

Also applies to: 31-32, 36-37, 41-42


57-62: Test snapshots updated for multi-route and view unions.

The snapshots correctly reflect the new multiline union formatting for both routes and views, with proper pipe prefixing and indentation.

Also applies to: 64-65, 81-86, 88-90, 94-95, 115-117, 121-123

docs/guide/typescript.md (1)

66-66: Documentation updated to show pipe-prefixed union syntax.

The documentation correctly demonstrates the new union format for child routes using the pipe prefix, consistent with the generated code changes.

docs/.vitepress/twoslash-files.ts (1)

34-34: Twoslash example updated to pipe-prefixed union format.

The example correctly demonstrates the new fifth type parameter format using the pipe-prefixed union syntax for child routes.

playground/typed-router.d.ts (1)

21-27: 5th generic addition and multiline unions: looks good

RouteRecordInfo’s 5th type parameter (children) is correctly introduced and the multiline rendering is clear and consistent.

src/codegen/generateRouteMap.spec.ts (4)

24-31: Snapshots correctly reflect the 5th generic and multiline formatting

The updated snapshots accurately capture the new children parameter and the piped-union layout. Good coverage on root and static paths.

Also applies to: 31-52


368-399: Nested index case looks correct

The parent has a children union and both index routes are leaves with never. Matches the intended behavior.


410-427: Parent path override test is precise

Children union and overridden path are asserted correctly. This guards against regressions in path overrides.


741-773: Children union filtering test reads well

Empty-name children are excluded from the parent’s children union; assertions are clear and specific.

@Anoesj Anoesj force-pushed the feat/cleaner-git-diffs branch from fe29fc6 to 0841939 Compare August 13, 2025 16:09
Copy link

@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 (3)
src/codegen/generateRouteMap.ts (2)

30-31: Default to never is fine; consider | never if you want single-line diffs when first child is added

Using never keeps the output cleaner. If you want to avoid the two-line change when adding the first child, emit | never instead to preserve the multiline-with-leading-pipe shape from the start.

Apply if desired:

-  let childRouteNames = 'never'
+  let childRouteNames = '| never'

43-45: Extract “leading-pipe union” formatting into a small helper to keep codegen consistent

This formatting logic appears in multiple generators. Centralizing it reduces drift and makes indentation changes trivial.

Use a helper:

// place near the top of the file (or in a shared util)
function formatLeadingPipeUnion(items: string[], indent = 4): string {
  const pad = ' '.repeat(indent)
  return items.map((s) => `| ${s}`).join('\n' + pad)
}

Then update here:

-      childRouteNames = deepNamedChildren
-        .map((childRouteName) => `| ${childRouteName}`)
-        .join('\n    ')
+      childRouteNames = formatLeadingPipeUnion(deepNamedChildren, 4)
playground/typed-router.d.ts (1)

454-460: Multiline unions for routes/views reduce churn; consider decision on never shape

The consistent “leading-pipe” shape for unions is good. Decide whether views: never should become:

  • views: never (current, clean), or
  • views:
    | never
    to keep the shape identical and minimize line changes when adding the first view.

If you want to switch to the multiline shape, the generator can emit | never by default. Would you like me to prepare the patch across the file-info generator as well?

Also applies to: 471-477, 524-531, 658-666

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fe29fc6 and 0841939.

📒 Files selected for processing (5)
  • docs/.vitepress/twoslash-files.ts (1 hunks)
  • docs/guide/typescript.md (1 hunks)
  • playground/typed-router.d.ts (2 hunks)
  • src/codegen/generateRouteMap.spec.ts (20 hunks)
  • src/codegen/generateRouteMap.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • docs/.vitepress/twoslash-files.ts
  • docs/guide/typescript.md
  • src/codegen/generateRouteMap.spec.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
playground/typed-router.d.ts (1)
src/codegen/generateRouteParams.ts (4)
  • ParamValue (55-57)
  • ParamValueOneOrMore (30-33)
  • ParamValueZeroOrMore (39-41)
  • ParamValueZeroOrOne (47-49)
🔇 Additional comments (4)
src/codegen/generateRouteMap.ts (1)

49-52: Multiline RouteRecordInfo formatting reads well and is stable

The indentation and comma placement produce tidy, low-churn diffs. Sorting child names ensures stable output.

playground/typed-router.d.ts (3)

21-28: RouteRecordInfo now carries child-route relationships cleanly

The 5th type arg is emitted as either never or a leading-pipe union. This matches the PR’s goals and keeps diffs stable.

Also applies to: 35-42


287-299: Representative example with a single child looks correct

Leading-pipe union under the 5th generic parameter parses fine and matches the intended nest semantics.


21-441: Leading-pipe ‘|’ syntax supported since TS 2.1.3
TypeScript has accepted a leading |/& for unions/intersections since November 2016 (TS 2.1.3/2.1.4). If your codebase and tooling (Volar, tsserver, etc.) target TS >= 2.1.3, the generated leading pipes are safe. Otherwise, consider removing the leading |.

  • Please verify your minimum TypeScript version (and any consumers’ tooling) is >= 2.1.3.

Copy link

@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 (5)
src/utils/index.ts (2)

24-26: Prefer a simpler, safer pad implementation

Using padStart works but is a bit opaque and can behave unexpectedly with negative indents. A direct space repeat is clearer and guards negatives.

-export function pad(indent: number, str = ''): string {
-  return str.padStart(str.length + indent)
-}
+export function pad(indent: number, str = ''): string {
+  const n = Math.max(0, indent)
+  return ' '.repeat(n) + str
+}

34-36: Guard empty unions to keep output stable

If items is ever empty, this returns an empty string. Given the PR’s goal (stable diffs and explicit never), consider defaulting to a single never line.

-export function formatMultilineUnion(items: string[], indent: number): string {
-  return items.map((s) => `| ${s}`).join(`\n${pad(indent)}`)
-}
+export function formatMultilineUnion(items: string[], indent: number): string {
+  const list = items.length ? items : ['never']
+  return list.map((s) => `| ${s}`).join(`\n${pad(indent)}`)
+}

Can you confirm callers never pass an empty array today (e.g., for views/routes unions)? If they can, the above change would align output with the intended “always explicit never” style.

src/codegen/generateDTS.ts (1)

13-13: Indentation via pad keeps behavior and intent intact

Switching from string concatenation to pad(2, line) preserves two-space indent and reads clearer.

If we want to improve readability of the generated DTS (given the FIXME), we could stop stripping empty lines here and let codegen control whitespace. Not blocking this PR.

src/codegen/generateRouteMap.ts (1)

31-47: Minor simplification of child route names computation

Logic is correct. You can simplify by deriving childRouteNames once without a mutable default.

-  let childRouteNames = ['never']
-
-  if (node.children.size > 0) {
-    // TODO: remove Array.from() once Node 20 support is dropped
-    const deepNamedChildren = Array.from(node.getChildrenDeep())
-      // skip routes that are not added to the types
-      .filter(
-        (childRoute) => childRoute.value.components.size > 0 && childRoute.name
-      )
-      .map((childRoute) => childRoute.name)
-      .sort()
-
-    if (deepNamedChildren.length > 0) {
-      childRouteNames = deepNamedChildren.map(
-        (childRouteName) => `'${childRouteName}'`
-      )
-    }
-  }
+  const deepChildrenNames =
+    node.children.size > 0
+      ? Array.from(node.getChildrenDeep())
+          .filter(
+            (r) => r.value.components.size > 0 && r.name
+          )
+          .map((r) => r.name!)
+          .sort()
+      : []
+  const childRouteNames =
+    deepChildrenNames.length > 0
+      ? deepChildrenNames.map((n) => `'${n}'`)
+      : ['never']

Optional: once Node ≥20 (or TS lib with ES2023) is baseline, you can switch to toSorted() and drop Array.from() if getChildrenDeep() already returns an array-like iterator.

src/codegen/generateRouteMap.spec.ts (1)

701-703: replaceAll with a global regex is fine; confirm CI Node version

This normalization is concise and readable. Ensure the test runner uses a Node version that supports String.prototype.replaceAll (Node ≥15).

If Node <15 needs support, replace with .replace and a loop or split/join:

- expect(result1.replaceAll(/\n\s+\|/g, ' |')).toContain(
+ expect(result1.replace(/\n\s+\|/g, ' |')).toContain(
   "| '/parent/alpha' | '/parent/beta' | '/parent/zebra'"
 )
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0841939 and b5ca976.

📒 Files selected for processing (12)
  • docs/.vitepress/twoslash-files.ts (1 hunks)
  • docs/.vitepress/twoslash/code/typed-router.ts (2 hunks)
  • docs/guide/typescript.md (1 hunks)
  • docs/introduction.md (1 hunks)
  • playground/src/router.ts (2 hunks)
  • playground/typed-router.d.ts (2 hunks)
  • src/codegen/generateDTS.ts (2 hunks)
  • src/codegen/generateRouteFileInfoMap.spec.ts (4 hunks)
  • src/codegen/generateRouteFileInfoMap.ts (2 hunks)
  • src/codegen/generateRouteMap.spec.ts (20 hunks)
  • src/codegen/generateRouteMap.ts (3 hunks)
  • src/utils/index.ts (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • playground/src/router.ts
  • docs/introduction.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/codegen/generateRouteFileInfoMap.ts
  • docs/.vitepress/twoslash-files.ts
  • docs/guide/typescript.md
  • src/codegen/generateRouteFileInfoMap.spec.ts
🧰 Additional context used
🧬 Code Graph Analysis (4)
src/codegen/generateDTS.ts (1)
src/utils/index.ts (1)
  • pad (24-26)
docs/.vitepress/twoslash/code/typed-router.ts (1)
src/codegen/generateRouteParams.ts (1)
  • ParamValue (55-57)
playground/typed-router.d.ts (1)
src/codegen/generateRouteParams.ts (4)
  • ParamValue (55-57)
  • ParamValueOneOrMore (30-33)
  • ParamValueZeroOrMore (39-41)
  • ParamValueZeroOrOne (47-49)
src/codegen/generateRouteMap.ts (1)
src/utils/index.ts (2)
  • pad (24-26)
  • formatMultilineUnion (34-36)
🔇 Additional comments (10)
src/codegen/generateDTS.ts (1)

1-1: Importing pad centralizes indentation logic — LGTM

Keeps formatting helpers in one place and avoids ad-hoc spacing.

src/codegen/generateRouteMap.ts (2)

15-16: Consistent indentation of entries — LGTM

Using pad(2, ...) for each RouteNamedMap entry keeps the root wrapper simple and the output consistent.


50-54: Multiline generics formatting achieves the “leading pipe” style — LGTM

formatMultilineUnion + pad produces the intended output and stable diffs.

docs/.vitepress/twoslash/code/typed-router.ts (1)

18-19: Leading-pipe unions in docs match the generator — LGTM

This mirrors the codegen output and helps reduce diff churn in documentation examples.

Open question from the PR: should entries with no members use “views: never” or multiline “views: | never”? Please decide for consistency across codegen, tests, and docs; I can update references accordingly.

Also applies to: 25-26, 32-33, 39-40

src/codegen/generateRouteMap.spec.ts (1)

24-51: Snapshots reflect the multiline union style — LGTM

The updated expectations for RouteRecordInfo’s 5th parameter and leading pipes match the new generator behavior.

playground/typed-router.d.ts (5)

21-27: Fifth parameter added to RouteRecordInfo type.

The new parameter represents child route relationships. The multiline union format with leading pipe improves git diff readability.


26-26: Consistent use of | never for routes without children.

Most routes correctly use | never for the fifth parameter when they have no child routes. This maintains consistency across the RouteNamedMap.

Also applies to: 33-34, 40-40, 47-47, 54-54, 61-61, 68-68, 75-75, 82-82, 89-89, 96-96, 103-103, 110-110, 117-117, 124-124, 131-131, 138-138, 145-145, 152-152, 159-159, 166-166, 173-173, 180-180, 187-187, 194-194, 201-201, 208-208, 215-215, 222-222, 229-229, 236-236, 243-243, 250-250, 257-257, 264-264, 271-271, 278-278, 285-285, 299-299, 306-306, 313-313, 320-320, 327-327, 334-334, 341-341, 348-348, 355-355, 362-362, 369-369, 376-376, 383-383, 390-390, 397-397, 404-404, 411-411, 418-418, 425-425, 432-432, 439-439


292-292: Child route relationship correctly represented.

Route '/named-views/parent' properly shows its child route '/named-views/parent/' in the fifth parameter union.


455-460: Multiline union formatting improves diff readability.

The _RouteFileInfoMap now uses consistent multiline union formatting with leading pipes for both routes and views. Entries without views correctly use views: | never.

Also applies to: 462-466, 468-472, 474-479, 481-485, 487-492, 494-498, 500-504, 506-510, 512-516, 518-522, 524-528, 530-534, 536-542, 544-548, 550-554, 556-560, 562-566, 568-572, 574-579, 581-585, 587-591, 593-597, 599-603, 605-609, 611-615, 617-621, 623-627, 629-633, 635-639, 641-645, 647-651, 653-657, 659-663, 665-669, 671-675, 677-681, 683-687, 689-693, 695-702, 704-708, 710-714, 716-720, 722-726, 728-732, 734-738, 740-744, 746-750, 752-756, 758-762, 764-768, 770-774, 776-780, 782-786, 788-792, 794-798, 800-804, 806-810, 812-816, 818-822, 824-828, 830-834, 836-840


465-465: Consistent views: | never notation.

All entries without views use the consistent views: | never format rather than views: never, maintaining uniformity with the multiline union style.

Also applies to: 471-471, 484-484, 491-491, 497-497, 503-503, 509-509, 515-515, 521-521, 527-527, 533-533, 547-547, 553-553, 559-559, 565-565, 571-571, 578-578, 584-584, 590-590, 596-596, 602-602, 608-608, 614-614, 620-620, 626-626, 632-632, 638-638, 644-644, 650-650, 656-656, 662-662, 668-668, 674-674, 680-680, 686-686, 692-692, 707-707, 713-713, 719-719, 725-725, 731-731, 737-737, 743-743, 749-749, 755-755, 761-761, 767-767, 773-773, 779-779, 785-785, 791-791, 797-797, 803-803, 809-809, 815-815, 821-821, 827-827, 833-833, 839-839

Copy link

@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

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b5ca976 and 59c3a23.

📒 Files selected for processing (4)
  • src/codegen/generateDTS.ts (2 hunks)
  • src/codegen/generateRouteFileInfoMap.ts (2 hunks)
  • src/codegen/generateRouteMap.ts (3 hunks)
  • src/utils/index.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/codegen/generateDTS.ts
  • src/codegen/generateRouteFileInfoMap.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/codegen/generateRouteMap.ts (2)
src/utils/index.ts (3)
  • indent (24-26)
  • formatMultilineUnion (34-38)
  • stringToStringType (47-49)
src/core/tree.ts (1)
  • TreeNode (15-304)
🔇 Additional comments (5)
src/utils/index.ts (2)

24-26: LGTM!

Clean implementation of the indent function.


34-38: LGTM!

Well-designed function that handles empty arrays correctly by defaulting to ['never'].

src/codegen/generateRouteMap.ts (3)

31-42: LGTM!

Good use of type guard to ensure type safety when filtering child routes.


44-46: LGTM!

Clean composition of utility functions to format child routes.


48-50: LGTM!

Consistent multiline formatting improves readability and reduces diff noise.

Copy link

@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: 2

🔭 Outside diff range comments (1)
src/codegen/generateRouteFileInfoMap.ts (1)

41-52: Deduplicate/sort unions and sort file keys for deterministic, minimal diffs

Prevents duplicated union members and stabilizes order across runs/changes. Also sorts file keys to keep the interface deterministic.

-  const code = Array.from(routesInfo.entries())
-    .map(
-      ([file, { routes, views }]) =>
-        `
-  '${file}': {
-    routes:
-      ${formatMultilineUnion(routes.map(stringToStringType), 6)}
-    views:
-      ${formatMultilineUnion(views.map(stringToStringType), 6)}
-  }`
-    )
-    .join('\n')
+  const code = Array.from(routesInfo.entries())
+    .sort(([a], [b]) => a.localeCompare(b))
+    .map(([file, { routes, views }]) => {
+      const routesUnion = [...new Set(routes)].sort().map(stringToStringType)
+      const viewsUnion = [...new Set(views)].sort().map(stringToStringType)
+      return `
+  '${file}': {
+    routes:
+      ${formatMultilineUnion(routesUnion, 6)}
+    views:
+      ${formatMultilineUnion(viewsUnion, 6)}
+  }`
+    })
+    .join('\n')
🧹 Nitpick comments (4)
src/codegen/generateRouteFileInfoMap.ts (1)

80-88: Avoid duplicate route names; use a Set during collection

Ensures uniqueness at the source and keeps output stable.

-  const routeNames = [node, ...node.getChildrenDeepSorted()]
-    // an unnamed route cannot be accessed in types
-    .reduce<string[]>((acc, node) => {
-      if (node.isNamed()) {
-        acc.push(node.name)
-      }
-      return acc
-    }, [])
+  const routeNames = Array.from(
+    [node, ...node.getChildrenDeepSorted()].reduce<Set<string>>((set, node) => {
+      if (node.isNamed()) set.add(node.name)
+      return set
+    }, new Set<string>())
+  )
src/core/tree.ts (1)

297-300: isNamed() guard is correct; add a clarifying doc comment

Truthiness captures empty-string and false overrides as “not named”. A short JSDoc will prevent confusion.

Apply this diff:

+  /** True when the computed name is a non-empty string.
+   * Empty string or `false` overrides are considered "not named".
+   */
   isNamed(): this is TreeNodeNamed {
     return !!this.name
   }
src/core/tree.spec.ts (1)

407-445: Consider adding a null-name override test to match the implementation comment

The code comment in TreeNode.get name() mentions null as a way to remove a name. Add a test to lock this in.

You could extend this test with:

// Set null name
// If types allow null on overrides:
node.value.setOverride('', { name: null as any })
expect(node.name).toBe(null)
expect(node.isNamed()).toBe(false)

If the route block type doesn’t currently allow null, either align the comment or widen the type accordingly and add this test.

src/codegen/generateRouteMap.ts (1)

34-47: Deduplicate child route names before building the union

Unlikely but possible to encounter duplicates; Set-based dedupe keeps unions minimal and deterministic.

Apply this diff:

-  const childRouteNames: string[] =
-    node.children.size > 0
-      ? // TODO: remove Array.from() once Node 20 support is dropped
-        Array.from(node.getChildrenDeep())
-          // skip routes that are not added to the types
-          .reduce<string[]>((acc, childRoute) => {
-            if (childRoute.value.components.size && childRoute.isNamed()) {
-              acc.push(childRoute.name)
-            }
-            return acc
-          }, [])
-          .sort()
-      : []
+  const childRouteNames: string[] =
+    node.children.size > 0
+      ? Array.from(
+          new Set(
+            // TODO: remove Array.from() once Node 20 support is dropped
+            Array.from(node.getChildrenDeep())
+              .filter((c) => c.value.components.size && c.isNamed())
+              .map((c) => c.name)
+          )
+        ).sort()
+      : []
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 59c3a23 and c9552a5.

📒 Files selected for processing (7)
  • src/codegen/generateDTS.ts (2 hunks)
  • src/codegen/generateRouteFileInfoMap.ts (3 hunks)
  • src/codegen/generateRouteMap.ts (2 hunks)
  • src/codegen/generateRouteRecords.ts (3 hunks)
  • src/core/tree.spec.ts (2 hunks)
  • src/core/tree.ts (2 hunks)
  • src/utils/index.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/utils/index.ts
  • src/codegen/generateDTS.ts
🧰 Additional context used
🧬 Code Graph Analysis (4)
src/core/tree.ts (1)
src/types.ts (1)
  • TreeNode (7-7)
src/codegen/generateRouteRecords.ts (1)
src/utils/index.ts (2)
  • pad (24-26)
  • stringToStringType (47-49)
src/codegen/generateRouteFileInfoMap.ts (2)
src/utils/index.ts (2)
  • formatMultilineUnion (34-38)
  • stringToStringType (47-49)
client.d.ts (1)
  • routes (7-7)
src/codegen/generateRouteMap.ts (3)
src/utils/index.ts (3)
  • pad (24-26)
  • stringToStringType (47-49)
  • formatMultilineUnion (34-38)
src/core/tree.ts (1)
  • TreeNodeNamed (15-17)
src/codegen/generateRouteParams.ts (1)
  • generateRouteParams (3-22)
🔇 Additional comments (9)
src/codegen/generateRouteFileInfoMap.ts (1)

3-3: Imports LGTM

Utility usage aligns with the new multiline-union formatting approach.

src/core/tree.ts (1)

15-18: Type alias is precise and fits the intended narrowing

TreeNodeNamed correctly narrows name to string for use with the isNamed() guard and downstream codegen.

src/core/tree.spec.ts (1)

390-405: Good coverage for custom names with the new isNamed() API

Asserting both node.name and node.isNamed() after overrides validates the guard behavior well.

src/codegen/generateRouteRecords.ts (2)

5-5: Importing pad/stringToStringType improves consistency

Centralizing spacing and string literal formatting is a good move toward cleaner diffs.


52-54: Indentation via pad() is cleaner and less error-prone

This reduces manual spacing logic and makes future tweaks trivial.

src/codegen/generateRouteMap.ts (4)

1-1: Type narrowing import looks good

Using TreeNodeNamed pairs well with the isNamed() predicate.


3-3: Utility imports match the new multiline union formatting

formatMultilineUnion and pad use is appropriate for leading-pipe unions and consistent spacing.


48-55: Multiline union and multiline type args look great

formatMultilineUnion with a leading pipe meets the “cleaner diffs” goal and reads well.


26-26: No external callers for generateRouteRecordInfo

The only invocation of generateRouteRecordInfo is in generateRouteNamedMap (line 11), immediately guarded by node.isNamed(). There are no other usages in the repo, so updating the signature to TreeNodeNamed won’t break external call sites.

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.

1 participant