@tanstack/router-generator 1.141.8 → 1.141.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/utils.ts CHANGED
@@ -14,7 +14,6 @@ import type { ImportDeclaration, RouteNode } from './types'
14
14
  export class RoutePrefixMap {
15
15
  private prefixToRoute: Map<string, RouteNode> = new Map()
16
16
  private layoutRoutes: Array<RouteNode> = []
17
- private nonNestedRoutes: Array<RouteNode> = []
18
17
 
19
18
  constructor(routes: Array<RouteNode>) {
20
19
  for (const route of routes) {
@@ -23,7 +22,6 @@ export class RoutePrefixMap {
23
22
  // Index by exact path for direct lookups
24
23
  this.prefixToRoute.set(route.routePath, route)
25
24
 
26
- // Track layout routes separately for non-nested route handling
27
25
  if (
28
26
  route._fsRouteType === 'pathless_layout' ||
29
27
  route._fsRouteType === 'layout' ||
@@ -31,20 +29,12 @@ export class RoutePrefixMap {
31
29
  ) {
32
30
  this.layoutRoutes.push(route)
33
31
  }
34
-
35
- // Track non-nested routes separately
36
- if (route._isExperimentalNonNestedRoute) {
37
- this.nonNestedRoutes.push(route)
38
- }
39
32
  }
40
33
 
41
34
  // Sort by path length descending for longest-match-first
42
35
  this.layoutRoutes.sort(
43
36
  (a, b) => (b.routePath?.length ?? 0) - (a.routePath?.length ?? 0),
44
37
  )
45
- this.nonNestedRoutes.sort(
46
- (a, b) => (b.routePath?.length ?? 0) - (a.routePath?.length ?? 0),
47
- )
48
38
  }
49
39
 
50
40
  /**
@@ -69,48 +59,6 @@ export class RoutePrefixMap {
69
59
  return null
70
60
  }
71
61
 
72
- /**
73
- * Find parent for non-nested routes (needs layout route matching).
74
- */
75
- findParentForNonNested(
76
- routePath: string,
77
- originalRoutePath: string | undefined,
78
- nonNestedSegments: Array<string>,
79
- ): RouteNode | null {
80
- // First check for other non-nested routes that are prefixes
81
- // Use pre-sorted array for longest-match-first
82
- for (const route of this.nonNestedRoutes) {
83
- if (
84
- route.routePath !== routePath &&
85
- originalRoutePath?.startsWith(`${route.originalRoutePath}/`)
86
- ) {
87
- return route
88
- }
89
- }
90
-
91
- // Then check layout routes
92
- for (const route of this.layoutRoutes) {
93
- if (route.routePath === '/') continue
94
-
95
- // Skip if this route's original path + underscore matches a non-nested segment
96
- if (
97
- nonNestedSegments.some((seg) => seg === `${route.originalRoutePath}_`)
98
- ) {
99
- continue
100
- }
101
-
102
- // Check if this layout route is a prefix of the path we're looking for
103
- if (
104
- routePath.startsWith(`${route.routePath}/`) &&
105
- route.routePath !== routePath
106
- ) {
107
- return route
108
- }
109
- }
110
-
111
- return null
112
- }
113
-
114
62
  /**
115
63
  * Check if a route exists at the given path.
116
64
  */
@@ -192,10 +140,7 @@ export function removeTrailingSlash(s: string) {
192
140
  const BRACKET_CONTENT_RE = /\[(.*?)\]/g
193
141
  const SPLIT_REGEX = /(?<!\[)\.(?!\])/g
194
142
 
195
- export function determineInitialRoutePath(
196
- routePath: string,
197
- config?: Pick<Config, 'experimental' | 'routeToken' | 'indexToken'>,
198
- ) {
143
+ export function determineInitialRoutePath(routePath: string) {
199
144
  const DISALLOWED_ESCAPE_CHARS = new Set([
200
145
  '/',
201
146
  '\\',
@@ -209,6 +154,7 @@ export function determineInitialRoutePath(
209
154
  '!',
210
155
  '$',
211
156
  '%',
157
+ '_',
212
158
  ])
213
159
 
214
160
  const originalRoutePath =
@@ -216,31 +162,7 @@ export function determineInitialRoutePath(
216
162
  `/${(cleanPath(routePath) || '').split(SPLIT_REGEX).join('/')}`,
217
163
  ) || ''
218
164
 
219
- // check if the route path is a valid non-nested path,
220
- // TODO with new major rename to reflect not experimental anymore
221
- const isExperimentalNonNestedRoute = isValidNonNestedRoute(
222
- originalRoutePath,
223
- config,
224
- )
225
-
226
- let cleanedRoutePath = routePath
227
-
228
- // we already identified the path as non-nested and can now remove the trailing underscores
229
- // we need to do this now before we encounter any escaped trailing underscores
230
- // this way we can be sure any remaining trailing underscores should remain
231
- // TODO with new major we can remove check and always remove leading underscores
232
- if (config?.experimental?.nonNestedRoutes) {
233
- // we should leave trailing underscores if the route path is the root path
234
- if (originalRoutePath !== `/${rootPathId}`) {
235
- // remove trailing underscores on various path segments
236
- cleanedRoutePath = removeTrailingUnderscores(
237
- originalRoutePath,
238
- config.routeToken,
239
- )
240
- }
241
- }
242
-
243
- const parts = cleanedRoutePath.split(SPLIT_REGEX)
165
+ const parts = routePath.split(SPLIT_REGEX)
244
166
 
245
167
  // Escape any characters that in square brackets
246
168
  // we keep the original path untouched
@@ -275,7 +197,6 @@ export function determineInitialRoutePath(
275
197
 
276
198
  return {
277
199
  routePath: final,
278
- isExperimentalNonNestedRoute,
279
200
  originalRoutePath,
280
201
  }
281
202
  }
@@ -518,30 +439,6 @@ export function removeLastSegmentFromPath(routePath: string = '/'): string {
518
439
  return segments.join('/')
519
440
  }
520
441
 
521
- const nonNestedSegmentRegex = /_(?=\/|$)/g
522
- const openBracketRegex = /\[/g
523
- const closeBracketRegex = /\]/g
524
-
525
- /**
526
- * Extracts non-nested segments from a route path.
527
- * Used for determining parent routes in non-nested route scenarios.
528
- */
529
- export function getNonNestedSegments(routePath: string): Array<string> {
530
- nonNestedSegmentRegex.lastIndex = 0
531
- const result: Array<string> = []
532
- for (const match of routePath.matchAll(nonNestedSegmentRegex)) {
533
- const beforeStr = routePath.substring(0, match.index)
534
- openBracketRegex.lastIndex = 0
535
- closeBracketRegex.lastIndex = 0
536
- const openBrackets = beforeStr.match(openBracketRegex)?.length ?? 0
537
- const closeBrackets = beforeStr.match(closeBracketRegex)?.length ?? 0
538
- if (openBrackets === closeBrackets) {
539
- result.push(routePath.substring(0, match.index + 1))
540
- }
541
- }
542
- return result.reverse()
543
- }
544
-
545
442
  /**
546
443
  * Find parent route using RoutePrefixMap for O(k) lookups instead of O(n).
547
444
  */
@@ -549,21 +446,11 @@ export function hasParentRoute(
549
446
  prefixMap: RoutePrefixMap,
550
447
  node: RouteNode,
551
448
  routePathToCheck: string | undefined,
552
- originalRoutePathToCheck: string | undefined,
553
449
  ): RouteNode | null {
554
450
  if (!routePathToCheck || routePathToCheck === '/') {
555
451
  return null
556
452
  }
557
453
 
558
- if (node._isExperimentalNonNestedRoute && originalRoutePathToCheck) {
559
- const nonNestedSegments = getNonNestedSegments(originalRoutePathToCheck)
560
- return prefixMap.findParentForNonNested(
561
- routePathToCheck,
562
- originalRoutePathToCheck,
563
- nonNestedSegments,
564
- )
565
- }
566
-
567
454
  return prefixMap.findParent(routePathToCheck)
568
455
  }
569
456
 
@@ -606,16 +493,9 @@ export const inferPath = (routeNode: RouteNode): string => {
606
493
  /**
607
494
  * Infers the full path for use by TS
608
495
  */
609
- export const inferFullPath = (
610
- routeNode: RouteNode,
611
- config?: Pick<Config, 'experimental' | 'routeToken'>,
612
- ): string => {
613
- // with new nonNestedPaths feature we can be sure any remaining trailing underscores are escaped and should remain
614
- // TODO with new major we can remove check and only remove leading underscores
496
+ export const inferFullPath = (routeNode: RouteNode): string => {
615
497
  const fullPath = removeGroups(
616
- (config?.experimental?.nonNestedRoutes
617
- ? removeLayoutSegments(routeNode.routePath)
618
- : removeUnderscores(removeLayoutSegments(routeNode.routePath))) ?? '',
498
+ removeUnderscores(removeLayoutSegments(routeNode.routePath)) ?? '',
619
499
  )
620
500
 
621
501
  return routeNode.cleanedPath === '/' ? fullPath : fullPath.replace(/\/$/, '')
@@ -626,13 +506,9 @@ export const inferFullPath = (
626
506
  */
627
507
  export const createRouteNodesByFullPath = (
628
508
  routeNodes: Array<RouteNode>,
629
- config?: Pick<Config, 'experimental' | 'routeToken'>,
630
509
  ): Map<string, RouteNode> => {
631
510
  return new Map(
632
- routeNodes.map((routeNode) => [
633
- inferFullPath(routeNode, config),
634
- routeNode,
635
- ]),
511
+ routeNodes.map((routeNode) => [inferFullPath(routeNode), routeNode]),
636
512
  )
637
513
  }
638
514
 
@@ -641,11 +517,10 @@ export const createRouteNodesByFullPath = (
641
517
  */
642
518
  export const createRouteNodesByTo = (
643
519
  routeNodes: Array<RouteNode>,
644
- config?: Pick<Config, 'experimental' | 'routeToken'>,
645
520
  ): Map<string, RouteNode> => {
646
521
  return new Map(
647
522
  dedupeBranchesAndIndexRoutes(routeNodes).map((routeNode) => [
648
- inferTo(routeNode, config),
523
+ inferTo(routeNode),
649
524
  routeNode,
650
525
  ]),
651
526
  )
@@ -668,11 +543,8 @@ export const createRouteNodesById = (
668
543
  /**
669
544
  * Infers to path
670
545
  */
671
- export const inferTo = (
672
- routeNode: RouteNode,
673
- config?: Pick<Config, 'experimental' | 'routeToken'>,
674
- ): string => {
675
- const fullPath = inferFullPath(routeNode, config)
546
+ export const inferTo = (routeNode: RouteNode): string => {
547
+ const fullPath = inferFullPath(routeNode)
676
548
 
677
549
  if (fullPath === '/') return fullPath
678
550
 
@@ -711,7 +583,7 @@ export function checkRouteFullPathUniqueness(
711
583
  config: Config,
712
584
  ) {
713
585
  const routes = _routes.map((d) => {
714
- const inferredFullPath = inferFullPath(d, config)
586
+ const inferredFullPath = inferFullPath(d)
715
587
  return { ...d, inferredFullPath }
716
588
  })
717
589
 
@@ -849,7 +721,7 @@ export function buildFileRoutesByPathInterface(opts: {
849
721
  routeNodes: Array<RouteNode>
850
722
  module: string
851
723
  interfaceName: string
852
- config?: Pick<Config, 'experimental' | 'routeToken'>
724
+ config?: Pick<Config, 'routeToken'>
853
725
  }): string {
854
726
  return `declare module '${opts.module}' {
855
727
  interface ${opts.interfaceName} {
@@ -863,7 +735,7 @@ export function buildFileRoutesByPathInterface(opts: {
863
735
  return `'${filePathId}': {
864
736
  id: '${filePathId}'
865
737
  path: '${inferPath(routeNode)}'
866
- fullPath: '${inferFullPath(routeNode, opts.config)}'
738
+ fullPath: '${inferFullPath(routeNode)}'
867
739
  preLoaderRoute: ${preloaderRoute}
868
740
  parentRoute: typeof ${parent}
869
741
  }`
@@ -916,51 +788,3 @@ export function getImportForRouteNode(
916
788
  ],
917
789
  } satisfies ImportDeclaration
918
790
  }
919
-
920
- /**
921
- * Used to validate if a route is a pathless layout route
922
- * @param normalizedRoutePath Normalized route path, i.e `/foo/_layout/route.tsx` and `/foo._layout.route.tsx` to `/foo/_layout/route`
923
- * @param config The `router-generator` configuration object
924
- * @returns Boolean indicating if the route is a pathless layout route
925
- */
926
- export function isValidNonNestedRoute(
927
- normalizedRoutePath: string,
928
- config?: Pick<Config, 'experimental' | 'routeToken' | 'indexToken'>,
929
- ): boolean {
930
- if (!config?.experimental?.nonNestedRoutes) {
931
- return false
932
- }
933
-
934
- const segments = normalizedRoutePath.split('/').filter(Boolean)
935
-
936
- if (segments.length === 0) {
937
- return false
938
- }
939
-
940
- const lastRouteSegment = segments[segments.length - 1]!
941
-
942
- // If segment === __root, then exit as false
943
- if (lastRouteSegment === rootPathId) {
944
- return false
945
- }
946
-
947
- if (
948
- lastRouteSegment !== config.indexToken &&
949
- lastRouteSegment !== config.routeToken &&
950
- lastRouteSegment.endsWith('_')
951
- ) {
952
- return true
953
- }
954
-
955
- for (const segment of segments.slice(0, -1).reverse()) {
956
- if (segment === config.routeToken) {
957
- return false
958
- }
959
-
960
- if (segment.endsWith('_')) {
961
- return true
962
- }
963
- }
964
-
965
- return false
966
- }