From 8bb9c50171707918cbda13a6668c34a2249390e8 Mon Sep 17 00:00:00 2001 From: Nico Lynzaad Date: Mon, 6 Oct 2025 04:57:05 +0200 Subject: [PATCH 01/25] revert router-core changes --- packages/router-core/src/path.ts | 45 +- .../router-core/src/process-route-tree.ts | 4 +- .../router-core/tests/match-by-path.test.ts | 189 ---- packages/router-core/tests/path.test.ts | 921 ------------------ 4 files changed, 16 insertions(+), 1143 deletions(-) diff --git a/packages/router-core/src/path.ts b/packages/router-core/src/path.ts index 6c2e1c8e58b..b47466eadf2 100644 --- a/packages/router-core/src/path.ts +++ b/packages/router-core/src/path.ts @@ -1,4 +1,3 @@ -import { rootRouteId } from './root' import { last } from './utils' import type { LRUCache } from './lru-cache' import type { MatchLocation } from './RouterProvider' @@ -155,8 +154,8 @@ export function resolvePath({ trailingSlash = 'never', parseCache, }: ResolvePathOptions) { - let baseSegments = parseBasePathSegments(base, parseCache).slice() - const toSegments = parseRoutePathSegments(to, parseCache) + let baseSegments = parsePathname(base, parseCache).slice() + const toSegments = parsePathname(to, parseCache) if (baseSegments.length > 1 && last(baseSegments)?.value === '/') { baseSegments.pop() @@ -202,16 +201,6 @@ export function resolvePath({ export type ParsePathnameCache = LRUCache> -export const parseBasePathSegments = ( - pathname?: string, - cache?: ParsePathnameCache, -): ReadonlyArray => parsePathname(pathname, cache, true) - -export const parseRoutePathSegments = ( - pathname?: string, - cache?: ParsePathnameCache, -): ReadonlyArray => parsePathname(pathname, cache, false) - export const parsePathname = ( pathname?: string, cache?: ParsePathnameCache, @@ -275,14 +264,8 @@ function baseParsePathname( segments.push( ...split.map((part): Segment => { - // strip tailing underscore for non-nested paths - const partToMatch = - !basePathValues && part !== rootRouteId && part.slice(-1) === '_' - ? part.slice(0, -1) - : part - // Check for wildcard with curly braces: prefix{$}suffix - const wildcardBracesMatch = partToMatch.match(WILDCARD_W_CURLY_BRACES_RE) + const wildcardBracesMatch = part.match(WILDCARD_W_CURLY_BRACES_RE) if (wildcardBracesMatch) { const prefix = wildcardBracesMatch[1] const suffix = wildcardBracesMatch[2] @@ -295,7 +278,7 @@ function baseParsePathname( } // Check for optional parameter format: prefix{-$paramName}suffix - const optionalParamBracesMatch = partToMatch.match( + const optionalParamBracesMatch = part.match( OPTIONAL_PARAM_W_CURLY_BRACES_RE, ) if (optionalParamBracesMatch) { @@ -311,7 +294,7 @@ function baseParsePathname( } // Check for the new parameter format: prefix{$paramName}suffix - const paramBracesMatch = partToMatch.match(PARAM_W_CURLY_BRACES_RE) + const paramBracesMatch = part.match(PARAM_W_CURLY_BRACES_RE) if (paramBracesMatch) { const prefix = paramBracesMatch[1] const paramName = paramBracesMatch[2] @@ -325,8 +308,8 @@ function baseParsePathname( } // Check for bare parameter format: $paramName (without curly braces) - if (PARAM_RE.test(partToMatch)) { - const paramName = partToMatch.substring(1) + if (PARAM_RE.test(part)) { + const paramName = part.substring(1) return { type: SEGMENT_TYPE_PARAM, value: '$' + paramName, @@ -336,7 +319,7 @@ function baseParsePathname( } // Check for bare wildcard: $ (without curly braces) - if (WILDCARD_RE.test(partToMatch)) { + if (WILDCARD_RE.test(part)) { return { type: SEGMENT_TYPE_WILDCARD, value: '$', @@ -348,12 +331,12 @@ function baseParsePathname( // Handle regular pathname segment return { type: SEGMENT_TYPE_PATHNAME, - value: partToMatch.includes('%25') - ? partToMatch + value: part.includes('%25') + ? part .split('%25') .map((segment) => decodeURI(segment)) .join('%25') - : decodeURI(partToMatch), + : decodeURI(part), } }), ) @@ -392,7 +375,7 @@ export function interpolatePath({ decodeCharMap, parseCache, }: InterpolatePathOptions): InterPolatePathResult { - const interpolatedPathSegments = parseRoutePathSegments(path, parseCache) + const interpolatedPathSegments = parsePathname(path, parseCache) function encodeParam(key: string): any { const value = params[key] @@ -537,11 +520,11 @@ export function matchByPath( const stringTo = to as string // Parse the from and to - const baseSegments = parseBasePathSegments( + const baseSegments = parsePathname( from.startsWith('/') ? from : `/${from}`, parseCache, ) - const routeSegments = parseRoutePathSegments( + const routeSegments = parsePathname( stringTo.startsWith('/') ? stringTo : `/${stringTo}`, parseCache, ) diff --git a/packages/router-core/src/process-route-tree.ts b/packages/router-core/src/process-route-tree.ts index 221cedc26de..a95c0a64e99 100644 --- a/packages/router-core/src/process-route-tree.ts +++ b/packages/router-core/src/process-route-tree.ts @@ -3,7 +3,7 @@ import { SEGMENT_TYPE_OPTIONAL_PARAM, SEGMENT_TYPE_PARAM, SEGMENT_TYPE_PATHNAME, - parseRoutePathSegments, + parsePathname, trimPathLeft, trimPathRight, } from './path' @@ -70,7 +70,7 @@ function sortRoutes( } const trimmed = trimPathLeft(d.fullPath) - let parsed = parseRoutePathSegments(trimmed) + let parsed = parsePathname(trimmed) // Removes the leading slash if it is not the only remaining segment let skip = 0 diff --git a/packages/router-core/tests/match-by-path.test.ts b/packages/router-core/tests/match-by-path.test.ts index 024db05a247..61f0bc369c7 100644 --- a/packages/router-core/tests/match-by-path.test.ts +++ b/packages/router-core/tests/match-by-path.test.ts @@ -199,192 +199,3 @@ describe('fuzzy path matching', () => { ) }) }) - -describe('non-nested paths', () => { - describe('default path matching', () => { - it.each([ - ['/a', '/a_', {}], - ['/a/b', '/a_/b_', {}], - ['/a', '/a_/', {}], - ['/a/', '/a_/', {}], - ['/a/', '/a_', undefined], - ['/b', '/a_', undefined], - ])('static %s %s => %s', (from, to, result) => { - expect( - matchByPath(from, { to, caseSensitive: true, fuzzy: false }), - ).toEqual(result) - }) - - it.each([ - ['/a/1', '/a_/$id_', { id: '1' }], - ['/a/1/b', '/a_/$id_/b_', { id: '1' }], - ['/a/1/b/2', '/a_/$id_/b_/$other_', { id: '1', other: '2' }], - ['/a/1_/b/2', '/a_/$id_/b_/$other_', { id: '1_', other: '2' }], - ['/a/1/b/2', '/a_/$id_/b_/$id_', { id: '2' }], - ])('params %s => %s', (from, to, result) => { - expect( - matchByPath(from, { to, caseSensitive: true, fuzzy: false }), - ).toEqual(result) - }) - - it('params support more than alphanumeric characters', () => { - // in the value: basically everything except / and % - expect( - matchByPath( - '/a/@&é"\'(§è!çà)-_°^¨$*€£`ù=+:;.,?~<>|î©#0123456789\\😀}{', - { to: '/a_/$id_' }, - ), - ).toEqual({ - id: '@&é"\'(§è!çà)-_°^¨$*€£`ù=+:;.,?~<>|î©#0123456789\\😀}{', - }) - // in the key: basically everything except / and % and $ - expect( - matchByPath('/a/1', { - to: '/a_/$@&é"\'(§è!çà)-_°^¨*€£`ù=+:;.,?~<>|î©#0123456789\\😀}{_', - }), - ).toEqual({ - '@&é"\'(§è!çà)-_°^¨*€£`ù=+:;.,?~<>|î©#0123456789\\😀}{': '1', - }) - }) - - it.each([ - ['/a/1', '/a_/{-$id}_', { id: '1' }], - ['/a', '/a_/{-$id}_', {}], - ['/a/1/b', '/a_/{-$id}_/b_', { id: '1' }], - ['/a/b', '/a_/{-$id}_/b_', {}], - ['/a/1/b/2', '/a_/{-$id}_/b_/{-$other}_', { id: '1', other: '2' }], - ['/a/b/2', '/a_/{-$id}_/b_/{-$other}_', { other: '2' }], - ['/a/1/b', '/a_/{-$id}_/b_/{-$other}_', { id: '1' }], - ['/a/b', '/a_/{-$id}_/b_/{-$other}_', {}], - ['/a/1/b/2', '/a_/{-$id}_/b_/{-$id}_', { id: '2' }], - ])('optional %s => %s', (from, to, result) => { - expect( - matchByPath(from, { to, caseSensitive: true, fuzzy: false }), - ).toEqual(result) - }) - - it.each([ - ['/a/b/c', '/a_/$_', { _splat: 'b/c', '*': 'b/c' }], - ['/a/', '/a_/$_', { _splat: '/', '*': '/' }], - ['/a', '/a_/$_', { _splat: '', '*': '' }], - ['/a/b/c', '/a_/$_/foo_', { _splat: 'b/c', '*': 'b/c' }], - ])('wildcard %s => %s', (from, to, result) => { - expect( - matchByPath(from, { to, caseSensitive: true, fuzzy: false }), - ).toEqual(result) - }) - }) - - describe('case insensitive path matching', () => { - it.each([ - ['/a', '/A_', {}], - ['/a/b', '/A_/B_', {}], - ['/a', '/A_/', {}], - ['/a/', '/A_/', {}], - ['/a/', '/A_', undefined], - ['/b', '/A_', undefined], - ])('static %s %s => %s', (from, to, result) => { - expect( - matchByPath(from, { to, caseSensitive: false, fuzzy: false }), - ).toEqual(result) - }) - - it.each([ - ['/a/1', '/A_/$id_', { id: '1' }], - ['/a/1/b', '/A_/$id_/B_', { id: '1' }], - ['/a/1/b/2', '/A_/$id_/B_/$other_', { id: '1', other: '2' }], - ['/a/1/b/2', '/A_/$id_/B_/$id_', { id: '2' }], - ])('params %s => %s', (from, to, result) => { - expect( - matchByPath(from, { to, caseSensitive: false, fuzzy: false }), - ).toEqual(result) - }) - - it.each([ - ['/a/1', '/A_/{-$id}_', { id: '1' }], - ['/a', '/A_/{-$id}_', {}], - ['/a/1/b', '/A_/{-$id}_/B_', { id: '1' }], - ['/a/1/b/2', '/A_/{-$id}_/B_/{-$other}_', { id: '1', other: '2' }], - ['/a/1/b', '/A_/{-$id}_/B_/{-$other}_', { id: '1' }], - ['/a/1/b/2', '/A_/{-$id}_/B/{-$id}_', { id: '2' }], - ])('optional %s => %s', (from, to, result) => { - expect( - matchByPath(from, { to, caseSensitive: false, fuzzy: false }), - ).toEqual(result) - }) - - it.each([ - ['/a/b/c', '/A_/$_', { _splat: 'b/c', '*': 'b/c' }], - ['/a/', '/A_/$_', { _splat: '/', '*': '/' }], - ['/a', '/A_/$_', { _splat: '', '*': '' }], - ['/a/b/c', '/A_/$_/foo_', { _splat: 'b/c', '*': 'b/c' }], - ])('wildcard %s => %s', (from, to, result) => { - expect( - matchByPath(from, { to, caseSensitive: false, fuzzy: false }), - ).toEqual(result) - }) - }) - - describe('fuzzy path matching', () => { - it.each([ - ['/a', '/a_', {}], - ['/a', '/a_/', {}], - ['/a/', '/a_/', {}], - ['/a/', '/a_', { '**': '/' }], - ['/a/b', '/a_/b_', {}], - ['/a/b', '/a_', { '**': 'b' }], - ['/a/b/', '/a_', { '**': 'b/' }], - ['/a/b/c', '/a_', { '**': 'b/c' }], - ['/a', '/a_/b_', undefined], - ['/b', '/a_', undefined], - ['/a', '/b_', undefined], - ])('static %s %s => %s', (from, to, result) => { - expect( - matchByPath(from, { to, fuzzy: true, caseSensitive: true }), - ).toEqual(result) - }) - - it.each([ - ['/a/1', '/a_/$id_', { id: '1' }], - ['/a/1/b', '/a_/$id_', { id: '1', '**': 'b' }], - ['/a/1/', '/a_/$id_/', { id: '1' }], - ['/a/1/b/2', '/a_/$id_/b_/$other_', { id: '1', other: '2' }], - ['/a/1/b/2/c', '/a_/$id_/b_/$other_', { id: '1', other: '2', '**': 'c' }], - ])('params %s => %s', (from, to, result) => { - expect( - matchByPath(from, { to, fuzzy: true, caseSensitive: true }), - ).toEqual(result) - }) - - it.each([ - ['/a/1', '/a_/{-$id}_', { id: '1' }], - ['/a', '/a_/{-$id}_', {}], - ['/a/1/b', '/a_/{-$id}_', { '**': 'b', id: '1' }], - ['/a/1/b', '/a_/{-$id}_/b_', { id: '1' }], - ['/a/b', '/a_/{-$id}_/b_', {}], - ['/a/b/c', '/a_/{-$id}_/b_', { '**': 'c' }], - ['/a/b', '/a/_{-$id}_/b_/{-$other}_', {}], - ['/a/b/2/d', '/a_/{-$id}_/b_/{-$other}_', { other: '2', '**': 'd' }], - [ - '/a/1/b/2/c', - '/a_/{-$id}_/b_/{-$other}_', - { id: '1', other: '2', '**': 'c' }, - ], - ])('optional %s => %s', (from, to, result) => { - expect( - matchByPath(from, { to, fuzzy: true, caseSensitive: true }), - ).toEqual(result) - }) - - it.each([ - ['/a/b/c', '/a_/$_', { _splat: 'b/c', '*': 'b/c' }], - ['/a/', '/a_/$_', { _splat: '/', '*': '/' }], - ['/a', '/a_/$_', { _splat: '', '*': '' }], - ['/a/b/c/d', '/a_/$_/foo_', { _splat: 'b/c/d', '*': 'b/c/d' }], - ])('wildcard %s => %s', (from, to, result) => { - expect( - matchByPath(from, { to, fuzzy: true, caseSensitive: true }), - ).toEqual(result) - }) - }) -}) diff --git a/packages/router-core/tests/path.test.ts b/packages/router-core/tests/path.test.ts index c84ca14fcee..2eda89918be 100644 --- a/packages/router-core/tests/path.test.ts +++ b/packages/router-core/tests/path.test.ts @@ -1006,924 +1006,3 @@ describe('parsePathname', () => { }) }) }) - -describe('non-nested paths', async () => { - describe('resolvePath', () => { - describe.each([ - ['/', '/a_', '/a'], - ['/', 'a_/', '/a'], - ['/', '/a_/b_', '/a/b'], - ['/a', 'b_', '/a/b'], - ['/c', '/a/b/c_', '/a/b/c'], - ['/', 'c_', '/c'], - ['/', './c_', '/c'], - ['/', 'a_/b_', '/a/b'], - ['/', './a_/b_', '/a/b'], - ['/a/b/c', 'd_', '/a/b/c/d'], - ['/a/b/c', './d_', '/a/b/c/d'], - ['/a/b/c', './../d_', '/a/b/d'], - ['/a/b/c/d', './../d_', '/a/b/c/d'], - ['/a/b/c', '../../d_', '/a/d'], - ['/a/b/c', '../d_', '/a/b/d'], - ['/', '/products-list_', '/products-list'], - ])('resolves correctly', (a, b, eq) => { - it(`${a} to ${b} === ${eq}`, () => { - expect(resolvePath({ base: a, to: b })).toEqual(eq) - }) - it(`${a}/ to ${b} === ${eq} (trailing slash)`, () => { - expect(resolvePath({ base: a + '/', to: b })).toEqual(eq) - }) - it(`${a}/ to ${b}/ === ${eq} (trailing slash + trailing slash)`, () => { - expect(resolvePath({ base: a + '/', to: b + '/' })).toEqual(eq) - }) - }) - - describe('trailingSlash', () => { - describe(`'always'`, () => { - it('keeps trailing slash', () => { - expect( - resolvePath({ - base: '/a/b/c', - to: 'd_/', - trailingSlash: 'always', - }), - ).toBe('/a/b/c/d/') - }) - it('adds trailing slash', () => { - expect( - resolvePath({ - base: '/a/b/c', - to: 'd_', - trailingSlash: 'always', - }), - ).toBe('/a/b/c/d/') - }) - }) - - describe(`'never'`, () => { - it('removes trailing slash', () => { - expect( - resolvePath({ - base: '/a/b/c', - to: 'd_/', - trailingSlash: 'never', - }), - ).toBe('/a/b/c/d') - }) - it('does not add trailing slash', () => { - expect( - resolvePath({ - base: '/a/b/c', - to: 'd_', - trailingSlash: 'never', - }), - ).toBe('/a/b/c/d') - }) - }) - - describe(`'preserve'`, () => { - it('keeps trailing slash', () => { - expect( - resolvePath({ - base: '/a/b/c', - to: 'd_/', - trailingSlash: 'preserve', - }), - ).toBe('/a/b/c/d/') - }) - it('does not add trailing slash', () => { - expect( - resolvePath({ - base: '/a/b/c', - to: 'd_', - trailingSlash: 'preserve', - }), - ).toBe('/a/b/c/d') - }) - }) - }) - - describe.each([{ base: '/' }, { base: '/nested' }])( - 'param routes w/ base=$base', - ({ base }) => { - describe('wildcard (prefix + suffix)', () => { - it.each([ - { name: 'regular top-level', to: '/$_', expected: '/$' }, - { - name: 'regular nested', - to: '/params/wildcard_/$_', - expected: '/params/wildcard/$', - }, - { - name: 'with top-level prefix', - to: '/prefix{$}_', - expected: '/prefix{$}', - }, - { - name: 'with nested prefix', - to: '/params_/wildcard/prefix{$}_', - expected: '/params/wildcard/prefix{$}', - }, - { - name: 'with top-level suffix', - to: '/{$}suffix_', - expected: '/{$}suffix', - }, - { - name: 'with nested suffix', - to: '/params_/wildcard_/{$}suffix_', - expected: '/params/wildcard/{$}suffix', - }, - { - name: 'with top-level prefix + suffix', - to: '/prefix{$}suffix_', - expected: '/prefix{$}suffix', - }, - { - name: 'with nested prefix + suffix', - to: '/params/wildcard/prefix{$}suffix_', - expected: '/params/wildcard/prefix{$}suffix', - }, - ])('$name', ({ to, expected }) => { - const candidate = base + trimPathLeft(to) - const result = base + trimPathLeft(expected) - expect( - resolvePath({ - base, - to: candidate, - trailingSlash: 'never', - }), - ).toEqual(result) - }) - }) - - describe('named (prefix + suffix)', () => { - it.each([ - { name: 'regular top-level', to: '/$foo_', expected: '/$foo' }, - { - name: 'regular nested', - to: '/params/named_/$foo_', - expected: '/params/named/$foo', - }, - { - name: 'with top-level prefix', - to: '/prefix{$foo}_', - expected: '/prefix{$foo}', - }, - { - name: 'with nested prefix', - to: '/params_/named/prefix{$foo}_', - expected: '/params/named/prefix{$foo}', - }, - { - name: 'with top-level suffix', - to: '/{$foo}suffix_', - expected: '/{$foo}suffix', - }, - { - name: 'with nested suffix', - to: '/params_/named_/{$foo}suffix_', - expected: '/params/named/{$foo}suffix', - }, - { - name: 'with top-level prefix + suffix', - to: '/prefix{$foo}suffix_', - expected: '/prefix{$foo}suffix', - }, - { - name: 'with nested prefix + suffix', - to: '/params_/named_/prefix{$foo}suffix_', - expected: '/params/named/prefix{$foo}suffix', - }, - ])('$name', ({ to, expected }) => { - const candidate = base + trimPathLeft(to) - const result = base + trimPathLeft(expected) - expect( - resolvePath({ - base, - to: candidate, - trailingSlash: 'never', - }), - ).toEqual(result) - }) - }) - }, - ) - }) - - describe('interpolatePath', () => { - describe('regular usage', () => { - it.each([ - { - name: 'should interpolate the path', - path: '/users_/$id_', - params: { id: '123' }, - result: '/users/123', - }, - { - name: 'should interpolate the path with multiple params', - path: '/users/$id_/$name_', - params: { id: '123', name: 'tanner' }, - result: '/users/123/tanner', - }, - { - name: 'should interpolate the path with extra params', - path: '/users/$id_', - params: { id: '123', name: 'tanner' }, - result: '/users/123', - }, - { - name: 'should interpolate the path with missing params', - path: '/users/$id/$name_', - params: { id: '123' }, - result: '/users/123/undefined', - }, - { - name: 'should interpolate the path with missing params and extra params', - path: '/users/$id_', - params: { name: 'john' }, - result: '/users/undefined', - }, - { - name: 'should interpolate the path with the param being a number', - path: '/users/$id_', - params: { id: 123 }, - result: '/users/123', - }, - { - name: 'should interpolate the path with the param being a falsey number', - path: '/users/$id_', - params: { id: 0 }, - result: '/users/0', - }, - { - name: 'should interpolate the path with URI component encoding', - path: '/users/$id_', - params: { id: '?#@john+smith' }, - result: '/users/%3F%23%40john%2Bsmith', - }, - { - name: 'should interpolate the path without URI encoding characters in decodeCharMap', - path: '/users/$id_', - params: { id: '?#@john+smith' }, - result: '/users/%3F%23@john+smith', - decodeCharMap: new Map( - ['@', '+'].map((char) => [encodeURIComponent(char), char]), - ), - }, - { - name: 'should interpolate the path with the splat param at the end', - path: '/users/$_', - params: { _splat: '123' }, - result: '/users/123', - }, - { - name: 'should interpolate the path with a single named path param and the splat param at the end', - path: '/users/$username/$_', - params: { username: 'seancassiere', _splat: '123' }, - result: '/users/seancassiere/123', - }, - { - name: 'should interpolate the path with 2 named path params with the splat param at the end', - path: '/users/$username/$id_/$_', - params: { username: 'seancassiere', id: '123', _splat: '456' }, - result: '/users/seancassiere/123/456', - }, - { - name: 'should interpolate the path with multiple named path params with the splat param at the end', - path: '/$username/settings_/$repo_/$id_/$_', - params: { - username: 'sean-cassiere', - repo: 'my-repo', - id: '123', - _splat: '456', - }, - result: '/sean-cassiere/settings/my-repo/123/456', - }, - { - name: 'should interpolate the path with the splat param containing slashes', - path: '/users_/$', - params: { _splat: 'sean/cassiere' }, - result: '/users/sean/cassiere', - }, - ])('$name', ({ path, params, decodeCharMap, result }) => { - expect( - interpolatePath({ - path, - params, - decodeCharMap, - }).interpolatedPath, - ).toBe(result) - }) - }) - - describe('wildcard (prefix + suffix)', () => { - it.each([ - { - name: 'regular', - to: '/$_', - params: { _splat: 'bar/foo/me' }, - result: '/bar/foo/me', - }, - { - name: 'regular curly braces', - to: '/{$}_', - params: { _splat: 'bar/foo/me' }, - result: '/bar/foo/me', - }, - { - name: 'with prefix', - to: '/prefix{$}_', - params: { _splat: 'bar' }, - result: '/prefixbar', - }, - { - name: 'with suffix', - to: '/{$}-suffix_', - params: { _splat: 'bar' }, - result: '/bar-suffix', - }, - { - name: 'with prefix + suffix', - to: '/prefix{$}-suffix_', - params: { _splat: 'bar' }, - result: '/prefixbar-suffix', - }, - ])('$name', ({ to, params, result }) => { - expect( - interpolatePath({ - path: to, - params, - }).interpolatedPath, - ).toBe(result) - }) - }) - - describe('named params (prefix + suffix)', () => { - it.each([ - { - name: 'regular', - to: '/$foo_', - params: { foo: 'bar' }, - result: '/bar', - }, - { - name: 'regular curly braces', - to: '/{$foo}_', - params: { foo: 'bar' }, - result: '/bar', - }, - { - name: 'with prefix', - to: '/prefix{$bar}_', - params: { bar: 'baz' }, - result: '/prefixbaz', - }, - { - name: 'with suffix', - to: '/{$foo}.suffix_', - params: { foo: 'bar' }, - result: '/bar.suffix', - }, - { - name: 'with prefix and suffix', - to: '/prefix{$param}.suffix_', - params: { param: 'foobar' }, - result: '/prefixfoobar.suffix', - }, - ])('$name', ({ to, params, result }) => { - expect( - interpolatePath({ - path: to, - params, - }).interpolatedPath, - ).toBe(result) - }) - }) - - describe('should handle missing _splat parameter for', () => { - it.each([ - { - name: 'basic splat route', - path: '/hello/$_', - params: {}, - expectedResult: '/hello', - }, - { - name: 'splat route with prefix', - path: '/hello_/prefix{$}_', - params: {}, - expectedResult: '/hello/prefix', - }, - { - name: 'splat route with suffix', - path: '/hello/{$}suffix_', - params: {}, - expectedResult: '/hello/suffix', - }, - { - name: 'splat route with prefix and suffix', - path: '/hello/prefix{$}suffix_', - params: {}, - expectedResult: '/hello/prefixsuffix', - }, - ])('$name', ({ path, params, expectedResult }) => { - const result = interpolatePath({ - path, - params, - }) - expect(result.interpolatedPath).toBe(expectedResult) - expect(result.isMissingParams).toBe(true) - }) - }) - }) - - describe('matchPathname', () => { - describe('path param(s) matching', () => { - it.each([ - { - name: 'should not match since `to` does not match the input', - input: '/', - matchingOptions: { - to: '/users_', - }, - expectedMatchedParams: undefined, - }, - { - name: 'should match since `to` matches the input', - input: '/users', - matchingOptions: { - to: '/users_', - }, - expectedMatchedParams: {}, - }, - { - name: 'should match and return the named path params', - input: '/users/123', - matchingOptions: { - to: '/users/$id_', - }, - expectedMatchedParams: { id: '123' }, - }, - { - name: 'should match and return the the splat param', - input: '/users/123', - matchingOptions: { - to: '/users/$_', - }, - expectedMatchedParams: { - '*': '123', - _splat: '123', - }, - }, - { - name: 'should match and return the named path and splat params', - input: '/users/123/456', - matchingOptions: { - to: '/users_/$id_/$_', - }, - expectedMatchedParams: { - id: '123', - '*': '456', - _splat: '456', - }, - }, - { - name: 'should match and return the multiple named path params and splat param', - input: '/sean-cassiere/settings/my-repo/123/456', - matchingOptions: { - to: '/$username_/settings_/$repo_/$id_/$_', - }, - expectedMatchedParams: { - username: 'sean-cassiere', - repo: 'my-repo', - id: '123', - '*': '456', - _splat: '456', - }, - }, - { - name: 'should match and return the splat params when multiple subsequent segments are present', - input: '/docs/tanner/sean/manuel', - matchingOptions: { - to: '/docs/$_', - }, - expectedMatchedParams: { - '*': 'tanner/sean/manuel', - _splat: 'tanner/sean/manuel', - }, - }, - ])('$name', ({ input, matchingOptions, expectedMatchedParams }) => { - expect(matchPathname(input, matchingOptions)).toStrictEqual( - expectedMatchedParams, - ) - }) - }) - - describe('wildcard (prefix + suffix)', () => { - it.each([ - { - name: 'regular', - input: '/docs/foo/bar', - matchingOptions: { - to: '/docs/$_', - }, - expectedMatchedParams: { - '*': 'foo/bar', - _splat: 'foo/bar', - }, - }, - { - name: 'regular curly braces', - input: '/docs/foo/bar', - matchingOptions: { - to: '/docs_/{$}_', - }, - expectedMatchedParams: { - '*': 'foo/bar', - _splat: 'foo/bar', - }, - }, - { - name: 'with prefix', - input: '/docs/prefixbar/baz', - matchingOptions: { - to: '/docs_/prefix{$}_', - }, - expectedMatchedParams: { - '*': 'bar/baz', - _splat: 'bar/baz', - }, - }, - { - name: 'with suffix', - input: '/docs/bar/baz.suffix', - matchingOptions: { - to: '/docs/{$}.suffix_', - }, - expectedMatchedParams: { - '*': 'bar/baz', - _splat: 'bar/baz', - }, - }, - { - name: 'with prefix + suffix', - input: '/docs/prefixbar/baz-suffix', - matchingOptions: { - to: '/docs/prefix{$}-suffix_', - }, - expectedMatchedParams: { - '*': 'bar/baz', - _splat: 'bar/baz', - }, - }, - ])('$name', ({ input, matchingOptions, expectedMatchedParams }) => { - expect(matchPathname(input, matchingOptions)).toStrictEqual( - expectedMatchedParams, - ) - }) - }) - - describe('named params (prefix + suffix)', () => { - it.each([ - { - name: 'regular', - input: '/docs/foo', - matchingOptions: { - to: '/docs/$bar_', - }, - expectedMatchedParams: { - bar: 'foo', - }, - }, - { - name: 'regular curly braces', - input: '/docs/foo', - matchingOptions: { - to: '/docs/{$bar}_', - }, - expectedMatchedParams: { - bar: 'foo', - }, - }, - { - name: 'with prefix', - input: '/docs/prefixfoo', - matchingOptions: { - to: '/docs/prefix{$bar}_', - }, - expectedMatchedParams: { - bar: 'foo', - }, - }, - { - name: 'with suffix', - input: '/docs/foo.suffix', - matchingOptions: { - to: '/docs/{$bar}.suffix_', - }, - expectedMatchedParams: { - bar: 'foo', - }, - }, - { - name: 'with prefix + suffix', - input: '/docs/prefixfoobar-suffix', - matchingOptions: { - to: '/docs/prefix{$param}-suffix_', - }, - expectedMatchedParams: { - param: 'foobar', - }, - }, - ])('$name', ({ input, matchingOptions, expectedMatchedParams }) => { - expect(matchPathname(input, matchingOptions)).toStrictEqual( - expectedMatchedParams, - ) - }) - }) - }) - - describe('parsePathname', () => { - type ParsePathnameTestScheme = Array<{ - name: string - to: string | undefined - expected: Array - }> - - describe('regular usage', () => { - it.each([ - { - name: 'should handle pathname with a single segment', - to: '/foo_', - expected: [ - { type: SEGMENT_TYPE_PATHNAME, value: '/' }, - { type: SEGMENT_TYPE_PATHNAME, value: 'foo' }, - ], - }, - { - name: 'should handle pathname with multiple segments', - to: '/foo_/bar_/baz_', - expected: [ - { type: SEGMENT_TYPE_PATHNAME, value: '/' }, - { type: SEGMENT_TYPE_PATHNAME, value: 'foo' }, - { type: SEGMENT_TYPE_PATHNAME, value: 'bar' }, - { type: SEGMENT_TYPE_PATHNAME, value: 'baz' }, - ], - }, - { - name: 'should handle pathname with a trailing slash', - to: '/foo_/', - expected: [ - { type: SEGMENT_TYPE_PATHNAME, value: '/' }, - { type: SEGMENT_TYPE_PATHNAME, value: 'foo' }, - { type: SEGMENT_TYPE_PATHNAME, value: '/' }, - ], - }, - { - name: 'should handle named params', - to: '/foo_/$bar_', - expected: [ - { type: SEGMENT_TYPE_PATHNAME, value: '/' }, - { type: SEGMENT_TYPE_PATHNAME, value: 'foo' }, - { type: SEGMENT_TYPE_PARAM, value: '$bar' }, - ], - }, - { - name: 'should handle named params at the root', - to: '/$bar_', - expected: [ - { type: SEGMENT_TYPE_PATHNAME, value: '/' }, - { type: SEGMENT_TYPE_PARAM, value: '$bar' }, - ], - }, - { - name: 'should handle named params followed by a segment', - to: '/foo_/$bar_/baz_', - expected: [ - { type: SEGMENT_TYPE_PATHNAME, value: '/' }, - { type: SEGMENT_TYPE_PATHNAME, value: 'foo' }, - { type: SEGMENT_TYPE_PARAM, value: '$bar' }, - { type: SEGMENT_TYPE_PATHNAME, value: 'baz' }, - ], - }, - { - name: 'should handle multiple named params', - to: '/foo_/$bar_/$baz_/qux_/$quux_', - expected: [ - { type: SEGMENT_TYPE_PATHNAME, value: '/' }, - { type: SEGMENT_TYPE_PATHNAME, value: 'foo' }, - { type: SEGMENT_TYPE_PARAM, value: '$bar' }, - { type: SEGMENT_TYPE_PARAM, value: '$baz' }, - { type: SEGMENT_TYPE_PATHNAME, value: 'qux' }, - { type: SEGMENT_TYPE_PARAM, value: '$quux' }, - ], - }, - { - name: 'should handle splat params', - to: '/foo_/$_', - expected: [ - { type: SEGMENT_TYPE_PATHNAME, value: '/' }, - { type: SEGMENT_TYPE_PATHNAME, value: 'foo' }, - { type: SEGMENT_TYPE_WILDCARD, value: '$' }, - ], - }, - { - name: 'should handle splat params at the root', - to: '/$_', - expected: [ - { type: SEGMENT_TYPE_PATHNAME, value: '/' }, - { type: SEGMENT_TYPE_WILDCARD, value: '$' }, - ], - }, - ] satisfies ParsePathnameTestScheme)('$name', ({ to, expected }) => { - const result = parsePathname(to) - expect(result).toEqual(expected) - }) - }) - - describe('wildcard (prefix + suffix)', () => { - it.each([ - { - name: 'regular', - to: '/$_', - expected: [ - { type: SEGMENT_TYPE_PATHNAME, value: '/' }, - { type: SEGMENT_TYPE_WILDCARD, value: '$' }, - ], - }, - { - name: 'regular curly braces', - to: '/{$}_', - expected: [ - { type: SEGMENT_TYPE_PATHNAME, value: '/' }, - { type: SEGMENT_TYPE_WILDCARD, value: '$' }, - ], - }, - { - name: 'with prefix (regular text)', - to: '/foo{$}_', - expected: [ - { type: SEGMENT_TYPE_PATHNAME, value: '/' }, - { - type: SEGMENT_TYPE_WILDCARD, - value: '$', - prefixSegment: 'foo', - }, - ], - }, - { - name: 'with prefix + followed by special character', - to: '/foo.{$}_', - expected: [ - { type: SEGMENT_TYPE_PATHNAME, value: '/' }, - { - type: SEGMENT_TYPE_WILDCARD, - value: '$', - prefixSegment: 'foo.', - }, - ], - }, - { - name: 'with suffix', - to: '/{$}-foo_', - expected: [ - { type: SEGMENT_TYPE_PATHNAME, value: '/' }, - { - type: SEGMENT_TYPE_WILDCARD, - value: '$', - suffixSegment: '-foo', - }, - ], - }, - { - name: 'with prefix + suffix', - to: '/foo{$}-bar_', - expected: [ - { type: SEGMENT_TYPE_PATHNAME, value: '/' }, - { - type: SEGMENT_TYPE_WILDCARD, - value: '$', - prefixSegment: 'foo', - suffixSegment: '-bar', - }, - ], - }, - { - name: 'with prefix + followed by special character and a segment', - to: '/foo.{$}_/bar_', - expected: [ - { type: SEGMENT_TYPE_PATHNAME, value: '/' }, - { - type: SEGMENT_TYPE_WILDCARD, - value: '$', - prefixSegment: 'foo.', - }, - { type: SEGMENT_TYPE_PATHNAME, value: 'bar' }, - ], - }, - ] satisfies ParsePathnameTestScheme)('$name', ({ to, expected }) => { - const result = parsePathname(to) - expect(result).toEqual(expected) - }) - }) - - describe('named params (prefix + suffix)', () => { - it.each([ - { - name: 'regular', - to: '/$bar_', - expected: [ - { type: SEGMENT_TYPE_PATHNAME, value: '/' }, - { type: SEGMENT_TYPE_PARAM, value: '$bar' }, - ], - }, - { - name: 'regular curly braces', - to: '/{$bar}_', - expected: [ - { type: SEGMENT_TYPE_PATHNAME, value: '/' }, - { type: SEGMENT_TYPE_PARAM, value: '$bar' }, - ], - }, - { - name: 'with prefix (regular text)', - to: '/foo{$bar}_', - expected: [ - { type: SEGMENT_TYPE_PATHNAME, value: '/' }, - { - type: SEGMENT_TYPE_PARAM, - value: '$bar', - prefixSegment: 'foo', - }, - ], - }, - { - name: 'with prefix + followed by special character', - to: '/foo.{$bar}_', - expected: [ - { type: SEGMENT_TYPE_PATHNAME, value: '/' }, - { - type: SEGMENT_TYPE_PARAM, - value: '$bar', - prefixSegment: 'foo.', - }, - ], - }, - { - name: 'with suffix', - to: '/{$bar}.foo_', - expected: [ - { type: SEGMENT_TYPE_PATHNAME, value: '/' }, - { - type: SEGMENT_TYPE_PARAM, - value: '$bar', - suffixSegment: '.foo', - }, - ], - }, - { - name: 'with suffix + started by special character', - to: '/{$bar}.foo_', - expected: [ - { type: SEGMENT_TYPE_PATHNAME, value: '/' }, - { - type: SEGMENT_TYPE_PARAM, - value: '$bar', - suffixSegment: '.foo', - }, - ], - }, - { - name: 'with suffix + started by special character and followed by segment', - to: '/{$bar}.foo_/baz_', - expected: [ - { type: SEGMENT_TYPE_PATHNAME, value: '/' }, - { - type: SEGMENT_TYPE_PARAM, - value: '$bar', - suffixSegment: '.foo', - }, - { type: SEGMENT_TYPE_PATHNAME, value: 'baz' }, - ], - }, - { - name: 'with suffix + prefix', - to: '/foo{$bar}.baz_', - expected: [ - { type: SEGMENT_TYPE_PATHNAME, value: '/' }, - { - type: SEGMENT_TYPE_PARAM, - value: '$bar', - prefixSegment: 'foo', - suffixSegment: '.baz', - }, - ], - }, - ] satisfies ParsePathnameTestScheme)('$name', ({ to, expected }) => { - const result = parsePathname(to) - expect(result).toEqual(expected) - }) - }) - }) -}) From 813e90217a38ef3970f5c6f1a984102604d97097 Mon Sep 17 00:00:00 2001 From: Nico Lynzaad Date: Mon, 6 Oct 2025 04:57:58 +0200 Subject: [PATCH 02/25] add experimental nonnested path changes to generator --- packages/router-generator/src/config.ts | 2 + .../src/filesystem/physical/getRouteNodes.ts | 26 +- packages/router-generator/src/generator.ts | 39 ++- packages/router-generator/src/types.ts | 2 + packages/router-generator/src/utils.ts | 261 ++++++++++++++++-- 5 files changed, 300 insertions(+), 30 deletions(-) diff --git a/packages/router-generator/src/config.ts b/packages/router-generator/src/config.ts index 332549594eb..06922d84f70 100644 --- a/packages/router-generator/src/config.ts +++ b/packages/router-generator/src/config.ts @@ -54,6 +54,8 @@ export const configSchema = baseConfigSchema.extend({ .object({ // TODO: This has been made stable and is now "autoCodeSplitting". Remove in next major version. enableCodeSplitting: z.boolean().optional(), + // TODO: This resolves issues with non-nested paths in file-based routing. To be made default in next major version. + nonNestedPaths: z.boolean().optional(), }) .optional(), plugins: z.array(z.custom()).optional(), diff --git a/packages/router-generator/src/filesystem/physical/getRouteNodes.ts b/packages/router-generator/src/filesystem/physical/getRouteNodes.ts index 2bafded507f..8bc3dad7f83 100644 --- a/packages/router-generator/src/filesystem/physical/getRouteNodes.ts +++ b/packages/router-generator/src/filesystem/physical/getRouteNodes.ts @@ -34,6 +34,7 @@ export async function getRouteNodes( | 'disableLogging' | 'routeToken' | 'indexToken' + | 'experimental' >, root: string, ): Promise { @@ -131,7 +132,12 @@ export async function getRouteNodes( } else if (fullPath.match(/\.(tsx|ts|jsx|js)$/)) { const filePath = replaceBackslash(path.join(dir, dirent.name)) const filePathNoExt = removeExt(filePath) - let routePath = determineInitialRoutePath(filePathNoExt) + const { + routePath: initialRoutePath, + cleanedRoutePath: originalRoutePath, + isExperimentalNonNestedPath, + } = determineInitialRoutePath(filePathNoExt, config) + let routePath = initialRoutePath if (routeFilePrefix) { routePath = routePath.replaceAll(routeFilePrefix, '') @@ -143,7 +149,11 @@ export async function getRouteNodes( throw new Error(errorMessage) } - const meta = getRouteMeta(routePath, config) + const meta = getRouteMeta( + routePath, + config, + isExperimentalNonNestedPath, + ) const variableName = meta.variableName let routeType: FsRouteType = meta.fsRouteType @@ -192,6 +202,8 @@ export async function getRouteNodes( routePath, variableName, _fsRouteType: routeType, + _isExperimentalNonNestedPath: isExperimentalNonNestedPath, + originalRoutePath, }) } }), @@ -220,11 +232,13 @@ export async function getRouteNodes( * * @param routePath - The determined initial routePath. * @param config - The user configuration object. + * @param isExperimentalNonNestedPath - true if route for which meta is generated is a non-nested path. * @returns An object containing the type of the route and the variable name derived from the route path. */ export function getRouteMeta( routePath: string, - config: Pick, + config: Pick, + isExperimentalNonNestedPath?: boolean, ): { // `__root` is can be more easily determined by filtering down to routePath === /${rootPathId} // `pathless` is needs to determined after `lazy` has been cleaned up from the routePath @@ -263,7 +277,11 @@ export function getRouteMeta( fsRouteType = 'errorComponent' } - const variableName = routePathToVariable(routePath) + const variableName = routePathToVariable( + routePath, + config as Config, + isExperimentalNonNestedPath, + ) return { fsRouteType, variableName } } diff --git a/packages/router-generator/src/generator.ts b/packages/router-generator/src/generator.ts index 956b1f7a4ec..3681ea30806 100644 --- a/packages/router-generator/src/generator.ts +++ b/packages/router-generator/src/generator.ts @@ -397,7 +397,7 @@ export class Generator { } for (const node of routeFileResult) { - Generator.handleNode(node, acc) + Generator.handleNode(node, acc, this.config) } this.crawlingResult = { rootRouteNode, routeFileResult, acc } @@ -676,14 +676,14 @@ export class Generator { if (!config.disableTypes) { fileRoutesByFullPath = [ `export interface FileRoutesByFullPath { -${[...createRouteNodesByFullPath(acc.routeNodes).entries()] +${[...createRouteNodesByFullPath(acc.routeNodes, this.config).entries()] .filter(([fullPath]) => fullPath) .map(([fullPath, routeNode]) => { return `'${fullPath}': typeof ${getResolvedRouteNodeVariableName(routeNode)}` })} }`, `export interface FileRoutesByTo { -${[...createRouteNodesByTo(acc.routeNodes).entries()] +${[...createRouteNodesByTo(acc.routeNodes, this.config).entries()] .filter(([to]) => to) .map(([to, routeNode]) => { return `'${to}': typeof ${getResolvedRouteNodeVariableName(routeNode)}` @@ -699,7 +699,12 @@ ${[...createRouteNodesById(acc.routeNodes).entries()].map(([id, routeNode]) => { fileRoutesByFullPath: FileRoutesByFullPath fullPaths: ${ acc.routeNodes.length > 0 - ? [...createRouteNodesByFullPath(acc.routeNodes).keys()] + ? [ + ...createRouteNodesByFullPath( + acc.routeNodes, + this.config, + ).keys(), + ] .filter((fullPath) => fullPath) .map((fullPath) => `'${fullPath}'`) .join('|') @@ -708,7 +713,7 @@ fullPaths: ${ fileRoutesByTo: FileRoutesByTo to: ${ acc.routeNodes.length > 0 - ? [...createRouteNodesByTo(acc.routeNodes).keys()] + ? [...createRouteNodesByTo(acc.routeNodes, this.config).keys()] .filter((to) => to) .map((to) => `'${to}'`) .join('|') @@ -726,6 +731,7 @@ ${acc.routeTree.map((child) => `${child.variableName}Route: typeof ${getResolved module: this.targetTemplate.fullPkg, interfaceName: 'FileRoutesByPath', routeNodes: sortedRouteNodes, + config: this.config, }) } @@ -1185,13 +1191,23 @@ ${acc.routeTree.map((child) => `${child.variableName}Route: typeof ${getResolved return this.crawlingResult } - private static handleNode(node: RouteNode, acc: HandleNodeAccumulator) { + private static handleNode( + node: RouteNode, + acc: HandleNodeAccumulator, + config?: Config, + ) { // Do not remove this as we need to set the lastIndex to 0 as it // is necessary to reset the regex's index when using the global flag // otherwise it might not match the next time it's used resetRegex(this.routeGroupPatternRegex) - let parentRoute = hasParentRoute(acc.routeNodes, node, node.routePath) + let parentRoute = hasParentRoute( + acc.routeNodes, + node, + node.routePath, + config?.experimental?.nonNestedPaths, + node.originalRoutePath, + ) // if the parent route is a virtual parent route, we need to find the real parent route if (parentRoute?.isVirtualParentRoute && parentRoute.children?.length) { @@ -1220,7 +1236,11 @@ ${acc.routeTree.map((child) => `${child.variableName}Route: typeof ${getResolved split.every((part) => this.routeGroupPatternRegex.test(part)) node.cleanedPath = removeGroups( - removeUnderscores(removeLayoutSegments(node.path)) ?? '', + removeUnderscores( + removeLayoutSegments(node.path), + config, + node._isExperimentalNonNestedPath, + ) ?? '', ) if ( @@ -1262,6 +1282,7 @@ ${acc.routeTree.map((child) => `${child.variableName}Route: typeof ${getResolved _fsRouteType: 'static', }, acc, + config, ) } return @@ -1308,7 +1329,7 @@ ${acc.routeTree.map((child) => `${child.variableName}Route: typeof ${getResolved node.path = determineNodePath(node) } - this.handleNode(parentNode, acc) + this.handleNode(parentNode, acc, config) } else { anchorRoute.children = anchorRoute.children ?? [] anchorRoute.children.push(node) diff --git a/packages/router-generator/src/types.ts b/packages/router-generator/src/types.ts index e29832f2c56..d61f2cbd370 100644 --- a/packages/router-generator/src/types.ts +++ b/packages/router-generator/src/types.ts @@ -4,6 +4,7 @@ export type RouteNode = { variableName: string _fsRouteType: FsRouteType routePath?: string + originalRoutePath?: string cleanedPath?: string path?: string isNonPath?: boolean @@ -13,6 +14,7 @@ export type RouteNode = { children?: Array parent?: RouteNode createFileRouteProps?: Set + _isExperimentalNonNestedPath?: boolean } export interface GetRouteNodesResult { diff --git a/packages/router-generator/src/utils.ts b/packages/router-generator/src/utils.ts index 3754f3288b4..dde7e0384cb 100644 --- a/packages/router-generator/src/utils.ts +++ b/packages/router-generator/src/utils.ts @@ -54,7 +54,12 @@ export function removeTrailingSlash(s: string) { const BRACKET_CONTENT_RE = /\[(.*?)\]/g -export function determineInitialRoutePath(routePath: string) { +const BRACKET_CONTENT_RE_NON_NESTED = /\[([^\]]*[^_\]]|)\]/g + +export function determineInitialRoutePath( + routePath: string, + config?: Pick, +) { const DISALLOWED_ESCAPE_CHARS = new Set([ '/', '\\', @@ -69,6 +74,8 @@ export function determineInitialRoutePath(routePath: string) { '$', '%', ]) + const useExperimentalNonNestedPaths = + config?.experimental?.nonNestedPaths ?? false const parts = routePath.split(/(? { + // If the match is an underscore, remove it. + // Otherwise, the match was a bracketed section, so return it unchanged. + return match === '_' ? '' : match + }) + .replace(BRACKET_CONTENT_RE, '$1') + + finalRoutePath = cleanPath(finalEscapedPath) || '' + } + } + + return { + routePath: finalRoutePath, + isExperimentalNonNestedPath, + cleanedRoutePath, + } +} + +export function escapeNonNestedPaths( + routePath: string, + config?: Pick, +) { + if (routePath === `/${rootPathId}`) return routePath + + // The regex finds either bracketed content or an underscore + const routeTokenToExclude = + config?.routeToken.slice(-1) === '_' + ? config.routeToken.slice(0, -1) + : config?.routeToken + + const regex = new RegExp(`_(?=\\/)|(? { + // If the match is an underscore, remove it. + // Otherwise, the match was a bracketed section, so return it unchanged. + return match === '_' ? '' : match + }) + .replace(BRACKET_CONTENT_RE, '$1') + + return cleanPath(cleanedRoutePath) || '' } export function replaceBackslash(s: string) { return s.replaceAll(/\\/gi, '/') } -export function routePathToVariable(routePath: string): string { +export function routePathToVariable( + routePath: string, + config?: Pick, + isExperimentalNonNestedPath?: boolean, +): string { const toVariableSafeChar = (char: string): string => { if (/[a-zA-Z0-9_]/.test(char)) { return char // Keep alphanumeric characters and underscores as is @@ -135,7 +213,7 @@ export function routePathToVariable(routePath: string): string { } return ( - removeUnderscores(routePath) + removeUnderscores(routePath, config, isExperimentalNonNestedPath) ?.replace(/\/\$\//g, '/splat/') .replace(/\$$/g, 'splat') .replace(/\$\{\$\}/g, 'splat') @@ -151,8 +229,35 @@ export function routePathToVariable(routePath: string): string { ) } -export function removeUnderscores(s?: string) { - return s?.replaceAll(/(^_|_$)/gi, '').replaceAll(/(\/_|_\/)/gi, '/') +export function removeUnderscores( + s?: string, + config?: Pick, + isExperimentalNonNestedPath?: boolean, +) { + if (!s) return s + + if (!config?.experimental?.nonNestedPaths) { + return s.replaceAll(/(^_|_$)/gi, '').replaceAll(/(\/_|_\/)/gi, '/') + } + + const parts = s.split('/') + + if (parts.length === 0) return s + + // Escape any remaining characters that are in square brackets + const escapedParts = parts.map((part) => { + // Since this split segment is safe at this point, we can + // remove the brackets and replace them with the content inside + if (!isExperimentalNonNestedPath && !s.endsWith(`/${config.routeToken}`)) { + return part.replaceAll(/(^_)/gi, '') + } + + return part.replaceAll(/(^_|_$)/gi, '') + }) + + const final = cleanPath(`/${escapedParts.join('/')}`) || '' + + return final } export function capitalize(s: string) { @@ -287,7 +392,23 @@ export function hasParentRoute( routes: Array, node: RouteNode, routePathToCheck: string | undefined, + useExperimentalNonNestedPaths?: boolean, + originalRoutePathToCheck?: string, ): RouteNode | null { + const getNonNestedSegments = (routePath: string) => { + const regex = /_(?=\/|$)/g + + return [...routePath.matchAll(regex)] + .filter((match) => { + const beforeStr = routePath.substring(0, match.index) + const openBrackets = (beforeStr.match(/\[/g) || []).length + const closeBrackets = (beforeStr.match(/\]/g) || []).length + return openBrackets === closeBrackets + }) + .map((match) => routePath.substring(0, match.index + 1)) + .reverse() + } + if (!routePathToCheck || routePathToCheck === '/') { return null } @@ -297,7 +418,43 @@ export function hasParentRoute( (d) => d.variableName, ]).filter((d) => d.routePath !== `/${rootPathId}`) - for (const route of sortedNodes) { + const filteredNodes = + useExperimentalNonNestedPaths && node._isExperimentalNonNestedPath + ? [] + : [...sortedNodes] + + if (useExperimentalNonNestedPaths && node._isExperimentalNonNestedPath) { + const nonNestedSegments = getNonNestedSegments( + originalRoutePathToCheck ?? '', + ) + + for (const route of sortedNodes) { + if (route.routePath === '/') continue + + if ( + routePathToCheck.startsWith(`${route.routePath}/`) && + route._isExperimentalNonNestedPath && + route.routePath !== routePathToCheck + ) { + return route + } + + if ( + nonNestedSegments.find((seg) => seg === `${route.routePath}_`) || + !( + route._fsRouteType === 'pathless_layout' || + route._fsRouteType === 'layout' || + route._fsRouteType === '__root' + ) + ) { + continue + } + + filteredNodes.push(route) + } + } + + for (const route of filteredNodes) { if (route.routePath === '/') continue if ( @@ -312,7 +469,13 @@ export function hasParentRoute( segments.pop() // Remove the last segment const parentRoutePath = segments.join('/') - return hasParentRoute(routes, node, parentRoutePath) + return hasParentRoute( + routes, + node, + parentRoutePath, + useExperimentalNonNestedPaths, + originalRoutePathToCheck, + ) } /** @@ -354,9 +517,16 @@ export const inferPath = (routeNode: RouteNode): string => { /** * Infers the full path for use by TS */ -export const inferFullPath = (routeNode: RouteNode): string => { +export const inferFullPath = ( + routeNode: RouteNode, + config?: Pick, +): string => { const fullPath = removeGroups( - removeUnderscores(removeLayoutSegments(routeNode.routePath)) ?? '', + removeUnderscores( + removeLayoutSegments(routeNode.routePath), + config, + routeNode._isExperimentalNonNestedPath, + ) ?? '', ) return routeNode.cleanedPath === '/' ? fullPath : fullPath.replace(/\/$/, '') @@ -367,9 +537,13 @@ export const inferFullPath = (routeNode: RouteNode): string => { */ export const createRouteNodesByFullPath = ( routeNodes: Array, + config?: Pick, ): Map => { return new Map( - routeNodes.map((routeNode) => [inferFullPath(routeNode), routeNode]), + routeNodes.map((routeNode) => [ + inferFullPath(routeNode, config), + routeNode, + ]), ) } @@ -378,10 +552,11 @@ export const createRouteNodesByFullPath = ( */ export const createRouteNodesByTo = ( routeNodes: Array, + config?: Pick, ): Map => { return new Map( dedupeBranchesAndIndexRoutes(routeNodes).map((routeNode) => [ - inferTo(routeNode), + inferTo(routeNode, config), routeNode, ]), ) @@ -404,8 +579,11 @@ export const createRouteNodesById = ( /** * Infers to path */ -export const inferTo = (routeNode: RouteNode): string => { - const fullPath = inferFullPath(routeNode) +export const inferTo = ( + routeNode: RouteNode, + config?: Pick, +): string => { + const fullPath = inferFullPath(routeNode, config) if (fullPath === '/') return fullPath @@ -444,7 +622,7 @@ export function checkRouteFullPathUniqueness( config: Config, ) { const routes = _routes.map((d) => { - const inferredFullPath = inferFullPath(d) + const inferredFullPath = inferFullPath(d, config) return { ...d, inferredFullPath } }) @@ -581,6 +759,7 @@ export function buildFileRoutesByPathInterface(opts: { routeNodes: Array module: string interfaceName: string + config?: Pick }): string { return `declare module '${opts.module}' { interface ${opts.interfaceName} { @@ -594,7 +773,7 @@ export function buildFileRoutesByPathInterface(opts: { return `'${filePathId}': { id: '${filePathId}' path: '${inferPath(routeNode)}' - fullPath: '${inferFullPath(routeNode)}' + fullPath: '${inferFullPath(routeNode, opts.config)}' preLoaderRoute: ${preloaderRoute} parentRoute: typeof ${parent} }` @@ -647,3 +826,51 @@ export function getImportForRouteNode( ], } satisfies ImportDeclaration } + +/** + * Used to validate if a route is a pathless layout route + * @param normalizedRoutePath Normalized route path, i.e `/foo/_layout/route.tsx` and `/foo._layout.route.tsx` to `/foo/_layout/route` + * @param config The `router-generator` configuration object + * @returns Boolean indicating if the route is a pathless layout route + */ +function isValidNonNestedPath( + normalizedRoutePath: string, + config?: Pick, +): boolean { + if (!config?.experimental?.nonNestedPaths) { + return false + } + + const segments = normalizedRoutePath.split('/').filter(Boolean) + + if (segments.length === 0) { + return false + } + + const lastRouteSegment = segments[segments.length - 1]! + + // If segment === __root, then exit as false + if (lastRouteSegment === rootPathId) { + return false + } + + if ( + lastRouteSegment !== config.indexToken && + lastRouteSegment !== config.routeToken && + lastRouteSegment.endsWith('_') + ) { + return true + } + + for (const segment of segments.slice(0, -1).reverse()) { + if (segment.endsWith('_') && segment !== config.routeToken) { + return true + } + + if (segment === config.routeToken) { + return false + } + } + + return false +} From bf9e788fd6f7bd1b442b39870d872a7c2c987d95 Mon Sep 17 00:00:00 2001 From: Nico Lynzaad Date: Mon, 6 Oct 2025 04:58:35 +0200 Subject: [PATCH 03/25] update router-generator unit tests --- .../deny-route-group-config.nonnested.test.ts | 73 ++ .../tests/generator.nonnested.test.ts | 329 +++++++++ .../routeTree.nonnested.snapshot.ts | 61 ++ .../routeTree.nonnested.snapshot.ts | 98 +++ .../routeTree.nonnested.snapshot.ts | 226 +++++++ .../routeTree.nonnested.snapshot.ts | 137 ++++ .../routeTree.nonnested.snapshot.ts | 78 +++ .../routeTree.nonnested.snapshot.ts | 207 ++++++ .../routeTree.nonnested.snapshot.ts | 155 +++++ .../flat/routeTree.nonnested.snapshot.ts | 247 +++++++ .../routeTree.nonnested.snapshot.ts | 637 ++++++++++++++++++ .../routeTree.nonnested.snapshot.ts | 274 ++++++++ .../routeTree.nonnested.snapshot.ts | 331 +++++++++ .../tests.nonnested.test-d.ts | 539 +++++++++++++++ .../tests.test-d.ts | 7 +- .../routeTree.nonnested.snapshot.ts | 247 +++++++ .../tests.nonnested.test-d.ts | 539 +++++++++++++++ .../routeTree.nonnested.snapshot.ts | 112 +++ .../routeTree.nonnested.snapshot.ts | 77 +++ .../routeTree.nonnested.snapshot.ts | 131 ++++ .../routeTree.nonnested.generated.snapshot.ts | 35 + .../only-root/routeTree.nonnested.snapshot.ts | 35 + .../routeTree.nonnested.snapshot.ts | 87 +++ .../routeTree.nonnested.snapshot.ts | 129 ++++ .../routeTree.nonnested.snapshot.ts | 345 ++++++++++ .../routeTree.nonnested.snapshot.ts | 77 +++ .../routeTree.nonnested.snapshot.ts | 77 +++ .../routeTree.nonnested.snapshot.ts | 77 +++ .../routeTree.nonnested.snapshot.js | 49 ++ .../types-disabled/routes/__root.tsx | 2 + .../generator/types-disabled/routes/index.tsx | 2 + .../generator/types-disabled/routes/posts.tsx | 2 + .../types-disabled/routes/posts/$postId.tsx | 2 + .../types-disabled/routes/users.$userId.tsx | 2 + .../routeTree.nonnested.snapshot.ts | 292 ++++++++ .../routeTree.nonnested.snapshot.ts | 292 ++++++++ .../routeTree.nonnested.snapshot.ts | 121 ++++ .../virtual/routeTree.nonnested.snapshot.ts | 292 ++++++++ packages/router-generator/tests/utils.test.ts | 42 +- packages/router-generator/vite.config.ts | 5 +- 40 files changed, 6459 insertions(+), 11 deletions(-) create mode 100644 packages/router-generator/tests/deny-route-group-config.nonnested.test.ts create mode 100644 packages/router-generator/tests/generator.nonnested.test.ts create mode 100644 packages/router-generator/tests/generator/append-and-prepend/routeTree.nonnested.snapshot.ts create mode 100644 packages/router-generator/tests/generator/custom-scaffolding/routeTree.nonnested.snapshot.ts create mode 100644 packages/router-generator/tests/generator/custom-tokens/routeTree.nonnested.snapshot.ts create mode 100644 packages/router-generator/tests/generator/dot-escaped/routeTree.nonnested.snapshot.ts create mode 100644 packages/router-generator/tests/generator/export-variations/routeTree.nonnested.snapshot.ts create mode 100644 packages/router-generator/tests/generator/file-modification-verboseFileRoutes-false/routeTree.nonnested.snapshot.ts create mode 100644 packages/router-generator/tests/generator/file-modification-verboseFileRoutes-true/routeTree.nonnested.snapshot.ts create mode 100644 packages/router-generator/tests/generator/flat/routeTree.nonnested.snapshot.ts create mode 100644 packages/router-generator/tests/generator/nested-layouts/routeTree.nonnested.snapshot.ts create mode 100644 packages/router-generator/tests/generator/nested-route-groups-with-layouts-before-physical/routeTree.nonnested.snapshot.ts create mode 100644 packages/router-generator/tests/generator/nested-verboseFileRoutes-false/routeTree.nonnested.snapshot.ts create mode 100644 packages/router-generator/tests/generator/nested-verboseFileRoutes-false/tests.nonnested.test-d.ts create mode 100644 packages/router-generator/tests/generator/nested-verboseFileRoutes-true/routeTree.nonnested.snapshot.ts create mode 100644 packages/router-generator/tests/generator/nested-verboseFileRoutes-true/tests.nonnested.test-d.ts create mode 100644 packages/router-generator/tests/generator/no-duplicate-route-segment/routeTree.nonnested.snapshot.ts create mode 100644 packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts create mode 100644 packages/router-generator/tests/generator/numbers-in-path/routeTree.nonnested.snapshot.ts create mode 100644 packages/router-generator/tests/generator/only-root/routeTree.nonnested.generated.snapshot.ts create mode 100644 packages/router-generator/tests/generator/only-root/routeTree.nonnested.snapshot.ts create mode 100644 packages/router-generator/tests/generator/path-above-route-in-group/routeTree.nonnested.snapshot.ts create mode 100644 packages/router-generator/tests/generator/prefix-suffix/routeTree.nonnested.snapshot.ts create mode 100644 packages/router-generator/tests/generator/route-groups/routeTree.nonnested.snapshot.ts create mode 100644 packages/router-generator/tests/generator/routeFileIgnore/routeTree.nonnested.snapshot.ts create mode 100644 packages/router-generator/tests/generator/routeFilePrefix/routeTree.nonnested.snapshot.ts create mode 100644 packages/router-generator/tests/generator/single-level/routeTree.nonnested.snapshot.ts create mode 100644 packages/router-generator/tests/generator/types-disabled/routeTree.nonnested.snapshot.js create mode 100644 packages/router-generator/tests/generator/virtual-config-file-default-export/routeTree.nonnested.snapshot.ts create mode 100644 packages/router-generator/tests/generator/virtual-config-file-named-export/routeTree.nonnested.snapshot.ts create mode 100644 packages/router-generator/tests/generator/virtual-inside-nested/routeTree.nonnested.snapshot.ts create mode 100644 packages/router-generator/tests/generator/virtual/routeTree.nonnested.snapshot.ts diff --git a/packages/router-generator/tests/deny-route-group-config.nonnested.test.ts b/packages/router-generator/tests/deny-route-group-config.nonnested.test.ts new file mode 100644 index 00000000000..fe5d9e1eec1 --- /dev/null +++ b/packages/router-generator/tests/deny-route-group-config.nonnested.test.ts @@ -0,0 +1,73 @@ +import { join } from 'node:path' +import { describe, expect, it } from 'vitest' + +import { Generator, getConfig } from '../src' +import type { Config } from '../src' + +function makeFolderDir(folder: string) { + return join(process.cwd(), 'tests', 'deny-route-group-config', folder) +} + +function setupConfig( + folder: string, + inlineConfig: Partial> = {}, +) { + const { generatedRouteTree = '/routeTree.nonnested.gen.ts', ...rest } = + inlineConfig + const dir = makeFolderDir(folder) + + const config = getConfig({ + disableLogging: true, + routesDirectory: dir, + generatedRouteTree: dir + generatedRouteTree, + experimental: { + nonNestedPaths: true, + }, + ...rest, + }) + return config +} + +type TestCases = Array<{ + folder: string + expectedError: string +}> + +describe('deny-route-group-config throws', () => { + it.each([ + { + folder: 'flat-flat', + expectedError: + 'A route configuration for a route group was found at `(group).tsx`. This is not supported. Did you mean to use a layout/pathless route instead?', + }, + { + folder: 'nested-flat', + expectedError: + 'A route configuration for a route group was found at `(group).tsx`. This is not supported. Did you mean to use a layout/pathless route instead?', + }, + { + folder: 'flat-nested', + expectedError: + 'A route configuration for a route group was found at `nested/(group).tsx`. This is not supported. Did you mean to use a layout/pathless route instead?', + }, + { + folder: 'nested-nested', + expectedError: + 'A route configuration for a route group was found at `nested/(group).tsx`. This is not supported. Did you mean to use a layout/pathless route instead?', + }, + ] satisfies TestCases)( + 'should throw an error for the folder: $folder', + async ({ folder, expectedError }) => { + const config = setupConfig(folder) + const folderRoot = makeFolderDir(folder) + + const generator = new Generator({ config, root: folderRoot }) + try { + await generator.run() + } catch (error) { + expect(error).toBeInstanceOf(Error) + expect((error as Error).message.startsWith(expectedError)).toBeTruthy() + } + }, + ) +}) diff --git a/packages/router-generator/tests/generator.nonnested.test.ts b/packages/router-generator/tests/generator.nonnested.test.ts new file mode 100644 index 00000000000..d74f12e8fca --- /dev/null +++ b/packages/router-generator/tests/generator.nonnested.test.ts @@ -0,0 +1,329 @@ +import fs from 'node:fs/promises' +import { dirname, join, relative } from 'node:path' +import { describe, expect, it } from 'vitest' + +import { + index, + layout, + physical, + rootRoute, + route, +} from '@tanstack/virtual-file-routes' +import { Generator, getConfig } from '../src' +import type { Config } from '../src' + +function makeFolderDir(folder: string) { + return join(process.cwd(), 'tests', 'generator', folder) +} + +async function readDir(...paths: Array) { + const folders = await fs.readdir( + join(process.cwd(), 'tests', 'generator', ...paths), + ) + return folders +} + +async function traverseDirectory( + dir: string, + handleFile: (filePath: string) => void | Promise, +) { + const files = await fs.readdir(dir, { withFileTypes: true }) + + for (const file of files) { + const filePath = join(dir, file.name) + + if (file.isDirectory()) { + await traverseDirectory(filePath, handleFile) + } else { + await handleFile(filePath) + } + } +} + +function setupConfig( + folder: string, + inlineConfig: Partial> = {}, +) { + const { generatedRouteTree = '/routeTree.nonnested.gen.ts', ...rest } = + inlineConfig + const dir = makeFolderDir(folder) + + const config = getConfig({ + disableLogging: true, + routesDirectory: dir + '/routes', + generatedRouteTree: dir + generatedRouteTree, + experimental: { + nonNestedPaths: true, + }, + ...rest, + }) + return config +} + +async function getRouteTreeFileText(config: Config) { + const location = config.generatedRouteTree + const text = await fs.readFile(location, 'utf-8') + return text +} + +function rewriteConfigByFolderName(folderName: string, config: Config) { + switch (folderName) { + case 'append-and-prepend': + config.routeTreeFileHeader = ['// prepend1', '// prepend2'] + config.routeTreeFileFooter = ['// append1', '// append2'] + break + case 'no-formatted-route-tree': + config.enableRouteTreeFormatting = false + break + case 'custom-tokens': + config.indexToken = '_1nd3x' + config.routeToken = '_r0ut3_' + break + case 'virtual': + { + const virtualRouteConfig = rootRoute('root.tsx', [ + index('index.tsx'), + route('$lang', [index('pages.tsx')]), + layout('layout.tsx', [ + route('/dashboard', 'db/dashboard.tsx', [ + index('db/dashboard-index.tsx'), + route('/invoices', 'db/dashboard-invoices.tsx', [ + index('db/invoices-index.tsx'), + route('$id', 'db/invoice-detail.tsx'), + ]), + ]), + physical('/hello', 'subtree'), + ]), + ]) + config.virtualRouteConfig = virtualRouteConfig + } + break + case 'virtual-config-file-named-export': + config.virtualRouteConfig = './routes.ts' + break + case 'virtual-config-file-default-export': + config.virtualRouteConfig = './routes.ts' + break + case 'types-disabled': + config.disableTypes = true + config.generatedRouteTree = + makeFolderDir(folderName) + '/routeTree.nonnested.gen.js' + break + case 'custom-scaffolding': + config.customScaffolding = { + routeTemplate: [ + 'import * as React from "react";\n', + '%%tsrImports%%\n\n', + '%%tsrExportStart%%{\n component: RouteComponent\n }%%tsrExportEnd%%\n\n', + 'function RouteComponent() { return "Hello %%tsrPath%%!" };\n', + ].join(''), + lazyRouteTemplate: [ + 'import React, { useState } from "react";\n', + '%%tsrImports%%\n\n', + '%%tsrExportStart%%{\n component: RouteComponent\n }%%tsrExportEnd%%\n\n', + 'function RouteComponent() { return "Hello %%tsrPath%%!" };\n', + ].join(''), + } + break + case 'file-modification-verboseFileRoutes-true': + config.verboseFileRoutes = true + break + case 'file-modification-verboseFileRoutes-false': + config.verboseFileRoutes = false + break + // these two folders contain type tests which are executed separately + case 'nested-verboseFileRoutes-true': + config.verboseFileRoutes = true + break + case 'nested-verboseFileRoutes-false': + config.verboseFileRoutes = false + break + case 'routeFileIgnore': + config.routeFileIgnorePattern = 'ignoredPattern' + config.routeFileIgnorePrefix = 'imIgnored' + break + case 'routeFilePrefix': + config.routeFileIgnorePattern = 'ignoredPattern' + config.routeFilePrefix = 'r&' + break + default: + break + } +} + +async function preprocess(folderName: string) { + if (folderName.startsWith('file-modification')) { + const templateVerbosePath = join( + makeFolderDir(folderName), + 'template-verbose.tsx', + ) + const templatePath = join(makeFolderDir(folderName), 'template.tsx') + const lazyTemplatePath = join( + makeFolderDir(folderName), + 'template.lazy.tsx', + ) + + const makeRoutePath = (file: string) => + join(makeFolderDir(folderName), 'routes', '(test)', file) + const makeEmptyFile = async (file: string) => { + const fh = await fs.open(makeRoutePath(file), 'w') + await fh.close() + } + + await fs.copyFile(templateVerbosePath, makeRoutePath('foo.bar.tsx')) + await fs.copyFile(templatePath, makeRoutePath('foo.tsx')) + await fs.copyFile(lazyTemplatePath, makeRoutePath('initiallyLazy.tsx')) + await fs.copyFile(templatePath, makeRoutePath('bar.lazy.tsx')) + await makeEmptyFile('initiallyEmpty.tsx') + await makeEmptyFile('initiallyEmpty.lazy.tsx') + } else if (folderName === 'custom-scaffolding') { + const makeEmptyFile = async (...file: Array) => { + const filePath = join(makeFolderDir(folderName), 'routes', ...file) + const dir = dirname(filePath) + await fs.mkdir(dir, { recursive: true }) + const fh = await fs.open(filePath, 'w') + await fh.close() + } + + await makeEmptyFile('__root.tsx') + await makeEmptyFile('index.tsx') + await makeEmptyFile('foo.lazy.tsx') + await makeEmptyFile('api', 'bar.tsx') + } +} + +async function postprocess(folderName: string) { + switch (folderName) { + case 'file-modification-verboseFileRoutes-false': + case 'file-modification-verboseFileRoutes-true': { + const routeFiles = await readDir(folderName, 'routes', '(test)') + await Promise.all( + routeFiles + .filter((r) => r.endsWith('.tsx')) + .map(async (routeFile) => { + const text = await fs.readFile( + join(makeFolderDir(folderName), 'routes', '(test)', routeFile), + 'utf-8', + ) + await expect(text).toMatchFileSnapshot( + join('generator', folderName, 'snapshots', routeFile), + ) + }), + ) + break + } + case 'custom-scaffolding': { + const startDir = join(makeFolderDir(folderName), 'routes') + await traverseDirectory(startDir, async (filePath) => { + const relativePath = relative(startDir, filePath) + if (filePath.endsWith('.tsx')) { + await expect( + await fs.readFile(filePath, 'utf-8'), + ).toMatchFileSnapshot( + join('generator', folderName, 'snapshots', relativePath), + ) + } + }) + } + } +} + +function shouldThrow(folderName: string) { + if (folderName === 'duplicate-fullPath') { + return `Conflicting configuration paths were found for the following routes: "/", "/".` + } + return undefined +} + +describe('generator works', async () => { + const folderNames = await readDir() + + it.each(folderNames.map((folder) => [folder]))( + 'should wire-up the routes for a "%s" tree', + async (folderName) => { + const folderRoot = makeFolderDir(folderName) + + const config = await setupConfig(folderName) + + rewriteConfigByFolderName(folderName, config) + + await preprocess(folderName) + const generator = new Generator({ config, root: folderRoot }) + const error = shouldThrow(folderName) + if (error) { + try { + await generator.run() + } catch (e) { + expect(e).toBeInstanceOf(Error) + expect((e as Error).message.startsWith(error)).toBeTruthy() + } + } else { + await generator.run() + + const generatedRouteTree = await getRouteTreeFileText(config) + + let snapshotPath = `routeTree.nonnested.snapshot.${config.disableTypes ? 'js' : 'ts'}` + + await expect(generatedRouteTree).toMatchFileSnapshot( + join('generator', folderName, snapshotPath), + ) + } + + await postprocess(folderName) + }, + ) + + it('should create directory for routeTree if it does not exist', async () => { + const folderName = 'only-root' + const folderRoot = makeFolderDir(folderName) + await fs.rm(join(folderRoot, 'generated'), { recursive: true, force: true }) + let pathCreated = false + + const config = await setupConfig(folderName) + + rewriteConfigByFolderName(folderName, config) + + await preprocess(folderName) + + config.generatedRouteTree = join( + folderRoot, + 'generated', + '/routeTree.nonnested.gen.ts', + ) + const generator = new Generator({ config, root: folderRoot }) + const error = shouldThrow(folderName) + if (error) { + try { + await generator.run() + } catch (e) { + expect(e).toBeInstanceOf(Error) + expect((e as Error).message.startsWith(error)).toBeTruthy() + } + } else { + await generator.run() + + const generatedRouteTree = await getRouteTreeFileText(config) + + await expect(generatedRouteTree).toMatchFileSnapshot( + join( + 'generator', + folderName, + `routeTree.nonnested.generated.${config.disableTypes ? 'js' : 'ts'}`, + ), + ) + + pathCreated = await fs.access(dirname(config.generatedRouteTree)).then( + () => true, + () => false, + ) + + await expect(pathCreated).toBe(true) + } + + await postprocess(folderName) + + if (pathCreated) { + await fs.rm(dirname(config.generatedRouteTree), { recursive: true }) + } + }) +}) diff --git a/packages/router-generator/tests/generator/append-and-prepend/routeTree.nonnested.snapshot.ts b/packages/router-generator/tests/generator/append-and-prepend/routeTree.nonnested.snapshot.ts new file mode 100644 index 00000000000..4592b6f51d1 --- /dev/null +++ b/packages/router-generator/tests/generator/append-and-prepend/routeTree.nonnested.snapshot.ts @@ -0,0 +1,61 @@ +// prepend1 + +// prepend2 + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/__root' +import { Route as IndexRouteImport } from './routes/index' + +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' + fileRoutesByTo: FileRoutesByTo + to: '/' + id: '__root__' | '/' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + } +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() + +// append1 + +// append2 diff --git a/packages/router-generator/tests/generator/custom-scaffolding/routeTree.nonnested.snapshot.ts b/packages/router-generator/tests/generator/custom-scaffolding/routeTree.nonnested.snapshot.ts new file mode 100644 index 00000000000..e80d1e7e73c --- /dev/null +++ b/packages/router-generator/tests/generator/custom-scaffolding/routeTree.nonnested.snapshot.ts @@ -0,0 +1,98 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { createFileRoute } from '@tanstack/react-router' + +import { Route as rootRouteImport } from './routes/__root' +import { Route as IndexRouteImport } from './routes/index' +import { Route as ApiBarRouteImport } from './routes/api/bar' + +const FooLazyRouteImport = createFileRoute('/foo')() + +const FooLazyRoute = FooLazyRouteImport.update({ + id: '/foo', + path: '/foo', + getParentRoute: () => rootRouteImport, +} as any).lazy(() => import('./routes/foo.lazy').then((d) => d.Route)) +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) +const ApiBarRoute = ApiBarRouteImport.update({ + id: '/api/bar', + path: '/api/bar', + getParentRoute: () => rootRouteImport, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute + '/foo': typeof FooLazyRoute + '/api/bar': typeof ApiBarRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute + '/foo': typeof FooLazyRoute + '/api/bar': typeof ApiBarRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute + '/foo': typeof FooLazyRoute + '/api/bar': typeof ApiBarRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' | '/foo' | '/api/bar' + fileRoutesByTo: FileRoutesByTo + to: '/' | '/foo' | '/api/bar' + id: '__root__' | '/' | '/foo' | '/api/bar' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute + FooLazyRoute: typeof FooLazyRoute + ApiBarRoute: typeof ApiBarRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/foo': { + id: '/foo' + path: '/foo' + fullPath: '/foo' + preLoaderRoute: typeof FooLazyRouteImport + parentRoute: typeof rootRouteImport + } + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + '/api/bar': { + id: '/api/bar' + path: '/api/bar' + fullPath: '/api/bar' + preLoaderRoute: typeof ApiBarRouteImport + parentRoute: typeof rootRouteImport + } + } +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, + FooLazyRoute: FooLazyRoute, + ApiBarRoute: ApiBarRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/packages/router-generator/tests/generator/custom-tokens/routeTree.nonnested.snapshot.ts b/packages/router-generator/tests/generator/custom-tokens/routeTree.nonnested.snapshot.ts new file mode 100644 index 00000000000..c82afab07df --- /dev/null +++ b/packages/router-generator/tests/generator/custom-tokens/routeTree.nonnested.snapshot.ts @@ -0,0 +1,226 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/__root' +import { Route as PostsR0ut3RouteImport } from './routes/posts/_r0ut3_' +import { Route as BlogR0ut3RouteImport } from './routes/blog/_r0ut3_' +import { Route as R1nd3xRouteImport } from './routes/_1nd3x' +import { Route as Posts1nd3xRouteImport } from './routes/posts/_1nd3x' +import { Route as Blog1nd3xRouteImport } from './routes/blog/_1nd3x' +import { Route as BlogSlugRouteImport } from './routes/blog/$slug' +import { Route as PostsPostId1nd3xRouteImport } from './routes/posts/$postId/_1nd3x' +import { Route as PostsPostIdDeepRouteImport } from './routes/posts/$postId/deep' + +const PostsR0ut3Route = PostsR0ut3RouteImport.update({ + id: '/posts', + path: '/posts', + getParentRoute: () => rootRouteImport, +} as any) +const BlogR0ut3Route = BlogR0ut3RouteImport.update({ + id: '/blog', + path: '/blog', + getParentRoute: () => rootRouteImport, +} as any) +const R1nd3xRoute = R1nd3xRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) +const Posts1nd3xRoute = Posts1nd3xRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => PostsR0ut3Route, +} as any) +const Blog1nd3xRoute = Blog1nd3xRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => BlogR0ut3Route, +} as any) +const BlogSlugRoute = BlogSlugRouteImport.update({ + id: '/$slug', + path: '/$slug', + getParentRoute: () => BlogR0ut3Route, +} as any) +const PostsPostId1nd3xRoute = PostsPostId1nd3xRouteImport.update({ + id: '/$postId/', + path: '/$postId/', + getParentRoute: () => PostsR0ut3Route, +} as any) +const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ + id: '/$postId/deep', + path: '/$postId/deep', + getParentRoute: () => PostsR0ut3Route, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof R1nd3xRoute + '/blog': typeof BlogR0ut3RouteWithChildren + '/posts': typeof PostsR0ut3RouteWithChildren + '/blog/$slug': typeof BlogSlugRoute + '/blog/': typeof Blog1nd3xRoute + '/posts/': typeof Posts1nd3xRoute + '/posts/$postId/deep': typeof PostsPostIdDeepRoute + '/posts/$postId': typeof PostsPostId1nd3xRoute +} +export interface FileRoutesByTo { + '/': typeof R1nd3xRoute + '/blog/$slug': typeof BlogSlugRoute + '/blog': typeof Blog1nd3xRoute + '/posts': typeof Posts1nd3xRoute + '/posts/$postId/deep': typeof PostsPostIdDeepRoute + '/posts/$postId': typeof PostsPostId1nd3xRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof R1nd3xRoute + '/blog': typeof BlogR0ut3RouteWithChildren + '/posts': typeof PostsR0ut3RouteWithChildren + '/blog/$slug': typeof BlogSlugRoute + '/blog/': typeof Blog1nd3xRoute + '/posts/': typeof Posts1nd3xRoute + '/posts/$postId/deep': typeof PostsPostIdDeepRoute + '/posts/$postId/': typeof PostsPostId1nd3xRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: + | '/' + | '/blog' + | '/posts' + | '/blog/$slug' + | '/blog/' + | '/posts/' + | '/posts/$postId/deep' + | '/posts/$postId' + fileRoutesByTo: FileRoutesByTo + to: + | '/' + | '/blog/$slug' + | '/blog' + | '/posts' + | '/posts/$postId/deep' + | '/posts/$postId' + id: + | '__root__' + | '/' + | '/blog' + | '/posts' + | '/blog/$slug' + | '/blog/' + | '/posts/' + | '/posts/$postId/deep' + | '/posts/$postId/' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + R1nd3xRoute: typeof R1nd3xRoute + BlogR0ut3Route: typeof BlogR0ut3RouteWithChildren + PostsR0ut3Route: typeof PostsR0ut3RouteWithChildren +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsR0ut3RouteImport + parentRoute: typeof rootRouteImport + } + '/blog': { + id: '/blog' + path: '/blog' + fullPath: '/blog' + preLoaderRoute: typeof BlogR0ut3RouteImport + parentRoute: typeof rootRouteImport + } + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof R1nd3xRouteImport + parentRoute: typeof rootRouteImport + } + '/posts/': { + id: '/posts/' + path: '/' + fullPath: '/posts/' + preLoaderRoute: typeof Posts1nd3xRouteImport + parentRoute: typeof PostsR0ut3Route + } + '/blog/': { + id: '/blog/' + path: '/' + fullPath: '/blog/' + preLoaderRoute: typeof Blog1nd3xRouteImport + parentRoute: typeof BlogR0ut3Route + } + '/blog/$slug': { + id: '/blog/$slug' + path: '/$slug' + fullPath: '/blog/$slug' + preLoaderRoute: typeof BlogSlugRouteImport + parentRoute: typeof BlogR0ut3Route + } + '/posts/$postId/': { + id: '/posts/$postId/' + path: '/$postId' + fullPath: '/posts/$postId' + preLoaderRoute: typeof PostsPostId1nd3xRouteImport + parentRoute: typeof PostsR0ut3Route + } + '/posts/$postId/deep': { + id: '/posts/$postId/deep' + path: '/$postId/deep' + fullPath: '/posts/$postId/deep' + preLoaderRoute: typeof PostsPostIdDeepRouteImport + parentRoute: typeof PostsR0ut3Route + } + } +} + +interface BlogR0ut3RouteChildren { + BlogSlugRoute: typeof BlogSlugRoute + Blog1nd3xRoute: typeof Blog1nd3xRoute +} + +const BlogR0ut3RouteChildren: BlogR0ut3RouteChildren = { + BlogSlugRoute: BlogSlugRoute, + Blog1nd3xRoute: Blog1nd3xRoute, +} + +const BlogR0ut3RouteWithChildren = BlogR0ut3Route._addFileChildren( + BlogR0ut3RouteChildren, +) + +interface PostsR0ut3RouteChildren { + Posts1nd3xRoute: typeof Posts1nd3xRoute + PostsPostIdDeepRoute: typeof PostsPostIdDeepRoute + PostsPostId1nd3xRoute: typeof PostsPostId1nd3xRoute +} + +const PostsR0ut3RouteChildren: PostsR0ut3RouteChildren = { + Posts1nd3xRoute: Posts1nd3xRoute, + PostsPostIdDeepRoute: PostsPostIdDeepRoute, + PostsPostId1nd3xRoute: PostsPostId1nd3xRoute, +} + +const PostsR0ut3RouteWithChildren = PostsR0ut3Route._addFileChildren( + PostsR0ut3RouteChildren, +) + +const rootRouteChildren: RootRouteChildren = { + R1nd3xRoute: R1nd3xRoute, + BlogR0ut3Route: BlogR0ut3RouteWithChildren, + PostsR0ut3Route: PostsR0ut3RouteWithChildren, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/packages/router-generator/tests/generator/dot-escaped/routeTree.nonnested.snapshot.ts b/packages/router-generator/tests/generator/dot-escaped/routeTree.nonnested.snapshot.ts new file mode 100644 index 00000000000..2ccc3a0a381 --- /dev/null +++ b/packages/router-generator/tests/generator/dot-escaped/routeTree.nonnested.snapshot.ts @@ -0,0 +1,137 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/__root' +import { Route as ScriptDotjsRouteImport } from './routes/script[.]js' +import { Route as NestedDotjsRouteImport } from './routes/nested[.]js' +import { Route as NestedDotjsScriptDotjsRouteImport } from './routes/nested[.]js.script[.]js' +import { Route as NestedDotjsDoubleDotextDotjsRouteImport } from './routes/nested[.]js.double[.]ext[.]js' + +const ScriptDotjsRoute = ScriptDotjsRouteImport.update({ + id: '/script.js', + path: '/script.js', + getParentRoute: () => rootRouteImport, +} as any) +const NestedDotjsRoute = NestedDotjsRouteImport.update({ + id: '/nested.js', + path: '/nested.js', + getParentRoute: () => rootRouteImport, +} as any) +const NestedDotjsScriptDotjsRoute = NestedDotjsScriptDotjsRouteImport.update({ + id: '/script.js', + path: '/script.js', + getParentRoute: () => NestedDotjsRoute, +} as any) +const NestedDotjsDoubleDotextDotjsRoute = + NestedDotjsDoubleDotextDotjsRouteImport.update({ + id: '/double.ext.js', + path: '/double.ext.js', + getParentRoute: () => NestedDotjsRoute, + } as any) + +export interface FileRoutesByFullPath { + '/nested.js': typeof NestedDotjsRouteWithChildren + '/script.js': typeof ScriptDotjsRoute + '/nested.js/double.ext.js': typeof NestedDotjsDoubleDotextDotjsRoute + '/nested.js/script.js': typeof NestedDotjsScriptDotjsRoute +} +export interface FileRoutesByTo { + '/nested.js': typeof NestedDotjsRouteWithChildren + '/script.js': typeof ScriptDotjsRoute + '/nested.js/double.ext.js': typeof NestedDotjsDoubleDotextDotjsRoute + '/nested.js/script.js': typeof NestedDotjsScriptDotjsRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/nested.js': typeof NestedDotjsRouteWithChildren + '/script.js': typeof ScriptDotjsRoute + '/nested.js/double.ext.js': typeof NestedDotjsDoubleDotextDotjsRoute + '/nested.js/script.js': typeof NestedDotjsScriptDotjsRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: + | '/nested.js' + | '/script.js' + | '/nested.js/double.ext.js' + | '/nested.js/script.js' + fileRoutesByTo: FileRoutesByTo + to: + | '/nested.js' + | '/script.js' + | '/nested.js/double.ext.js' + | '/nested.js/script.js' + id: + | '__root__' + | '/nested.js' + | '/script.js' + | '/nested.js/double.ext.js' + | '/nested.js/script.js' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + NestedDotjsRoute: typeof NestedDotjsRouteWithChildren + ScriptDotjsRoute: typeof ScriptDotjsRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/script.js': { + id: '/script.js' + path: '/script.js' + fullPath: '/script.js' + preLoaderRoute: typeof ScriptDotjsRouteImport + parentRoute: typeof rootRouteImport + } + '/nested.js': { + id: '/nested.js' + path: '/nested.js' + fullPath: '/nested.js' + preLoaderRoute: typeof NestedDotjsRouteImport + parentRoute: typeof rootRouteImport + } + '/nested.js/script.js': { + id: '/nested.js/script.js' + path: '/script.js' + fullPath: '/nested.js/script.js' + preLoaderRoute: typeof NestedDotjsScriptDotjsRouteImport + parentRoute: typeof NestedDotjsRoute + } + '/nested.js/double.ext.js': { + id: '/nested.js/double.ext.js' + path: '/double.ext.js' + fullPath: '/nested.js/double.ext.js' + preLoaderRoute: typeof NestedDotjsDoubleDotextDotjsRouteImport + parentRoute: typeof NestedDotjsRoute + } + } +} + +interface NestedDotjsRouteChildren { + NestedDotjsDoubleDotextDotjsRoute: typeof NestedDotjsDoubleDotextDotjsRoute + NestedDotjsScriptDotjsRoute: typeof NestedDotjsScriptDotjsRoute +} + +const NestedDotjsRouteChildren: NestedDotjsRouteChildren = { + NestedDotjsDoubleDotextDotjsRoute: NestedDotjsDoubleDotextDotjsRoute, + NestedDotjsScriptDotjsRoute: NestedDotjsScriptDotjsRoute, +} + +const NestedDotjsRouteWithChildren = NestedDotjsRoute._addFileChildren( + NestedDotjsRouteChildren, +) + +const rootRouteChildren: RootRouteChildren = { + NestedDotjsRoute: NestedDotjsRouteWithChildren, + ScriptDotjsRoute: ScriptDotjsRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/packages/router-generator/tests/generator/export-variations/routeTree.nonnested.snapshot.ts b/packages/router-generator/tests/generator/export-variations/routeTree.nonnested.snapshot.ts new file mode 100644 index 00000000000..63624894355 --- /dev/null +++ b/packages/router-generator/tests/generator/export-variations/routeTree.nonnested.snapshot.ts @@ -0,0 +1,78 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/__root' +import { Route as ExportWithAsRouteImport } from './routes/export-with-as' +import { Route as ExportSeparateFromDeclarationRouteImport } from './routes/export-separate-from-declaration' + +const ExportWithAsRoute = ExportWithAsRouteImport.update({ + id: '/export-with-as', + path: '/export-with-as', + getParentRoute: () => rootRouteImport, +} as any) +const ExportSeparateFromDeclarationRoute = + ExportSeparateFromDeclarationRouteImport.update({ + id: '/export-separate-from-declaration', + path: '/export-separate-from-declaration', + getParentRoute: () => rootRouteImport, + } as any) + +export interface FileRoutesByFullPath { + '/export-separate-from-declaration': typeof ExportSeparateFromDeclarationRoute + '/export-with-as': typeof ExportWithAsRoute +} +export interface FileRoutesByTo { + '/export-separate-from-declaration': typeof ExportSeparateFromDeclarationRoute + '/export-with-as': typeof ExportWithAsRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/export-separate-from-declaration': typeof ExportSeparateFromDeclarationRoute + '/export-with-as': typeof ExportWithAsRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/export-separate-from-declaration' | '/export-with-as' + fileRoutesByTo: FileRoutesByTo + to: '/export-separate-from-declaration' | '/export-with-as' + id: '__root__' | '/export-separate-from-declaration' | '/export-with-as' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + ExportSeparateFromDeclarationRoute: typeof ExportSeparateFromDeclarationRoute + ExportWithAsRoute: typeof ExportWithAsRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/export-with-as': { + id: '/export-with-as' + path: '/export-with-as' + fullPath: '/export-with-as' + preLoaderRoute: typeof ExportWithAsRouteImport + parentRoute: typeof rootRouteImport + } + '/export-separate-from-declaration': { + id: '/export-separate-from-declaration' + path: '/export-separate-from-declaration' + fullPath: '/export-separate-from-declaration' + preLoaderRoute: typeof ExportSeparateFromDeclarationRouteImport + parentRoute: typeof rootRouteImport + } + } +} + +const rootRouteChildren: RootRouteChildren = { + ExportSeparateFromDeclarationRoute: ExportSeparateFromDeclarationRoute, + ExportWithAsRoute: ExportWithAsRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/packages/router-generator/tests/generator/file-modification-verboseFileRoutes-false/routeTree.nonnested.snapshot.ts b/packages/router-generator/tests/generator/file-modification-verboseFileRoutes-false/routeTree.nonnested.snapshot.ts new file mode 100644 index 00000000000..6cd0f9c68ec --- /dev/null +++ b/packages/router-generator/tests/generator/file-modification-verboseFileRoutes-false/routeTree.nonnested.snapshot.ts @@ -0,0 +1,207 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { createFileRoute } from '@tanstack/react-router' +import type { + CreateFileRoute, + CreateLazyFileRoute, + FileRoutesByPath, +} from '@tanstack/react-router' + +import { Route as rootRouteImport } from './routes/__root' +import { Route as testInitiallyLazyRouteImport } from './routes/(test)/initiallyLazy' +import { Route as testInitiallyEmptyRouteImport } from './routes/(test)/initiallyEmpty' +import { Route as testFooRouteImport } from './routes/(test)/foo' +import { Route as testFooBarRouteImport } from './routes/(test)/foo.bar' + +const testBarLazyRouteImport = createFileRoute('/(test)/bar')() + +const testBarLazyRoute = testBarLazyRouteImport + .update({ + id: '/(test)/bar', + path: '/bar', + getParentRoute: () => rootRouteImport, + } as any) + .lazy(() => import('./routes/(test)/bar.lazy').then((d) => d.Route)) +const testInitiallyLazyRoute = testInitiallyLazyRouteImport.update({ + id: '/(test)/initiallyLazy', + path: '/initiallyLazy', + getParentRoute: () => rootRouteImport, +} as any) +const testInitiallyEmptyRoute = testInitiallyEmptyRouteImport + .update({ + id: '/(test)/initiallyEmpty', + path: '/initiallyEmpty', + getParentRoute: () => rootRouteImport, + } as any) + .lazy(() => + import('./routes/(test)/initiallyEmpty.lazy').then((d) => d.Route), + ) +const testFooRoute = testFooRouteImport.update({ + id: '/(test)/foo', + path: '/foo', + getParentRoute: () => rootRouteImport, +} as any) +const testFooBarRoute = testFooBarRouteImport.update({ + id: '/bar', + path: '/bar', + getParentRoute: () => testFooRoute, +} as any) + +export interface FileRoutesByFullPath { + '/foo': typeof testFooRouteWithChildren + '/initiallyEmpty': typeof testInitiallyEmptyRoute + '/initiallyLazy': typeof testInitiallyLazyRoute + '/bar': typeof testBarLazyRoute + '/foo/bar': typeof testFooBarRoute +} +export interface FileRoutesByTo { + '/foo': typeof testFooRouteWithChildren + '/initiallyEmpty': typeof testInitiallyEmptyRoute + '/initiallyLazy': typeof testInitiallyLazyRoute + '/bar': typeof testBarLazyRoute + '/foo/bar': typeof testFooBarRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/(test)/foo': typeof testFooRouteWithChildren + '/(test)/initiallyEmpty': typeof testInitiallyEmptyRoute + '/(test)/initiallyLazy': typeof testInitiallyLazyRoute + '/(test)/bar': typeof testBarLazyRoute + '/(test)/foo/bar': typeof testFooBarRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/foo' | '/initiallyEmpty' | '/initiallyLazy' | '/bar' | '/foo/bar' + fileRoutesByTo: FileRoutesByTo + to: '/foo' | '/initiallyEmpty' | '/initiallyLazy' | '/bar' | '/foo/bar' + id: + | '__root__' + | '/(test)/foo' + | '/(test)/initiallyEmpty' + | '/(test)/initiallyLazy' + | '/(test)/bar' + | '/(test)/foo/bar' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + testFooRoute: typeof testFooRouteWithChildren + testInitiallyEmptyRoute: typeof testInitiallyEmptyRoute + testInitiallyLazyRoute: typeof testInitiallyLazyRoute + testBarLazyRoute: typeof testBarLazyRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/(test)/bar': { + id: '/(test)/bar' + path: '/bar' + fullPath: '/bar' + preLoaderRoute: typeof testBarLazyRouteImport + parentRoute: typeof rootRouteImport + } + '/(test)/initiallyLazy': { + id: '/(test)/initiallyLazy' + path: '/initiallyLazy' + fullPath: '/initiallyLazy' + preLoaderRoute: typeof testInitiallyLazyRouteImport + parentRoute: typeof rootRouteImport + } + '/(test)/initiallyEmpty': { + id: '/(test)/initiallyEmpty' + path: '/initiallyEmpty' + fullPath: '/initiallyEmpty' + preLoaderRoute: typeof testInitiallyEmptyRouteImport + parentRoute: typeof rootRouteImport + } + '/(test)/foo': { + id: '/(test)/foo' + path: '/foo' + fullPath: '/foo' + preLoaderRoute: typeof testFooRouteImport + parentRoute: typeof rootRouteImport + } + '/(test)/foo/bar': { + id: '/(test)/foo/bar' + path: '/bar' + fullPath: '/foo/bar' + preLoaderRoute: typeof testFooBarRouteImport + parentRoute: typeof testFooRoute + } + } +} + +declare module './routes/(test)/foo' { + const createFileRoute: CreateFileRoute< + '/(test)/foo', + FileRoutesByPath['/(test)/foo']['parentRoute'], + FileRoutesByPath['/(test)/foo']['id'], + FileRoutesByPath['/(test)/foo']['path'], + FileRoutesByPath['/(test)/foo']['fullPath'] + > +} +declare module './routes/(test)/initiallyEmpty' { + const createFileRoute: CreateFileRoute< + '/(test)/initiallyEmpty', + FileRoutesByPath['/(test)/initiallyEmpty']['parentRoute'], + FileRoutesByPath['/(test)/initiallyEmpty']['id'], + FileRoutesByPath['/(test)/initiallyEmpty']['path'], + FileRoutesByPath['/(test)/initiallyEmpty']['fullPath'] + > +} +declare module './routes/(test)/initiallyLazy' { + const createFileRoute: CreateFileRoute< + '/(test)/initiallyLazy', + FileRoutesByPath['/(test)/initiallyLazy']['parentRoute'], + FileRoutesByPath['/(test)/initiallyLazy']['id'], + FileRoutesByPath['/(test)/initiallyLazy']['path'], + FileRoutesByPath['/(test)/initiallyLazy']['fullPath'] + > +} +declare module './routes/(test)/bar.lazy' { + const createLazyFileRoute: CreateLazyFileRoute< + FileRoutesByPath['/(test)/bar']['preLoaderRoute'] + > +} +declare module './routes/(test)/initiallyEmpty.lazy' { + const createLazyFileRoute: CreateLazyFileRoute< + FileRoutesByPath['/(test)/initiallyEmpty']['preLoaderRoute'] + > +} +declare module './routes/(test)/foo.bar' { + const createFileRoute: CreateFileRoute< + '/(test)/foo/bar', + FileRoutesByPath['/(test)/foo/bar']['parentRoute'], + FileRoutesByPath['/(test)/foo/bar']['id'], + FileRoutesByPath['/(test)/foo/bar']['path'], + FileRoutesByPath['/(test)/foo/bar']['fullPath'] + > +} + +interface testFooRouteChildren { + testFooBarRoute: typeof testFooBarRoute +} + +const testFooRouteChildren: testFooRouteChildren = { + testFooBarRoute: testFooBarRoute, +} + +const testFooRouteWithChildren = + testFooRoute._addFileChildren(testFooRouteChildren) + +const rootRouteChildren: RootRouteChildren = { + testFooRoute: testFooRouteWithChildren, + testInitiallyEmptyRoute: testInitiallyEmptyRoute, + testInitiallyLazyRoute: testInitiallyLazyRoute, + testBarLazyRoute: testBarLazyRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/packages/router-generator/tests/generator/file-modification-verboseFileRoutes-true/routeTree.nonnested.snapshot.ts b/packages/router-generator/tests/generator/file-modification-verboseFileRoutes-true/routeTree.nonnested.snapshot.ts new file mode 100644 index 00000000000..816d3ddaa26 --- /dev/null +++ b/packages/router-generator/tests/generator/file-modification-verboseFileRoutes-true/routeTree.nonnested.snapshot.ts @@ -0,0 +1,155 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { createFileRoute } from '@tanstack/react-router' + +import { Route as rootRouteImport } from './routes/__root' +import { Route as testInitiallyLazyRouteImport } from './routes/(test)/initiallyLazy' +import { Route as testInitiallyEmptyRouteImport } from './routes/(test)/initiallyEmpty' +import { Route as testFooRouteImport } from './routes/(test)/foo' +import { Route as testFooBarRouteImport } from './routes/(test)/foo.bar' + +const testBarLazyRouteImport = createFileRoute('/(test)/bar')() + +const testBarLazyRoute = testBarLazyRouteImport + .update({ + id: '/(test)/bar', + path: '/bar', + getParentRoute: () => rootRouteImport, + } as any) + .lazy(() => import('./routes/(test)/bar.lazy').then((d) => d.Route)) +const testInitiallyLazyRoute = testInitiallyLazyRouteImport.update({ + id: '/(test)/initiallyLazy', + path: '/initiallyLazy', + getParentRoute: () => rootRouteImport, +} as any) +const testInitiallyEmptyRoute = testInitiallyEmptyRouteImport + .update({ + id: '/(test)/initiallyEmpty', + path: '/initiallyEmpty', + getParentRoute: () => rootRouteImport, + } as any) + .lazy(() => + import('./routes/(test)/initiallyEmpty.lazy').then((d) => d.Route), + ) +const testFooRoute = testFooRouteImport.update({ + id: '/(test)/foo', + path: '/foo', + getParentRoute: () => rootRouteImport, +} as any) +const testFooBarRoute = testFooBarRouteImport.update({ + id: '/bar', + path: '/bar', + getParentRoute: () => testFooRoute, +} as any) + +export interface FileRoutesByFullPath { + '/foo': typeof testFooRouteWithChildren + '/initiallyEmpty': typeof testInitiallyEmptyRoute + '/initiallyLazy': typeof testInitiallyLazyRoute + '/bar': typeof testBarLazyRoute + '/foo/bar': typeof testFooBarRoute +} +export interface FileRoutesByTo { + '/foo': typeof testFooRouteWithChildren + '/initiallyEmpty': typeof testInitiallyEmptyRoute + '/initiallyLazy': typeof testInitiallyLazyRoute + '/bar': typeof testBarLazyRoute + '/foo/bar': typeof testFooBarRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/(test)/foo': typeof testFooRouteWithChildren + '/(test)/initiallyEmpty': typeof testInitiallyEmptyRoute + '/(test)/initiallyLazy': typeof testInitiallyLazyRoute + '/(test)/bar': typeof testBarLazyRoute + '/(test)/foo/bar': typeof testFooBarRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/foo' | '/initiallyEmpty' | '/initiallyLazy' | '/bar' | '/foo/bar' + fileRoutesByTo: FileRoutesByTo + to: '/foo' | '/initiallyEmpty' | '/initiallyLazy' | '/bar' | '/foo/bar' + id: + | '__root__' + | '/(test)/foo' + | '/(test)/initiallyEmpty' + | '/(test)/initiallyLazy' + | '/(test)/bar' + | '/(test)/foo/bar' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + testFooRoute: typeof testFooRouteWithChildren + testInitiallyEmptyRoute: typeof testInitiallyEmptyRoute + testInitiallyLazyRoute: typeof testInitiallyLazyRoute + testBarLazyRoute: typeof testBarLazyRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/(test)/bar': { + id: '/(test)/bar' + path: '/bar' + fullPath: '/bar' + preLoaderRoute: typeof testBarLazyRouteImport + parentRoute: typeof rootRouteImport + } + '/(test)/initiallyLazy': { + id: '/(test)/initiallyLazy' + path: '/initiallyLazy' + fullPath: '/initiallyLazy' + preLoaderRoute: typeof testInitiallyLazyRouteImport + parentRoute: typeof rootRouteImport + } + '/(test)/initiallyEmpty': { + id: '/(test)/initiallyEmpty' + path: '/initiallyEmpty' + fullPath: '/initiallyEmpty' + preLoaderRoute: typeof testInitiallyEmptyRouteImport + parentRoute: typeof rootRouteImport + } + '/(test)/foo': { + id: '/(test)/foo' + path: '/foo' + fullPath: '/foo' + preLoaderRoute: typeof testFooRouteImport + parentRoute: typeof rootRouteImport + } + '/(test)/foo/bar': { + id: '/(test)/foo/bar' + path: '/bar' + fullPath: '/foo/bar' + preLoaderRoute: typeof testFooBarRouteImport + parentRoute: typeof testFooRoute + } + } +} + +interface testFooRouteChildren { + testFooBarRoute: typeof testFooBarRoute +} + +const testFooRouteChildren: testFooRouteChildren = { + testFooBarRoute: testFooBarRoute, +} + +const testFooRouteWithChildren = + testFooRoute._addFileChildren(testFooRouteChildren) + +const rootRouteChildren: RootRouteChildren = { + testFooRoute: testFooRouteWithChildren, + testInitiallyEmptyRoute: testInitiallyEmptyRoute, + testInitiallyLazyRoute: testInitiallyLazyRoute, + testBarLazyRoute: testBarLazyRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/packages/router-generator/tests/generator/flat/routeTree.nonnested.snapshot.ts b/packages/router-generator/tests/generator/flat/routeTree.nonnested.snapshot.ts new file mode 100644 index 00000000000..e908079785d --- /dev/null +++ b/packages/router-generator/tests/generator/flat/routeTree.nonnested.snapshot.ts @@ -0,0 +1,247 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/__root' +import { Route as PostsRouteRouteImport } from './routes/posts.route' +import { Route as BlogRouteRouteImport } from './routes/blog.route' +import { Route as IndexRouteImport } from './routes/index' +import { Route as PostsIndexRouteImport } from './routes/posts.index' +import { Route as BlogIndexRouteImport } from './routes/blog.index' +import { Route as BlogStatsRouteImport } from './routes/blog_.stats' +import { Route as PostsPostIdIndexRouteImport } from './routes/posts.$postId.index' +import { Route as BlogSlugIndexRouteImport } from './routes/blog.$slug.index' +import { Route as PostsPostIdDeepRouteImport } from './routes/posts.$postId.deep' + +const PostsRouteRoute = PostsRouteRouteImport.update({ + id: '/posts', + path: '/posts', + getParentRoute: () => rootRouteImport, +} as any) +const BlogRouteRoute = BlogRouteRouteImport.update({ + id: '/blog', + path: '/blog', + getParentRoute: () => rootRouteImport, +} as any) +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) +const PostsIndexRoute = PostsIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => PostsRouteRoute, +} as any) +const BlogIndexRoute = BlogIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => BlogRouteRoute, +} as any) +const BlogStatsRoute = BlogStatsRouteImport.update({ + id: '/blog/stats', + path: '/blog/stats', + getParentRoute: () => rootRouteImport, +} as any) +const PostsPostIdIndexRoute = PostsPostIdIndexRouteImport.update({ + id: '/$postId/', + path: '/$postId/', + getParentRoute: () => PostsRouteRoute, +} as any) +const BlogSlugIndexRoute = BlogSlugIndexRouteImport.update({ + id: '/$slug/', + path: '/$slug/', + getParentRoute: () => BlogRouteRoute, +} as any) +const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ + id: '/$postId/deep', + path: '/$postId/deep', + getParentRoute: () => PostsRouteRoute, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute + '/blog': typeof BlogRouteRouteWithChildren + '/posts': typeof PostsRouteRouteWithChildren + '/blog/stats': typeof BlogStatsRoute + '/blog/': typeof BlogIndexRoute + '/posts/': typeof PostsIndexRoute + '/posts/$postId/deep': typeof PostsPostIdDeepRoute + '/blog/$slug': typeof BlogSlugIndexRoute + '/posts/$postId': typeof PostsPostIdIndexRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute + '/blog/stats': typeof BlogStatsRoute + '/blog': typeof BlogIndexRoute + '/posts': typeof PostsIndexRoute + '/posts/$postId/deep': typeof PostsPostIdDeepRoute + '/blog/$slug': typeof BlogSlugIndexRoute + '/posts/$postId': typeof PostsPostIdIndexRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute + '/blog': typeof BlogRouteRouteWithChildren + '/posts': typeof PostsRouteRouteWithChildren + '/blog/stats': typeof BlogStatsRoute + '/blog/': typeof BlogIndexRoute + '/posts/': typeof PostsIndexRoute + '/posts/$postId/deep': typeof PostsPostIdDeepRoute + '/blog/$slug/': typeof BlogSlugIndexRoute + '/posts/$postId/': typeof PostsPostIdIndexRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: + | '/' + | '/blog' + | '/posts' + | '/blog/stats' + | '/blog/' + | '/posts/' + | '/posts/$postId/deep' + | '/blog/$slug' + | '/posts/$postId' + fileRoutesByTo: FileRoutesByTo + to: + | '/' + | '/blog/stats' + | '/blog' + | '/posts' + | '/posts/$postId/deep' + | '/blog/$slug' + | '/posts/$postId' + id: + | '__root__' + | '/' + | '/blog' + | '/posts' + | '/blog/stats' + | '/blog/' + | '/posts/' + | '/posts/$postId/deep' + | '/blog/$slug/' + | '/posts/$postId/' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute + BlogRouteRoute: typeof BlogRouteRouteWithChildren + PostsRouteRoute: typeof PostsRouteRouteWithChildren + BlogStatsRoute: typeof BlogStatsRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteRouteImport + parentRoute: typeof rootRouteImport + } + '/blog': { + id: '/blog' + path: '/blog' + fullPath: '/blog' + preLoaderRoute: typeof BlogRouteRouteImport + parentRoute: typeof rootRouteImport + } + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + '/posts/': { + id: '/posts/' + path: '/' + fullPath: '/posts/' + preLoaderRoute: typeof PostsIndexRouteImport + parentRoute: typeof PostsRouteRoute + } + '/blog/': { + id: '/blog/' + path: '/' + fullPath: '/blog/' + preLoaderRoute: typeof BlogIndexRouteImport + parentRoute: typeof BlogRouteRoute + } + '/blog/stats': { + id: '/blog/stats' + path: '/blog/stats' + fullPath: '/blog/stats' + preLoaderRoute: typeof BlogStatsRouteImport + parentRoute: typeof rootRouteImport + } + '/posts/$postId/': { + id: '/posts/$postId/' + path: '/$postId' + fullPath: '/posts/$postId' + preLoaderRoute: typeof PostsPostIdIndexRouteImport + parentRoute: typeof PostsRouteRoute + } + '/blog/$slug/': { + id: '/blog/$slug/' + path: '/$slug' + fullPath: '/blog/$slug' + preLoaderRoute: typeof BlogSlugIndexRouteImport + parentRoute: typeof BlogRouteRoute + } + '/posts/$postId/deep': { + id: '/posts/$postId/deep' + path: '/$postId/deep' + fullPath: '/posts/$postId/deep' + preLoaderRoute: typeof PostsPostIdDeepRouteImport + parentRoute: typeof PostsRouteRoute + } + } +} + +interface BlogRouteRouteChildren { + BlogIndexRoute: typeof BlogIndexRoute + BlogSlugIndexRoute: typeof BlogSlugIndexRoute +} + +const BlogRouteRouteChildren: BlogRouteRouteChildren = { + BlogIndexRoute: BlogIndexRoute, + BlogSlugIndexRoute: BlogSlugIndexRoute, +} + +const BlogRouteRouteWithChildren = BlogRouteRoute._addFileChildren( + BlogRouteRouteChildren, +) + +interface PostsRouteRouteChildren { + PostsIndexRoute: typeof PostsIndexRoute + PostsPostIdDeepRoute: typeof PostsPostIdDeepRoute + PostsPostIdIndexRoute: typeof PostsPostIdIndexRoute +} + +const PostsRouteRouteChildren: PostsRouteRouteChildren = { + PostsIndexRoute: PostsIndexRoute, + PostsPostIdDeepRoute: PostsPostIdDeepRoute, + PostsPostIdIndexRoute: PostsPostIdIndexRoute, +} + +const PostsRouteRouteWithChildren = PostsRouteRoute._addFileChildren( + PostsRouteRouteChildren, +) + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, + BlogRouteRoute: BlogRouteRouteWithChildren, + PostsRouteRoute: PostsRouteRouteWithChildren, + BlogStatsRoute: BlogStatsRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/packages/router-generator/tests/generator/nested-layouts/routeTree.nonnested.snapshot.ts b/packages/router-generator/tests/generator/nested-layouts/routeTree.nonnested.snapshot.ts new file mode 100644 index 00000000000..c76e88e21d9 --- /dev/null +++ b/packages/router-generator/tests/generator/nested-layouts/routeTree.nonnested.snapshot.ts @@ -0,0 +1,637 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { createFileRoute } from '@tanstack/react-router' + +import { Route as rootRouteImport } from './routes/__root' +import { Route as LayoutA2RouteImport } from './routes/_layout-a2' +import { Route as LayoutA1RouteImport } from './routes/_layout-a1' +import { Route as JestedRouteRouteImport } from './routes/jested/route' +import { Route as IndexRouteImport } from './routes/index' +import { Route as NestedLayoutB2RouteImport } from './routes/nested/_layout-b2' +import { Route as NestedLayoutB1RouteImport } from './routes/nested/_layout-b1' +import { Route as JestedLayoutB4RouteImport } from './routes/jested/_layout-b4' +import { Route as JestedLayoutB3RouteImport } from './routes/jested/_layout-b3' +import { Route as FooBarRouteImport } from './routes/foo/bar' +import { Route as LayoutA2BarRouteImport } from './routes/_layout-a2/bar' +import { Route as LayoutA1FooRouteImport } from './routes/_layout-a1/foo' +import { Route as folderInFolderRouteImport } from './routes/(folder)/in-folder' +import { Route as FooLayoutB5RouteRouteImport } from './routes/foo/_layout-b5/route' +import { Route as NestedLayoutB1IndexRouteImport } from './routes/nested/_layout-b1/index' +import { Route as JestedLayoutB3IndexRouteImport } from './routes/jested/_layout-b3/index' +import { Route as FooLayoutB5IndexRouteImport } from './routes/foo/_layout-b5/index' +import { Route as NestedLayoutB2FooRouteImport } from './routes/nested/_layout-b2/foo' +import { Route as NestedLayoutB1LayoutC1RouteImport } from './routes/nested/_layout-b1/_layout-c1' +import { Route as JestedLayoutB4FooRouteImport } from './routes/jested/_layout-b4/foo' +import { Route as JestedLayoutB3LayoutC2RouteImport } from './routes/jested/_layout-b3/_layout-c2' +import { Route as FooLayoutB5IdRouteImport } from './routes/foo/_layout-b5/$id' +import { Route as NestedLayoutB1LayoutC1BarRouteImport } from './routes/nested/_layout-b1/_layout-c1/bar' +import { Route as JestedLayoutB3LayoutC2BarRouteImport } from './routes/jested/_layout-b3/_layout-c2/bar' + +const NestedRouteImport = createFileRoute('/nested')() +const FooRouteImport = createFileRoute('/foo')() + +const NestedRoute = NestedRouteImport.update({ + id: '/nested', + path: '/nested', + getParentRoute: () => rootRouteImport, +} as any) +const FooRoute = FooRouteImport.update({ + id: '/foo', + path: '/foo', + getParentRoute: () => rootRouteImport, +} as any) +const LayoutA2Route = LayoutA2RouteImport.update({ + id: '/_layout-a2', + getParentRoute: () => rootRouteImport, +} as any) +const LayoutA1Route = LayoutA1RouteImport.update({ + id: '/_layout-a1', + getParentRoute: () => rootRouteImport, +} as any) +const JestedRouteRoute = JestedRouteRouteImport.update({ + id: '/jested', + path: '/jested', + getParentRoute: () => rootRouteImport, +} as any) +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) +const NestedLayoutB2Route = NestedLayoutB2RouteImport.update({ + id: '/_layout-b2', + getParentRoute: () => NestedRoute, +} as any) +const NestedLayoutB1Route = NestedLayoutB1RouteImport.update({ + id: '/_layout-b1', + getParentRoute: () => NestedRoute, +} as any) +const JestedLayoutB4Route = JestedLayoutB4RouteImport.update({ + id: '/_layout-b4', + getParentRoute: () => JestedRouteRoute, +} as any) +const JestedLayoutB3Route = JestedLayoutB3RouteImport.update({ + id: '/_layout-b3', + getParentRoute: () => JestedRouteRoute, +} as any) +const FooBarRoute = FooBarRouteImport.update({ + id: '/bar', + path: '/bar', + getParentRoute: () => FooRoute, +} as any) +const LayoutA2BarRoute = LayoutA2BarRouteImport.update({ + id: '/bar', + path: '/bar', + getParentRoute: () => LayoutA2Route, +} as any) +const LayoutA1FooRoute = LayoutA1FooRouteImport.update({ + id: '/foo', + path: '/foo', + getParentRoute: () => LayoutA1Route, +} as any) +const folderInFolderRoute = folderInFolderRouteImport.update({ + id: '/(folder)/in-folder', + path: '/in-folder', + getParentRoute: () => rootRouteImport, +} as any) +const FooLayoutB5RouteRoute = FooLayoutB5RouteRouteImport.update({ + id: '/_layout-b5', + getParentRoute: () => FooRoute, +} as any) +const NestedLayoutB1IndexRoute = NestedLayoutB1IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => NestedLayoutB1Route, +} as any) +const JestedLayoutB3IndexRoute = JestedLayoutB3IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => JestedLayoutB3Route, +} as any) +const FooLayoutB5IndexRoute = FooLayoutB5IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => FooLayoutB5RouteRoute, +} as any) +const NestedLayoutB2FooRoute = NestedLayoutB2FooRouteImport.update({ + id: '/foo', + path: '/foo', + getParentRoute: () => NestedLayoutB2Route, +} as any) +const NestedLayoutB1LayoutC1Route = NestedLayoutB1LayoutC1RouteImport.update({ + id: '/_layout-c1', + getParentRoute: () => NestedLayoutB1Route, +} as any) +const JestedLayoutB4FooRoute = JestedLayoutB4FooRouteImport.update({ + id: '/foo', + path: '/foo', + getParentRoute: () => JestedLayoutB4Route, +} as any) +const JestedLayoutB3LayoutC2Route = JestedLayoutB3LayoutC2RouteImport.update({ + id: '/_layout-c2', + getParentRoute: () => JestedLayoutB3Route, +} as any) +const FooLayoutB5IdRoute = FooLayoutB5IdRouteImport.update({ + id: '/$id', + path: '/$id', + getParentRoute: () => FooLayoutB5RouteRoute, +} as any) +const NestedLayoutB1LayoutC1BarRoute = + NestedLayoutB1LayoutC1BarRouteImport.update({ + id: '/bar', + path: '/bar', + getParentRoute: () => NestedLayoutB1LayoutC1Route, + } as any) +const JestedLayoutB3LayoutC2BarRoute = + JestedLayoutB3LayoutC2BarRouteImport.update({ + id: '/bar', + path: '/bar', + getParentRoute: () => JestedLayoutB3LayoutC2Route, + } as any) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute + '/jested': typeof JestedLayoutB3LayoutC2RouteWithChildren + '/foo': typeof LayoutA1FooRoute + '/in-folder': typeof folderInFolderRoute + '/bar': typeof LayoutA2BarRoute + '/foo/bar': typeof FooBarRoute + '/nested': typeof NestedLayoutB1LayoutC1RouteWithChildren + '/foo/$id': typeof FooLayoutB5IdRoute + '/jested/foo': typeof JestedLayoutB4FooRoute + '/nested/foo': typeof NestedLayoutB2FooRoute + '/foo/': typeof FooLayoutB5IndexRoute + '/jested/': typeof JestedLayoutB3IndexRoute + '/nested/': typeof NestedLayoutB1IndexRoute + '/jested/bar': typeof JestedLayoutB3LayoutC2BarRoute + '/nested/bar': typeof NestedLayoutB1LayoutC1BarRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute + '/jested': typeof JestedLayoutB3IndexRoute + '/foo': typeof FooLayoutB5IndexRoute + '/in-folder': typeof folderInFolderRoute + '/bar': typeof LayoutA2BarRoute + '/foo/bar': typeof FooBarRoute + '/nested': typeof NestedLayoutB1IndexRoute + '/foo/$id': typeof FooLayoutB5IdRoute + '/jested/foo': typeof JestedLayoutB4FooRoute + '/nested/foo': typeof NestedLayoutB2FooRoute + '/jested/bar': typeof JestedLayoutB3LayoutC2BarRoute + '/nested/bar': typeof NestedLayoutB1LayoutC1BarRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute + '/jested': typeof JestedRouteRouteWithChildren + '/_layout-a1': typeof LayoutA1RouteWithChildren + '/_layout-a2': typeof LayoutA2RouteWithChildren + '/foo': typeof FooRouteWithChildren + '/foo/_layout-b5': typeof FooLayoutB5RouteRouteWithChildren + '/(folder)/in-folder': typeof folderInFolderRoute + '/_layout-a1/foo': typeof LayoutA1FooRoute + '/_layout-a2/bar': typeof LayoutA2BarRoute + '/foo/bar': typeof FooBarRoute + '/jested/_layout-b3': typeof JestedLayoutB3RouteWithChildren + '/jested/_layout-b4': typeof JestedLayoutB4RouteWithChildren + '/nested': typeof NestedRouteWithChildren + '/nested/_layout-b1': typeof NestedLayoutB1RouteWithChildren + '/nested/_layout-b2': typeof NestedLayoutB2RouteWithChildren + '/foo/_layout-b5/$id': typeof FooLayoutB5IdRoute + '/jested/_layout-b3/_layout-c2': typeof JestedLayoutB3LayoutC2RouteWithChildren + '/jested/_layout-b4/foo': typeof JestedLayoutB4FooRoute + '/nested/_layout-b1/_layout-c1': typeof NestedLayoutB1LayoutC1RouteWithChildren + '/nested/_layout-b2/foo': typeof NestedLayoutB2FooRoute + '/foo/_layout-b5/': typeof FooLayoutB5IndexRoute + '/jested/_layout-b3/': typeof JestedLayoutB3IndexRoute + '/nested/_layout-b1/': typeof NestedLayoutB1IndexRoute + '/jested/_layout-b3/_layout-c2/bar': typeof JestedLayoutB3LayoutC2BarRoute + '/nested/_layout-b1/_layout-c1/bar': typeof NestedLayoutB1LayoutC1BarRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: + | '/' + | '/jested' + | '/foo' + | '/in-folder' + | '/bar' + | '/foo/bar' + | '/nested' + | '/foo/$id' + | '/jested/foo' + | '/nested/foo' + | '/foo/' + | '/jested/' + | '/nested/' + | '/jested/bar' + | '/nested/bar' + fileRoutesByTo: FileRoutesByTo + to: + | '/' + | '/jested' + | '/foo' + | '/in-folder' + | '/bar' + | '/foo/bar' + | '/nested' + | '/foo/$id' + | '/jested/foo' + | '/nested/foo' + | '/jested/bar' + | '/nested/bar' + id: + | '__root__' + | '/' + | '/jested' + | '/_layout-a1' + | '/_layout-a2' + | '/foo' + | '/foo/_layout-b5' + | '/(folder)/in-folder' + | '/_layout-a1/foo' + | '/_layout-a2/bar' + | '/foo/bar' + | '/jested/_layout-b3' + | '/jested/_layout-b4' + | '/nested' + | '/nested/_layout-b1' + | '/nested/_layout-b2' + | '/foo/_layout-b5/$id' + | '/jested/_layout-b3/_layout-c2' + | '/jested/_layout-b4/foo' + | '/nested/_layout-b1/_layout-c1' + | '/nested/_layout-b2/foo' + | '/foo/_layout-b5/' + | '/jested/_layout-b3/' + | '/nested/_layout-b1/' + | '/jested/_layout-b3/_layout-c2/bar' + | '/nested/_layout-b1/_layout-c1/bar' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute + JestedRouteRoute: typeof JestedRouteRouteWithChildren + LayoutA1Route: typeof LayoutA1RouteWithChildren + LayoutA2Route: typeof LayoutA2RouteWithChildren + FooRoute: typeof FooRouteWithChildren + folderInFolderRoute: typeof folderInFolderRoute + NestedRoute: typeof NestedRouteWithChildren +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/nested': { + id: '/nested' + path: '/nested' + fullPath: '/nested' + preLoaderRoute: typeof NestedRouteImport + parentRoute: typeof rootRouteImport + } + '/foo': { + id: '/foo' + path: '/foo' + fullPath: '/foo' + preLoaderRoute: typeof FooRouteImport + parentRoute: typeof rootRouteImport + } + '/_layout-a2': { + id: '/_layout-a2' + path: '' + fullPath: '' + preLoaderRoute: typeof LayoutA2RouteImport + parentRoute: typeof rootRouteImport + } + '/_layout-a1': { + id: '/_layout-a1' + path: '' + fullPath: '' + preLoaderRoute: typeof LayoutA1RouteImport + parentRoute: typeof rootRouteImport + } + '/jested': { + id: '/jested' + path: '/jested' + fullPath: '/jested' + preLoaderRoute: typeof JestedRouteRouteImport + parentRoute: typeof rootRouteImport + } + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + '/nested/_layout-b2': { + id: '/nested/_layout-b2' + path: '' + fullPath: '/nested' + preLoaderRoute: typeof NestedLayoutB2RouteImport + parentRoute: typeof NestedRoute + } + '/nested/_layout-b1': { + id: '/nested/_layout-b1' + path: '/nested' + fullPath: '/nested' + preLoaderRoute: typeof NestedLayoutB1RouteImport + parentRoute: typeof NestedRoute + } + '/jested/_layout-b4': { + id: '/jested/_layout-b4' + path: '' + fullPath: '/jested' + preLoaderRoute: typeof JestedLayoutB4RouteImport + parentRoute: typeof JestedRouteRoute + } + '/jested/_layout-b3': { + id: '/jested/_layout-b3' + path: '' + fullPath: '/jested' + preLoaderRoute: typeof JestedLayoutB3RouteImport + parentRoute: typeof JestedRouteRoute + } + '/foo/bar': { + id: '/foo/bar' + path: '/bar' + fullPath: '/foo/bar' + preLoaderRoute: typeof FooBarRouteImport + parentRoute: typeof FooRoute + } + '/_layout-a2/bar': { + id: '/_layout-a2/bar' + path: '/bar' + fullPath: '/bar' + preLoaderRoute: typeof LayoutA2BarRouteImport + parentRoute: typeof LayoutA2Route + } + '/_layout-a1/foo': { + id: '/_layout-a1/foo' + path: '/foo' + fullPath: '/foo' + preLoaderRoute: typeof LayoutA1FooRouteImport + parentRoute: typeof LayoutA1Route + } + '/(folder)/in-folder': { + id: '/(folder)/in-folder' + path: '/in-folder' + fullPath: '/in-folder' + preLoaderRoute: typeof folderInFolderRouteImport + parentRoute: typeof rootRouteImport + } + '/foo/_layout-b5': { + id: '/foo/_layout-b5' + path: '/foo' + fullPath: '/foo' + preLoaderRoute: typeof FooLayoutB5RouteRouteImport + parentRoute: typeof FooRoute + } + '/nested/_layout-b1/': { + id: '/nested/_layout-b1/' + path: '/' + fullPath: '/nested/' + preLoaderRoute: typeof NestedLayoutB1IndexRouteImport + parentRoute: typeof NestedLayoutB1Route + } + '/jested/_layout-b3/': { + id: '/jested/_layout-b3/' + path: '/' + fullPath: '/jested/' + preLoaderRoute: typeof JestedLayoutB3IndexRouteImport + parentRoute: typeof JestedLayoutB3Route + } + '/foo/_layout-b5/': { + id: '/foo/_layout-b5/' + path: '/' + fullPath: '/foo/' + preLoaderRoute: typeof FooLayoutB5IndexRouteImport + parentRoute: typeof FooLayoutB5RouteRoute + } + '/nested/_layout-b2/foo': { + id: '/nested/_layout-b2/foo' + path: '/foo' + fullPath: '/nested/foo' + preLoaderRoute: typeof NestedLayoutB2FooRouteImport + parentRoute: typeof NestedLayoutB2Route + } + '/nested/_layout-b1/_layout-c1': { + id: '/nested/_layout-b1/_layout-c1' + path: '' + fullPath: '/nested' + preLoaderRoute: typeof NestedLayoutB1LayoutC1RouteImport + parentRoute: typeof NestedLayoutB1Route + } + '/jested/_layout-b4/foo': { + id: '/jested/_layout-b4/foo' + path: '/foo' + fullPath: '/jested/foo' + preLoaderRoute: typeof JestedLayoutB4FooRouteImport + parentRoute: typeof JestedLayoutB4Route + } + '/jested/_layout-b3/_layout-c2': { + id: '/jested/_layout-b3/_layout-c2' + path: '' + fullPath: '/jested' + preLoaderRoute: typeof JestedLayoutB3LayoutC2RouteImport + parentRoute: typeof JestedLayoutB3Route + } + '/foo/_layout-b5/$id': { + id: '/foo/_layout-b5/$id' + path: '/$id' + fullPath: '/foo/$id' + preLoaderRoute: typeof FooLayoutB5IdRouteImport + parentRoute: typeof FooLayoutB5RouteRoute + } + '/nested/_layout-b1/_layout-c1/bar': { + id: '/nested/_layout-b1/_layout-c1/bar' + path: '/bar' + fullPath: '/nested/bar' + preLoaderRoute: typeof NestedLayoutB1LayoutC1BarRouteImport + parentRoute: typeof NestedLayoutB1LayoutC1Route + } + '/jested/_layout-b3/_layout-c2/bar': { + id: '/jested/_layout-b3/_layout-c2/bar' + path: '/bar' + fullPath: '/jested/bar' + preLoaderRoute: typeof JestedLayoutB3LayoutC2BarRouteImport + parentRoute: typeof JestedLayoutB3LayoutC2Route + } + } +} + +interface JestedLayoutB3LayoutC2RouteChildren { + JestedLayoutB3LayoutC2BarRoute: typeof JestedLayoutB3LayoutC2BarRoute +} + +const JestedLayoutB3LayoutC2RouteChildren: JestedLayoutB3LayoutC2RouteChildren = + { + JestedLayoutB3LayoutC2BarRoute: JestedLayoutB3LayoutC2BarRoute, + } + +const JestedLayoutB3LayoutC2RouteWithChildren = + JestedLayoutB3LayoutC2Route._addFileChildren( + JestedLayoutB3LayoutC2RouteChildren, + ) + +interface JestedLayoutB3RouteChildren { + JestedLayoutB3LayoutC2Route: typeof JestedLayoutB3LayoutC2RouteWithChildren + JestedLayoutB3IndexRoute: typeof JestedLayoutB3IndexRoute +} + +const JestedLayoutB3RouteChildren: JestedLayoutB3RouteChildren = { + JestedLayoutB3LayoutC2Route: JestedLayoutB3LayoutC2RouteWithChildren, + JestedLayoutB3IndexRoute: JestedLayoutB3IndexRoute, +} + +const JestedLayoutB3RouteWithChildren = JestedLayoutB3Route._addFileChildren( + JestedLayoutB3RouteChildren, +) + +interface JestedLayoutB4RouteChildren { + JestedLayoutB4FooRoute: typeof JestedLayoutB4FooRoute +} + +const JestedLayoutB4RouteChildren: JestedLayoutB4RouteChildren = { + JestedLayoutB4FooRoute: JestedLayoutB4FooRoute, +} + +const JestedLayoutB4RouteWithChildren = JestedLayoutB4Route._addFileChildren( + JestedLayoutB4RouteChildren, +) + +interface JestedRouteRouteChildren { + JestedLayoutB3Route: typeof JestedLayoutB3RouteWithChildren + JestedLayoutB4Route: typeof JestedLayoutB4RouteWithChildren +} + +const JestedRouteRouteChildren: JestedRouteRouteChildren = { + JestedLayoutB3Route: JestedLayoutB3RouteWithChildren, + JestedLayoutB4Route: JestedLayoutB4RouteWithChildren, +} + +const JestedRouteRouteWithChildren = JestedRouteRoute._addFileChildren( + JestedRouteRouteChildren, +) + +interface LayoutA1RouteChildren { + LayoutA1FooRoute: typeof LayoutA1FooRoute +} + +const LayoutA1RouteChildren: LayoutA1RouteChildren = { + LayoutA1FooRoute: LayoutA1FooRoute, +} + +const LayoutA1RouteWithChildren = LayoutA1Route._addFileChildren( + LayoutA1RouteChildren, +) + +interface LayoutA2RouteChildren { + LayoutA2BarRoute: typeof LayoutA2BarRoute +} + +const LayoutA2RouteChildren: LayoutA2RouteChildren = { + LayoutA2BarRoute: LayoutA2BarRoute, +} + +const LayoutA2RouteWithChildren = LayoutA2Route._addFileChildren( + LayoutA2RouteChildren, +) + +interface FooLayoutB5RouteRouteChildren { + FooLayoutB5IdRoute: typeof FooLayoutB5IdRoute + FooLayoutB5IndexRoute: typeof FooLayoutB5IndexRoute +} + +const FooLayoutB5RouteRouteChildren: FooLayoutB5RouteRouteChildren = { + FooLayoutB5IdRoute: FooLayoutB5IdRoute, + FooLayoutB5IndexRoute: FooLayoutB5IndexRoute, +} + +const FooLayoutB5RouteRouteWithChildren = + FooLayoutB5RouteRoute._addFileChildren(FooLayoutB5RouteRouteChildren) + +interface FooRouteChildren { + FooLayoutB5RouteRoute: typeof FooLayoutB5RouteRouteWithChildren + FooBarRoute: typeof FooBarRoute +} + +const FooRouteChildren: FooRouteChildren = { + FooLayoutB5RouteRoute: FooLayoutB5RouteRouteWithChildren, + FooBarRoute: FooBarRoute, +} + +const FooRouteWithChildren = FooRoute._addFileChildren(FooRouteChildren) + +interface NestedLayoutB1LayoutC1RouteChildren { + NestedLayoutB1LayoutC1BarRoute: typeof NestedLayoutB1LayoutC1BarRoute +} + +const NestedLayoutB1LayoutC1RouteChildren: NestedLayoutB1LayoutC1RouteChildren = + { + NestedLayoutB1LayoutC1BarRoute: NestedLayoutB1LayoutC1BarRoute, + } + +const NestedLayoutB1LayoutC1RouteWithChildren = + NestedLayoutB1LayoutC1Route._addFileChildren( + NestedLayoutB1LayoutC1RouteChildren, + ) + +interface NestedLayoutB1RouteChildren { + NestedLayoutB1LayoutC1Route: typeof NestedLayoutB1LayoutC1RouteWithChildren + NestedLayoutB1IndexRoute: typeof NestedLayoutB1IndexRoute +} + +const NestedLayoutB1RouteChildren: NestedLayoutB1RouteChildren = { + NestedLayoutB1LayoutC1Route: NestedLayoutB1LayoutC1RouteWithChildren, + NestedLayoutB1IndexRoute: NestedLayoutB1IndexRoute, +} + +const NestedLayoutB1RouteWithChildren = NestedLayoutB1Route._addFileChildren( + NestedLayoutB1RouteChildren, +) + +interface NestedLayoutB2RouteChildren { + NestedLayoutB2FooRoute: typeof NestedLayoutB2FooRoute +} + +const NestedLayoutB2RouteChildren: NestedLayoutB2RouteChildren = { + NestedLayoutB2FooRoute: NestedLayoutB2FooRoute, +} + +const NestedLayoutB2RouteWithChildren = NestedLayoutB2Route._addFileChildren( + NestedLayoutB2RouteChildren, +) + +interface NestedRouteChildren { + NestedLayoutB1Route: typeof NestedLayoutB1RouteWithChildren + NestedLayoutB2Route: typeof NestedLayoutB2RouteWithChildren +} + +const NestedRouteChildren: NestedRouteChildren = { + NestedLayoutB1Route: NestedLayoutB1RouteWithChildren, + NestedLayoutB2Route: NestedLayoutB2RouteWithChildren, +} + +const NestedRouteWithChildren = + NestedRoute._addFileChildren(NestedRouteChildren) + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, + JestedRouteRoute: JestedRouteRouteWithChildren, + LayoutA1Route: LayoutA1RouteWithChildren, + LayoutA2Route: LayoutA2RouteWithChildren, + FooRoute: FooRouteWithChildren, + folderInFolderRoute: folderInFolderRoute, + NestedRoute: NestedRouteWithChildren, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/packages/router-generator/tests/generator/nested-route-groups-with-layouts-before-physical/routeTree.nonnested.snapshot.ts b/packages/router-generator/tests/generator/nested-route-groups-with-layouts-before-physical/routeTree.nonnested.snapshot.ts new file mode 100644 index 00000000000..a3b5c3f72fa --- /dev/null +++ b/packages/router-generator/tests/generator/nested-route-groups-with-layouts-before-physical/routeTree.nonnested.snapshot.ts @@ -0,0 +1,274 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { createFileRoute } from '@tanstack/react-router' + +import { Route as rootRouteImport } from './routes/__root' +import { Route as groupCLayoutCRouteImport } from './routes/(group-c)/_layout-c' +import { Route as groupBLayoutBRouteImport } from './routes/(group-b)/_layout-b' +import { Route as groupALayoutARouteImport } from './routes/(group-a)/_layout-a' +import { Route as groupCLayoutCIndexRouteImport } from './routes/(group-c)/_layout-c/index' +import { Route as groupBLayoutBDashboardRouteImport } from './routes/(group-b)/_layout-b/dashboard' +import { Route as groupALayoutASignupRouteImport } from './routes/(group-a)/_layout-a/signup' +import { Route as groupALayoutALoginRouteImport } from './routes/(group-a)/_layout-a/login' + +const groupCRouteImport = createFileRoute('/(group-c)')() +const groupBRouteImport = createFileRoute('/(group-b)')() +const groupARouteImport = createFileRoute('/(group-a)')() + +const groupCRoute = groupCRouteImport.update({ + id: '/(group-c)', + getParentRoute: () => rootRouteImport, +} as any) +const groupBRoute = groupBRouteImport.update({ + id: '/(group-b)', + getParentRoute: () => rootRouteImport, +} as any) +const groupARoute = groupARouteImport.update({ + id: '/(group-a)', + getParentRoute: () => rootRouteImport, +} as any) +const groupCLayoutCRoute = groupCLayoutCRouteImport.update({ + id: '/_layout-c', + getParentRoute: () => groupCRoute, +} as any) +const groupBLayoutBRoute = groupBLayoutBRouteImport.update({ + id: '/_layout-b', + getParentRoute: () => groupBRoute, +} as any) +const groupALayoutARoute = groupALayoutARouteImport.update({ + id: '/_layout-a', + getParentRoute: () => groupARoute, +} as any) +const groupCLayoutCIndexRoute = groupCLayoutCIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => groupCLayoutCRoute, +} as any) +const groupBLayoutBDashboardRoute = groupBLayoutBDashboardRouteImport.update({ + id: '/dashboard', + path: '/dashboard', + getParentRoute: () => groupBLayoutBRoute, +} as any) +const groupALayoutASignupRoute = groupALayoutASignupRouteImport.update({ + id: '/signup', + path: '/signup', + getParentRoute: () => groupALayoutARoute, +} as any) +const groupALayoutALoginRoute = groupALayoutALoginRouteImport.update({ + id: '/login', + path: '/login', + getParentRoute: () => groupALayoutARoute, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof groupCLayoutCIndexRoute + '/login': typeof groupALayoutALoginRoute + '/signup': typeof groupALayoutASignupRoute + '/dashboard': typeof groupBLayoutBDashboardRoute +} +export interface FileRoutesByTo { + '/': typeof groupCLayoutCIndexRoute + '/login': typeof groupALayoutALoginRoute + '/signup': typeof groupALayoutASignupRoute + '/dashboard': typeof groupBLayoutBDashboardRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/(group-a)': typeof groupARouteWithChildren + '/(group-a)/_layout-a': typeof groupALayoutARouteWithChildren + '/(group-b)': typeof groupBRouteWithChildren + '/(group-b)/_layout-b': typeof groupBLayoutBRouteWithChildren + '/(group-c)': typeof groupCRouteWithChildren + '/(group-c)/_layout-c': typeof groupCLayoutCRouteWithChildren + '/(group-a)/_layout-a/login': typeof groupALayoutALoginRoute + '/(group-a)/_layout-a/signup': typeof groupALayoutASignupRoute + '/(group-b)/_layout-b/dashboard': typeof groupBLayoutBDashboardRoute + '/(group-c)/_layout-c/': typeof groupCLayoutCIndexRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' | '/login' | '/signup' | '/dashboard' + fileRoutesByTo: FileRoutesByTo + to: '/' | '/login' | '/signup' | '/dashboard' + id: + | '__root__' + | '/(group-a)' + | '/(group-a)/_layout-a' + | '/(group-b)' + | '/(group-b)/_layout-b' + | '/(group-c)' + | '/(group-c)/_layout-c' + | '/(group-a)/_layout-a/login' + | '/(group-a)/_layout-a/signup' + | '/(group-b)/_layout-b/dashboard' + | '/(group-c)/_layout-c/' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + groupARoute: typeof groupARouteWithChildren + groupBRoute: typeof groupBRouteWithChildren + groupCRoute: typeof groupCRouteWithChildren +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/(group-c)': { + id: '/(group-c)' + path: '/' + fullPath: '/' + preLoaderRoute: typeof groupCRouteImport + parentRoute: typeof rootRouteImport + } + '/(group-b)': { + id: '/(group-b)' + path: '/' + fullPath: '/' + preLoaderRoute: typeof groupBRouteImport + parentRoute: typeof rootRouteImport + } + '/(group-a)': { + id: '/(group-a)' + path: '/' + fullPath: '/' + preLoaderRoute: typeof groupARouteImport + parentRoute: typeof rootRouteImport + } + '/(group-c)/_layout-c': { + id: '/(group-c)/_layout-c' + path: '/' + fullPath: '/' + preLoaderRoute: typeof groupCLayoutCRouteImport + parentRoute: typeof groupCRoute + } + '/(group-b)/_layout-b': { + id: '/(group-b)/_layout-b' + path: '/' + fullPath: '/' + preLoaderRoute: typeof groupBLayoutBRouteImport + parentRoute: typeof groupBRoute + } + '/(group-a)/_layout-a': { + id: '/(group-a)/_layout-a' + path: '/' + fullPath: '/' + preLoaderRoute: typeof groupALayoutARouteImport + parentRoute: typeof groupARoute + } + '/(group-c)/_layout-c/': { + id: '/(group-c)/_layout-c/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof groupCLayoutCIndexRouteImport + parentRoute: typeof groupCLayoutCRoute + } + '/(group-b)/_layout-b/dashboard': { + id: '/(group-b)/_layout-b/dashboard' + path: '/dashboard' + fullPath: '/dashboard' + preLoaderRoute: typeof groupBLayoutBDashboardRouteImport + parentRoute: typeof groupBLayoutBRoute + } + '/(group-a)/_layout-a/signup': { + id: '/(group-a)/_layout-a/signup' + path: '/signup' + fullPath: '/signup' + preLoaderRoute: typeof groupALayoutASignupRouteImport + parentRoute: typeof groupALayoutARoute + } + '/(group-a)/_layout-a/login': { + id: '/(group-a)/_layout-a/login' + path: '/login' + fullPath: '/login' + preLoaderRoute: typeof groupALayoutALoginRouteImport + parentRoute: typeof groupALayoutARoute + } + } +} + +interface groupALayoutARouteChildren { + groupALayoutALoginRoute: typeof groupALayoutALoginRoute + groupALayoutASignupRoute: typeof groupALayoutASignupRoute +} + +const groupALayoutARouteChildren: groupALayoutARouteChildren = { + groupALayoutALoginRoute: groupALayoutALoginRoute, + groupALayoutASignupRoute: groupALayoutASignupRoute, +} + +const groupALayoutARouteWithChildren = groupALayoutARoute._addFileChildren( + groupALayoutARouteChildren, +) + +interface groupARouteChildren { + groupALayoutARoute: typeof groupALayoutARouteWithChildren +} + +const groupARouteChildren: groupARouteChildren = { + groupALayoutARoute: groupALayoutARouteWithChildren, +} + +const groupARouteWithChildren = + groupARoute._addFileChildren(groupARouteChildren) + +interface groupBLayoutBRouteChildren { + groupBLayoutBDashboardRoute: typeof groupBLayoutBDashboardRoute +} + +const groupBLayoutBRouteChildren: groupBLayoutBRouteChildren = { + groupBLayoutBDashboardRoute: groupBLayoutBDashboardRoute, +} + +const groupBLayoutBRouteWithChildren = groupBLayoutBRoute._addFileChildren( + groupBLayoutBRouteChildren, +) + +interface groupBRouteChildren { + groupBLayoutBRoute: typeof groupBLayoutBRouteWithChildren +} + +const groupBRouteChildren: groupBRouteChildren = { + groupBLayoutBRoute: groupBLayoutBRouteWithChildren, +} + +const groupBRouteWithChildren = + groupBRoute._addFileChildren(groupBRouteChildren) + +interface groupCLayoutCRouteChildren { + groupCLayoutCIndexRoute: typeof groupCLayoutCIndexRoute +} + +const groupCLayoutCRouteChildren: groupCLayoutCRouteChildren = { + groupCLayoutCIndexRoute: groupCLayoutCIndexRoute, +} + +const groupCLayoutCRouteWithChildren = groupCLayoutCRoute._addFileChildren( + groupCLayoutCRouteChildren, +) + +interface groupCRouteChildren { + groupCLayoutCRoute: typeof groupCLayoutCRouteWithChildren +} + +const groupCRouteChildren: groupCRouteChildren = { + groupCLayoutCRoute: groupCLayoutCRouteWithChildren, +} + +const groupCRouteWithChildren = + groupCRoute._addFileChildren(groupCRouteChildren) + +const rootRouteChildren: RootRouteChildren = { + groupARoute: groupARouteWithChildren, + groupBRoute: groupBRouteWithChildren, + groupCRoute: groupCRouteWithChildren, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/packages/router-generator/tests/generator/nested-verboseFileRoutes-false/routeTree.nonnested.snapshot.ts b/packages/router-generator/tests/generator/nested-verboseFileRoutes-false/routeTree.nonnested.snapshot.ts new file mode 100644 index 00000000000..fa3545870a9 --- /dev/null +++ b/packages/router-generator/tests/generator/nested-verboseFileRoutes-false/routeTree.nonnested.snapshot.ts @@ -0,0 +1,331 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import type { CreateFileRoute, FileRoutesByPath } from '@tanstack/react-router' + +import { Route as rootRouteImport } from './routes/__root' +import { Route as PostsRouteRouteImport } from './routes/posts/route' +import { Route as BlogRouteRouteImport } from './routes/blog/route' +import { Route as IndexRouteImport } from './routes/index' +import { Route as PostsIndexRouteImport } from './routes/posts/index' +import { Route as BlogIndexRouteImport } from './routes/blog/index' +import { Route as BlogStatsRouteImport } from './routes/blog_/stats' +import { Route as BlogSlugRouteImport } from './routes/blog/$slug' +import { Route as PostsPostIdIndexRouteImport } from './routes/posts/$postId/index' +import { Route as PostsPostIdDeepRouteImport } from './routes/posts/$postId/deep' + +const PostsRouteRoute = PostsRouteRouteImport.update({ + id: '/posts', + path: '/posts', + getParentRoute: () => rootRouteImport, +} as any) +const BlogRouteRoute = BlogRouteRouteImport.update({ + id: '/blog', + path: '/blog', + getParentRoute: () => rootRouteImport, +} as any) +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) +const PostsIndexRoute = PostsIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => PostsRouteRoute, +} as any) +const BlogIndexRoute = BlogIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => BlogRouteRoute, +} as any) +const BlogStatsRoute = BlogStatsRouteImport.update({ + id: '/blog/stats', + path: '/blog/stats', + getParentRoute: () => rootRouteImport, +} as any) +const BlogSlugRoute = BlogSlugRouteImport.update({ + id: '/$slug', + path: '/$slug', + getParentRoute: () => BlogRouteRoute, +} as any) +const PostsPostIdIndexRoute = PostsPostIdIndexRouteImport.update({ + id: '/$postId/', + path: '/$postId/', + getParentRoute: () => PostsRouteRoute, +} as any) +const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ + id: '/$postId/deep', + path: '/$postId/deep', + getParentRoute: () => PostsRouteRoute, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute + '/blog': typeof BlogRouteRouteWithChildren + '/posts': typeof PostsRouteRouteWithChildren + '/blog/$slug': typeof BlogSlugRoute + '/blog/stats': typeof BlogStatsRoute + '/blog/': typeof BlogIndexRoute + '/posts/': typeof PostsIndexRoute + '/posts/$postId/deep': typeof PostsPostIdDeepRoute + '/posts/$postId': typeof PostsPostIdIndexRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute + '/blog/$slug': typeof BlogSlugRoute + '/blog/stats': typeof BlogStatsRoute + '/blog': typeof BlogIndexRoute + '/posts': typeof PostsIndexRoute + '/posts/$postId/deep': typeof PostsPostIdDeepRoute + '/posts/$postId': typeof PostsPostIdIndexRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute + '/blog': typeof BlogRouteRouteWithChildren + '/posts': typeof PostsRouteRouteWithChildren + '/blog/$slug': typeof BlogSlugRoute + '/blog/stats': typeof BlogStatsRoute + '/blog/': typeof BlogIndexRoute + '/posts/': typeof PostsIndexRoute + '/posts/$postId/deep': typeof PostsPostIdDeepRoute + '/posts/$postId/': typeof PostsPostIdIndexRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: + | '/' + | '/blog' + | '/posts' + | '/blog/$slug' + | '/blog/stats' + | '/blog/' + | '/posts/' + | '/posts/$postId/deep' + | '/posts/$postId' + fileRoutesByTo: FileRoutesByTo + to: + | '/' + | '/blog/$slug' + | '/blog/stats' + | '/blog' + | '/posts' + | '/posts/$postId/deep' + | '/posts/$postId' + id: + | '__root__' + | '/' + | '/blog' + | '/posts' + | '/blog/$slug' + | '/blog/stats' + | '/blog/' + | '/posts/' + | '/posts/$postId/deep' + | '/posts/$postId/' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute + BlogRouteRoute: typeof BlogRouteRouteWithChildren + PostsRouteRoute: typeof PostsRouteRouteWithChildren + BlogStatsRoute: typeof BlogStatsRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteRouteImport + parentRoute: typeof rootRouteImport + } + '/blog': { + id: '/blog' + path: '/blog' + fullPath: '/blog' + preLoaderRoute: typeof BlogRouteRouteImport + parentRoute: typeof rootRouteImport + } + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + '/posts/': { + id: '/posts/' + path: '/' + fullPath: '/posts/' + preLoaderRoute: typeof PostsIndexRouteImport + parentRoute: typeof PostsRouteRoute + } + '/blog/': { + id: '/blog/' + path: '/' + fullPath: '/blog/' + preLoaderRoute: typeof BlogIndexRouteImport + parentRoute: typeof BlogRouteRoute + } + '/blog/stats': { + id: '/blog/stats' + path: '/blog/stats' + fullPath: '/blog/stats' + preLoaderRoute: typeof BlogStatsRouteImport + parentRoute: typeof rootRouteImport + } + '/blog/$slug': { + id: '/blog/$slug' + path: '/$slug' + fullPath: '/blog/$slug' + preLoaderRoute: typeof BlogSlugRouteImport + parentRoute: typeof BlogRouteRoute + } + '/posts/$postId/': { + id: '/posts/$postId/' + path: '/$postId' + fullPath: '/posts/$postId' + preLoaderRoute: typeof PostsPostIdIndexRouteImport + parentRoute: typeof PostsRouteRoute + } + '/posts/$postId/deep': { + id: '/posts/$postId/deep' + path: '/$postId/deep' + fullPath: '/posts/$postId/deep' + preLoaderRoute: typeof PostsPostIdDeepRouteImport + parentRoute: typeof PostsRouteRoute + } + } +} + +declare module './routes/index' { + const createFileRoute: CreateFileRoute< + '/', + FileRoutesByPath['/']['parentRoute'], + FileRoutesByPath['/']['id'], + FileRoutesByPath['/']['path'], + FileRoutesByPath['/']['fullPath'] + > +} +declare module './routes/blog/route' { + const createFileRoute: CreateFileRoute< + '/blog', + FileRoutesByPath['/blog']['parentRoute'], + FileRoutesByPath['/blog']['id'], + FileRoutesByPath['/blog']['path'], + FileRoutesByPath['/blog']['fullPath'] + > +} +declare module './routes/posts/route' { + const createFileRoute: CreateFileRoute< + '/posts', + FileRoutesByPath['/posts']['parentRoute'], + FileRoutesByPath['/posts']['id'], + FileRoutesByPath['/posts']['path'], + FileRoutesByPath['/posts']['fullPath'] + > +} +declare module './routes/blog/$slug' { + const createFileRoute: CreateFileRoute< + '/blog/$slug', + FileRoutesByPath['/blog/$slug']['parentRoute'], + FileRoutesByPath['/blog/$slug']['id'], + FileRoutesByPath['/blog/$slug']['path'], + FileRoutesByPath['/blog/$slug']['fullPath'] + > +} +declare module './routes/blog_/stats' { + const createFileRoute: CreateFileRoute< + '/blog/stats', + FileRoutesByPath['/blog/stats']['parentRoute'], + FileRoutesByPath['/blog/stats']['id'], + FileRoutesByPath['/blog/stats']['path'], + FileRoutesByPath['/blog/stats']['fullPath'] + > +} +declare module './routes/blog/index' { + const createFileRoute: CreateFileRoute< + '/blog/', + FileRoutesByPath['/blog/']['parentRoute'], + FileRoutesByPath['/blog/']['id'], + FileRoutesByPath['/blog/']['path'], + FileRoutesByPath['/blog/']['fullPath'] + > +} +declare module './routes/posts/index' { + const createFileRoute: CreateFileRoute< + '/posts/', + FileRoutesByPath['/posts/']['parentRoute'], + FileRoutesByPath['/posts/']['id'], + FileRoutesByPath['/posts/']['path'], + FileRoutesByPath['/posts/']['fullPath'] + > +} +declare module './routes/posts/$postId/deep' { + const createFileRoute: CreateFileRoute< + '/posts/$postId/deep', + FileRoutesByPath['/posts/$postId/deep']['parentRoute'], + FileRoutesByPath['/posts/$postId/deep']['id'], + FileRoutesByPath['/posts/$postId/deep']['path'], + FileRoutesByPath['/posts/$postId/deep']['fullPath'] + > +} +declare module './routes/posts/$postId/index' { + const createFileRoute: CreateFileRoute< + '/posts/$postId/', + FileRoutesByPath['/posts/$postId/']['parentRoute'], + FileRoutesByPath['/posts/$postId/']['id'], + FileRoutesByPath['/posts/$postId/']['path'], + FileRoutesByPath['/posts/$postId/']['fullPath'] + > +} + +interface BlogRouteRouteChildren { + BlogSlugRoute: typeof BlogSlugRoute + BlogIndexRoute: typeof BlogIndexRoute +} + +const BlogRouteRouteChildren: BlogRouteRouteChildren = { + BlogSlugRoute: BlogSlugRoute, + BlogIndexRoute: BlogIndexRoute, +} + +const BlogRouteRouteWithChildren = BlogRouteRoute._addFileChildren( + BlogRouteRouteChildren, +) + +interface PostsRouteRouteChildren { + PostsIndexRoute: typeof PostsIndexRoute + PostsPostIdDeepRoute: typeof PostsPostIdDeepRoute + PostsPostIdIndexRoute: typeof PostsPostIdIndexRoute +} + +const PostsRouteRouteChildren: PostsRouteRouteChildren = { + PostsIndexRoute: PostsIndexRoute, + PostsPostIdDeepRoute: PostsPostIdDeepRoute, + PostsPostIdIndexRoute: PostsPostIdIndexRoute, +} + +const PostsRouteRouteWithChildren = PostsRouteRoute._addFileChildren( + PostsRouteRouteChildren, +) + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, + BlogRouteRoute: BlogRouteRouteWithChildren, + PostsRouteRoute: PostsRouteRouteWithChildren, + BlogStatsRoute: BlogStatsRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/packages/router-generator/tests/generator/nested-verboseFileRoutes-false/tests.nonnested.test-d.ts b/packages/router-generator/tests/generator/nested-verboseFileRoutes-false/tests.nonnested.test-d.ts new file mode 100644 index 00000000000..8a6b9948f97 --- /dev/null +++ b/packages/router-generator/tests/generator/nested-verboseFileRoutes-false/tests.nonnested.test-d.ts @@ -0,0 +1,539 @@ +import { + Link, + createRouter, + redirect, + useLoaderData, + useLoaderDeps, + useMatch, + useNavigate, + useParams, + useRouteContext, + useSearch, +} from '@tanstack/react-router' +import { expectTypeOf, test } from 'vitest' +import { routeTree } from './routeTree.nonnested.gen' +import type { MakeRouteMatch } from '@tanstack/react-router' + +const defaultRouter = createRouter({ + routeTree, +}) + +type DefaultRouter = typeof defaultRouter + +const alwaysTrailingSlashRouter = createRouter({ + routeTree, + trailingSlash: 'always', +}) + +const neverTrailingSlashRouter = createRouter({ + routeTree, + trailingSlash: 'never', +}) + +const preserveTrailingSlashRouter = createRouter({ + routeTree, + trailingSlash: 'preserve', +}) + +test('when navigating to the root', () => { + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('to') + .toEqualTypeOf< + | '/' + | '/blog' + | '/posts' + | '/blog/$slug' + | '/blog/stats' + | '/posts/$postId/deep' + | '/posts/$postId' + | undefined + >() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('to') + .toEqualTypeOf< + | '/' + | '/blog/' + | '/posts/' + | '/blog/$slug/' + | '/blog/stats/' + | '/posts/$postId/deep/' + | '/posts/$postId/' + | undefined + >() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('to') + .toEqualTypeOf< + | '/' + | '/blog' + | '/posts' + | '/blog/$slug' + | '/blog/stats' + | '/posts/$postId/deep' + | '/posts/$postId' + | undefined + >() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('to') + .toEqualTypeOf< + | '/' + | '/blog' + | '/posts' + | '/blog/$slug' + | '/blog/stats' + | '/posts/$postId/deep' + | '/posts/$postId' + | '/blog/' + | '/posts/' + | '/blog/$slug/' + | '/blog/stats/' + | '/posts/$postId/deep/' + | '/posts/$postId/' + | undefined + >() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('from') + .toEqualTypeOf< + | '/' + | '/blog' + | '/posts' + | '/blog/$slug' + | '/blog/stats' + | '/blog/' + | '/posts/' + | '/posts/$postId/deep' + | '/posts/$postId' + | undefined + >() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('search') + .exclude<(...args: any) => any>() + .toEqualTypeOf() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('search') + .exclude<(...args: any) => any>() + .toEqualTypeOf() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('search') + .exclude<(...args: any) => any>() + .toEqualTypeOf() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('search') + .exclude<(...args: any) => any>() + .toEqualTypeOf() +}) + +test('when navigating a index route with search and params', () => { + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('to') + .toEqualTypeOf< + | '.' + | '..' + | '/' + | '/blog' + | '/posts' + | '/blog/$slug' + | '/blog/stats' + | '/posts/$postId/deep' + | '/posts/$postId' + >() + + expectTypeOf( + Link, + ) + .parameter(0) + .toHaveProperty('to') + .toEqualTypeOf< + | './' + | '../' + | '/' + | '/blog/' + | '/posts/' + | '/blog/$slug/' + | '/blog/stats/' + | '/posts/$postId/deep/' + | '/posts/$postId/' + >() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('to') + .toEqualTypeOf< + | '/' + | '/posts/$postId' + | '/blog' + | '/posts' + | '/blog/$slug' + | '/blog/stats' + | '/posts/$postId/deep' + | '/posts/$postId' + | '.' + | '..' + >() + + expectTypeOf( + Link, + ) + .parameter(0) + .toHaveProperty('to') + .toEqualTypeOf< + | '..' + | '../' + | '.' + | './' + | '/' + | '/blog' + | '/posts' + | '/blog/$slug' + | '/blog/stats' + | '/posts/$postId/deep' + | '/posts/$postId' + | '/blog/' + | '/posts/' + | '/blog/$slug/' + | '/blog/stats/' + | '/posts/$postId/deep/' + | '/posts/$postId/' + >() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('from') + .toEqualTypeOf< + | '/' + | '/posts/$postId' + | '/blog' + | '/posts' + | '/blog/$slug' + | '/blog/stats' + | '/blog/' + | '/posts/' + | '/posts/$postId/deep' + | undefined + >() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('search') + .exclude<(...args: any) => any>() + .toEqualTypeOf<{ indexSearch: string }>() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('search') + .exclude<(...args: any) => any>() + .toEqualTypeOf() + + expectTypeOf( + Link, + ) + .parameter(0) + .toHaveProperty('search') + .exclude<(...args: any) => any>() + .toEqualTypeOf<{ indexSearch: string }>() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('search') + .exclude<(...args: any) => any>() + .toEqualTypeOf() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('search') + .exclude<(...args: any) => any>() + .toEqualTypeOf<{ indexSearch: string }>() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('search') + .exclude<(...args: any) => any>() + .toEqualTypeOf<{ indexSearch: string }>() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('search') + .exclude<(...args: any) => any>() + .toEqualTypeOf<{ indexSearch: string }>() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('params') + .exclude<(...args: any) => any>() + .toEqualTypeOf<{ postId: string }>() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('params') + .exclude<(...args: any) => any>() + .toEqualTypeOf() + + expectTypeOf( + Link, + ) + .parameter(0) + .toHaveProperty('params') + .exclude<(...args: any) => any>() + .toEqualTypeOf<{ postId: string }>() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('params') + .exclude<(...args: any) => any>() + .toEqualTypeOf() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('params') + .exclude<(...args: any) => any>() + .toEqualTypeOf<{ postId: string }>() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('params') + .exclude<(...args: any) => any>() + .toEqualTypeOf<{ postId: string }>() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('params') + .exclude<(...args: any) => any>() + .toEqualTypeOf<{ postId: string }>() +}) + +test('when navigating from a index route with search and params', () => { + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('from') + .toEqualTypeOf< + | '/' + | '/posts/$postId' + | '/blog' + | '/posts' + | '/blog/$slug' + | '/blog/stats' + | '/blog/' + | '/posts/' + | '/posts/$postId/deep' + | undefined + >() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('search') + .parameter(0) + .toEqualTypeOf<{ indexSearch: string }>() +}) + +test('when using useNavigate', () => { + const navigate = useNavigate() + + expectTypeOf(navigate) + .parameter(0) + .toHaveProperty('to') + .toEqualTypeOf< + | '/' + | '.' + | '..' + | '/blog' + | '/posts' + | '/blog/$slug' + | '/blog/stats' + | '/posts/$postId/deep' + | '/posts/$postId' + >() +}) + +test('when using redirect', () => { + expectTypeOf(redirect) + .parameter(0) + .toHaveProperty('to') + .toEqualTypeOf< + | '/' + | '/posts/$postId' + | '/blog' + | '/posts' + | '/blog/$slug' + | '/blog/stats' + | '/posts/$postId/deep' + | undefined + >() +}) + +test('when using useSearch from a route with no search', () => { + expectTypeOf(useSearch) + .parameter(0) + .toHaveProperty('from') + .toEqualTypeOf< + | '__root__' + | '/' + | '/blog' + | '/blog/' + | '/posts' + | '/blog/$slug' + | '/blog/stats' + | '/blog/' + | '/posts/' + | '/posts/$postId/deep' + | '/posts/$postId/' + >() + + expectTypeOf(useSearch).returns.toEqualTypeOf<{}>() +}) + +test('when using useSearch from a route with search', () => { + expectTypeOf(useSearch) + .parameter(0) + .toHaveProperty('from') + .toEqualTypeOf< + | '__root__' + | '/' + | '/blog' + | '/blog/' + | '/posts' + | '/blog/$slug' + | '/blog/stats' + | '/blog/' + | '/posts/' + | '/posts/$postId/deep' + | '/posts/$postId/' + >() + + expectTypeOf( + useSearch, + ).returns.toEqualTypeOf<{ indexSearch: string }>() +}) + +test('when using useLoaderData from a route with loaderData', () => { + expectTypeOf(useLoaderData) + .parameter(0) + .toHaveProperty('from') + .toEqualTypeOf< + | '__root__' + | '/' + | '/blog' + | '/blog/' + | '/posts' + | '/blog/$slug' + | '/blog/stats' + | '/blog/' + | '/posts/' + | '/posts/$postId/deep' + | '/posts/$postId/' + >() + + expectTypeOf( + useLoaderData, + ).returns.toEqualTypeOf<{ data: string }>() +}) + +test('when using useLoaderDeps from a route with loaderDeps', () => { + expectTypeOf(useLoaderDeps) + .parameter(0) + .toHaveProperty('from') + .toEqualTypeOf< + | '__root__' + | '/' + | '/blog' + | '/blog/' + | '/posts' + | '/blog/$slug' + | '/blog/stats' + | '/blog/' + | '/posts/' + | '/posts/$postId/deep' + | '/posts/$postId/' + >() + + expectTypeOf( + useLoaderDeps, + ).returns.toEqualTypeOf<{ dep: number }>() +}) + +test('when using useMatch from a route', () => { + expectTypeOf(useMatch) + .parameter(0) + .toHaveProperty('from') + .toEqualTypeOf< + | '__root__' + | '/' + | '/blog' + | '/blog/' + | '/posts' + | '/blog/$slug' + | '/blog/stats' + | '/blog/' + | '/posts/' + | '/posts/$postId/deep' + | '/posts/$postId/' + >() + + expectTypeOf( + useMatch, + ).returns.toEqualTypeOf< + MakeRouteMatch + >() +}) + +test('when using useParams from a route', () => { + expectTypeOf(useParams) + .parameter(0) + .toHaveProperty('from') + .toEqualTypeOf< + | '__root__' + | '/' + | '/blog' + | '/blog/' + | '/posts' + | '/blog/$slug' + | '/blog/stats' + | '/blog/' + | '/posts/' + | '/posts/$postId/deep' + | '/posts/$postId/' + >() + + expectTypeOf( + useParams, + ).returns.toEqualTypeOf<{ postId: string }>() +}) + +test('when using useRouteContext from a route', () => { + expectTypeOf(useRouteContext) + .parameter(0) + .toHaveProperty('from') + .toEqualTypeOf< + | '__root__' + | '/' + | '/blog' + | '/blog/' + | '/posts' + | '/blog/$slug' + | '/blog/stats' + | '/blog/' + | '/posts/' + | '/posts/$postId/deep' + | '/posts/$postId/' + >() + + expectTypeOf( + useRouteContext, + ).returns.toEqualTypeOf<{ someContext: string }>() +}) diff --git a/packages/router-generator/tests/generator/nested-verboseFileRoutes-false/tests.test-d.ts b/packages/router-generator/tests/generator/nested-verboseFileRoutes-false/tests.test-d.ts index a349a48c5e5..877082339fc 100644 --- a/packages/router-generator/tests/generator/nested-verboseFileRoutes-false/tests.test-d.ts +++ b/packages/router-generator/tests/generator/nested-verboseFileRoutes-false/tests.test-d.ts @@ -1,7 +1,6 @@ import { - createRouter, Link, - MakeRouteMatch, + createRouter, redirect, useLoaderData, useLoaderDeps, @@ -11,8 +10,10 @@ import { useRouteContext, useSearch, } from '@tanstack/react-router' -import { test, expectTypeOf } from 'vitest' +import { expectTypeOf, test } from 'vitest' import { routeTree } from './routeTree.gen' +import type { + MakeRouteMatch} from '@tanstack/react-router'; const defaultRouter = createRouter({ routeTree, diff --git a/packages/router-generator/tests/generator/nested-verboseFileRoutes-true/routeTree.nonnested.snapshot.ts b/packages/router-generator/tests/generator/nested-verboseFileRoutes-true/routeTree.nonnested.snapshot.ts new file mode 100644 index 00000000000..a7f314ecc87 --- /dev/null +++ b/packages/router-generator/tests/generator/nested-verboseFileRoutes-true/routeTree.nonnested.snapshot.ts @@ -0,0 +1,247 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/__root' +import { Route as PostsRouteRouteImport } from './routes/posts/route' +import { Route as BlogRouteRouteImport } from './routes/blog/route' +import { Route as IndexRouteImport } from './routes/index' +import { Route as PostsIndexRouteImport } from './routes/posts/index' +import { Route as BlogIndexRouteImport } from './routes/blog/index' +import { Route as BlogStatsRouteImport } from './routes/blog_/stats' +import { Route as BlogSlugRouteImport } from './routes/blog/$slug' +import { Route as PostsPostIdIndexRouteImport } from './routes/posts/$postId/index' +import { Route as PostsPostIdDeepRouteImport } from './routes/posts/$postId/deep' + +const PostsRouteRoute = PostsRouteRouteImport.update({ + id: '/posts', + path: '/posts', + getParentRoute: () => rootRouteImport, +} as any) +const BlogRouteRoute = BlogRouteRouteImport.update({ + id: '/blog', + path: '/blog', + getParentRoute: () => rootRouteImport, +} as any) +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) +const PostsIndexRoute = PostsIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => PostsRouteRoute, +} as any) +const BlogIndexRoute = BlogIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => BlogRouteRoute, +} as any) +const BlogStatsRoute = BlogStatsRouteImport.update({ + id: '/blog/stats', + path: '/blog/stats', + getParentRoute: () => rootRouteImport, +} as any) +const BlogSlugRoute = BlogSlugRouteImport.update({ + id: '/$slug', + path: '/$slug', + getParentRoute: () => BlogRouteRoute, +} as any) +const PostsPostIdIndexRoute = PostsPostIdIndexRouteImport.update({ + id: '/$postId/', + path: '/$postId/', + getParentRoute: () => PostsRouteRoute, +} as any) +const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ + id: '/$postId/deep', + path: '/$postId/deep', + getParentRoute: () => PostsRouteRoute, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute + '/blog': typeof BlogRouteRouteWithChildren + '/posts': typeof PostsRouteRouteWithChildren + '/blog/$slug': typeof BlogSlugRoute + '/blog/stats': typeof BlogStatsRoute + '/blog/': typeof BlogIndexRoute + '/posts/': typeof PostsIndexRoute + '/posts/$postId/deep': typeof PostsPostIdDeepRoute + '/posts/$postId': typeof PostsPostIdIndexRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute + '/blog/$slug': typeof BlogSlugRoute + '/blog/stats': typeof BlogStatsRoute + '/blog': typeof BlogIndexRoute + '/posts': typeof PostsIndexRoute + '/posts/$postId/deep': typeof PostsPostIdDeepRoute + '/posts/$postId': typeof PostsPostIdIndexRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute + '/blog': typeof BlogRouteRouteWithChildren + '/posts': typeof PostsRouteRouteWithChildren + '/blog/$slug': typeof BlogSlugRoute + '/blog/stats': typeof BlogStatsRoute + '/blog/': typeof BlogIndexRoute + '/posts/': typeof PostsIndexRoute + '/posts/$postId/deep': typeof PostsPostIdDeepRoute + '/posts/$postId/': typeof PostsPostIdIndexRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: + | '/' + | '/blog' + | '/posts' + | '/blog/$slug' + | '/blog/stats' + | '/blog/' + | '/posts/' + | '/posts/$postId/deep' + | '/posts/$postId' + fileRoutesByTo: FileRoutesByTo + to: + | '/' + | '/blog/$slug' + | '/blog/stats' + | '/blog' + | '/posts' + | '/posts/$postId/deep' + | '/posts/$postId' + id: + | '__root__' + | '/' + | '/blog' + | '/posts' + | '/blog/$slug' + | '/blog/stats' + | '/blog/' + | '/posts/' + | '/posts/$postId/deep' + | '/posts/$postId/' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute + BlogRouteRoute: typeof BlogRouteRouteWithChildren + PostsRouteRoute: typeof PostsRouteRouteWithChildren + BlogStatsRoute: typeof BlogStatsRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteRouteImport + parentRoute: typeof rootRouteImport + } + '/blog': { + id: '/blog' + path: '/blog' + fullPath: '/blog' + preLoaderRoute: typeof BlogRouteRouteImport + parentRoute: typeof rootRouteImport + } + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + '/posts/': { + id: '/posts/' + path: '/' + fullPath: '/posts/' + preLoaderRoute: typeof PostsIndexRouteImport + parentRoute: typeof PostsRouteRoute + } + '/blog/': { + id: '/blog/' + path: '/' + fullPath: '/blog/' + preLoaderRoute: typeof BlogIndexRouteImport + parentRoute: typeof BlogRouteRoute + } + '/blog/stats': { + id: '/blog/stats' + path: '/blog/stats' + fullPath: '/blog/stats' + preLoaderRoute: typeof BlogStatsRouteImport + parentRoute: typeof rootRouteImport + } + '/blog/$slug': { + id: '/blog/$slug' + path: '/$slug' + fullPath: '/blog/$slug' + preLoaderRoute: typeof BlogSlugRouteImport + parentRoute: typeof BlogRouteRoute + } + '/posts/$postId/': { + id: '/posts/$postId/' + path: '/$postId' + fullPath: '/posts/$postId' + preLoaderRoute: typeof PostsPostIdIndexRouteImport + parentRoute: typeof PostsRouteRoute + } + '/posts/$postId/deep': { + id: '/posts/$postId/deep' + path: '/$postId/deep' + fullPath: '/posts/$postId/deep' + preLoaderRoute: typeof PostsPostIdDeepRouteImport + parentRoute: typeof PostsRouteRoute + } + } +} + +interface BlogRouteRouteChildren { + BlogSlugRoute: typeof BlogSlugRoute + BlogIndexRoute: typeof BlogIndexRoute +} + +const BlogRouteRouteChildren: BlogRouteRouteChildren = { + BlogSlugRoute: BlogSlugRoute, + BlogIndexRoute: BlogIndexRoute, +} + +const BlogRouteRouteWithChildren = BlogRouteRoute._addFileChildren( + BlogRouteRouteChildren, +) + +interface PostsRouteRouteChildren { + PostsIndexRoute: typeof PostsIndexRoute + PostsPostIdDeepRoute: typeof PostsPostIdDeepRoute + PostsPostIdIndexRoute: typeof PostsPostIdIndexRoute +} + +const PostsRouteRouteChildren: PostsRouteRouteChildren = { + PostsIndexRoute: PostsIndexRoute, + PostsPostIdDeepRoute: PostsPostIdDeepRoute, + PostsPostIdIndexRoute: PostsPostIdIndexRoute, +} + +const PostsRouteRouteWithChildren = PostsRouteRoute._addFileChildren( + PostsRouteRouteChildren, +) + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, + BlogRouteRoute: BlogRouteRouteWithChildren, + PostsRouteRoute: PostsRouteRouteWithChildren, + BlogStatsRoute: BlogStatsRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/packages/router-generator/tests/generator/nested-verboseFileRoutes-true/tests.nonnested.test-d.ts b/packages/router-generator/tests/generator/nested-verboseFileRoutes-true/tests.nonnested.test-d.ts new file mode 100644 index 00000000000..8a6b9948f97 --- /dev/null +++ b/packages/router-generator/tests/generator/nested-verboseFileRoutes-true/tests.nonnested.test-d.ts @@ -0,0 +1,539 @@ +import { + Link, + createRouter, + redirect, + useLoaderData, + useLoaderDeps, + useMatch, + useNavigate, + useParams, + useRouteContext, + useSearch, +} from '@tanstack/react-router' +import { expectTypeOf, test } from 'vitest' +import { routeTree } from './routeTree.nonnested.gen' +import type { MakeRouteMatch } from '@tanstack/react-router' + +const defaultRouter = createRouter({ + routeTree, +}) + +type DefaultRouter = typeof defaultRouter + +const alwaysTrailingSlashRouter = createRouter({ + routeTree, + trailingSlash: 'always', +}) + +const neverTrailingSlashRouter = createRouter({ + routeTree, + trailingSlash: 'never', +}) + +const preserveTrailingSlashRouter = createRouter({ + routeTree, + trailingSlash: 'preserve', +}) + +test('when navigating to the root', () => { + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('to') + .toEqualTypeOf< + | '/' + | '/blog' + | '/posts' + | '/blog/$slug' + | '/blog/stats' + | '/posts/$postId/deep' + | '/posts/$postId' + | undefined + >() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('to') + .toEqualTypeOf< + | '/' + | '/blog/' + | '/posts/' + | '/blog/$slug/' + | '/blog/stats/' + | '/posts/$postId/deep/' + | '/posts/$postId/' + | undefined + >() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('to') + .toEqualTypeOf< + | '/' + | '/blog' + | '/posts' + | '/blog/$slug' + | '/blog/stats' + | '/posts/$postId/deep' + | '/posts/$postId' + | undefined + >() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('to') + .toEqualTypeOf< + | '/' + | '/blog' + | '/posts' + | '/blog/$slug' + | '/blog/stats' + | '/posts/$postId/deep' + | '/posts/$postId' + | '/blog/' + | '/posts/' + | '/blog/$slug/' + | '/blog/stats/' + | '/posts/$postId/deep/' + | '/posts/$postId/' + | undefined + >() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('from') + .toEqualTypeOf< + | '/' + | '/blog' + | '/posts' + | '/blog/$slug' + | '/blog/stats' + | '/blog/' + | '/posts/' + | '/posts/$postId/deep' + | '/posts/$postId' + | undefined + >() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('search') + .exclude<(...args: any) => any>() + .toEqualTypeOf() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('search') + .exclude<(...args: any) => any>() + .toEqualTypeOf() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('search') + .exclude<(...args: any) => any>() + .toEqualTypeOf() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('search') + .exclude<(...args: any) => any>() + .toEqualTypeOf() +}) + +test('when navigating a index route with search and params', () => { + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('to') + .toEqualTypeOf< + | '.' + | '..' + | '/' + | '/blog' + | '/posts' + | '/blog/$slug' + | '/blog/stats' + | '/posts/$postId/deep' + | '/posts/$postId' + >() + + expectTypeOf( + Link, + ) + .parameter(0) + .toHaveProperty('to') + .toEqualTypeOf< + | './' + | '../' + | '/' + | '/blog/' + | '/posts/' + | '/blog/$slug/' + | '/blog/stats/' + | '/posts/$postId/deep/' + | '/posts/$postId/' + >() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('to') + .toEqualTypeOf< + | '/' + | '/posts/$postId' + | '/blog' + | '/posts' + | '/blog/$slug' + | '/blog/stats' + | '/posts/$postId/deep' + | '/posts/$postId' + | '.' + | '..' + >() + + expectTypeOf( + Link, + ) + .parameter(0) + .toHaveProperty('to') + .toEqualTypeOf< + | '..' + | '../' + | '.' + | './' + | '/' + | '/blog' + | '/posts' + | '/blog/$slug' + | '/blog/stats' + | '/posts/$postId/deep' + | '/posts/$postId' + | '/blog/' + | '/posts/' + | '/blog/$slug/' + | '/blog/stats/' + | '/posts/$postId/deep/' + | '/posts/$postId/' + >() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('from') + .toEqualTypeOf< + | '/' + | '/posts/$postId' + | '/blog' + | '/posts' + | '/blog/$slug' + | '/blog/stats' + | '/blog/' + | '/posts/' + | '/posts/$postId/deep' + | undefined + >() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('search') + .exclude<(...args: any) => any>() + .toEqualTypeOf<{ indexSearch: string }>() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('search') + .exclude<(...args: any) => any>() + .toEqualTypeOf() + + expectTypeOf( + Link, + ) + .parameter(0) + .toHaveProperty('search') + .exclude<(...args: any) => any>() + .toEqualTypeOf<{ indexSearch: string }>() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('search') + .exclude<(...args: any) => any>() + .toEqualTypeOf() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('search') + .exclude<(...args: any) => any>() + .toEqualTypeOf<{ indexSearch: string }>() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('search') + .exclude<(...args: any) => any>() + .toEqualTypeOf<{ indexSearch: string }>() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('search') + .exclude<(...args: any) => any>() + .toEqualTypeOf<{ indexSearch: string }>() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('params') + .exclude<(...args: any) => any>() + .toEqualTypeOf<{ postId: string }>() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('params') + .exclude<(...args: any) => any>() + .toEqualTypeOf() + + expectTypeOf( + Link, + ) + .parameter(0) + .toHaveProperty('params') + .exclude<(...args: any) => any>() + .toEqualTypeOf<{ postId: string }>() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('params') + .exclude<(...args: any) => any>() + .toEqualTypeOf() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('params') + .exclude<(...args: any) => any>() + .toEqualTypeOf<{ postId: string }>() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('params') + .exclude<(...args: any) => any>() + .toEqualTypeOf<{ postId: string }>() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('params') + .exclude<(...args: any) => any>() + .toEqualTypeOf<{ postId: string }>() +}) + +test('when navigating from a index route with search and params', () => { + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('from') + .toEqualTypeOf< + | '/' + | '/posts/$postId' + | '/blog' + | '/posts' + | '/blog/$slug' + | '/blog/stats' + | '/blog/' + | '/posts/' + | '/posts/$postId/deep' + | undefined + >() + + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('search') + .parameter(0) + .toEqualTypeOf<{ indexSearch: string }>() +}) + +test('when using useNavigate', () => { + const navigate = useNavigate() + + expectTypeOf(navigate) + .parameter(0) + .toHaveProperty('to') + .toEqualTypeOf< + | '/' + | '.' + | '..' + | '/blog' + | '/posts' + | '/blog/$slug' + | '/blog/stats' + | '/posts/$postId/deep' + | '/posts/$postId' + >() +}) + +test('when using redirect', () => { + expectTypeOf(redirect) + .parameter(0) + .toHaveProperty('to') + .toEqualTypeOf< + | '/' + | '/posts/$postId' + | '/blog' + | '/posts' + | '/blog/$slug' + | '/blog/stats' + | '/posts/$postId/deep' + | undefined + >() +}) + +test('when using useSearch from a route with no search', () => { + expectTypeOf(useSearch) + .parameter(0) + .toHaveProperty('from') + .toEqualTypeOf< + | '__root__' + | '/' + | '/blog' + | '/blog/' + | '/posts' + | '/blog/$slug' + | '/blog/stats' + | '/blog/' + | '/posts/' + | '/posts/$postId/deep' + | '/posts/$postId/' + >() + + expectTypeOf(useSearch).returns.toEqualTypeOf<{}>() +}) + +test('when using useSearch from a route with search', () => { + expectTypeOf(useSearch) + .parameter(0) + .toHaveProperty('from') + .toEqualTypeOf< + | '__root__' + | '/' + | '/blog' + | '/blog/' + | '/posts' + | '/blog/$slug' + | '/blog/stats' + | '/blog/' + | '/posts/' + | '/posts/$postId/deep' + | '/posts/$postId/' + >() + + expectTypeOf( + useSearch, + ).returns.toEqualTypeOf<{ indexSearch: string }>() +}) + +test('when using useLoaderData from a route with loaderData', () => { + expectTypeOf(useLoaderData) + .parameter(0) + .toHaveProperty('from') + .toEqualTypeOf< + | '__root__' + | '/' + | '/blog' + | '/blog/' + | '/posts' + | '/blog/$slug' + | '/blog/stats' + | '/blog/' + | '/posts/' + | '/posts/$postId/deep' + | '/posts/$postId/' + >() + + expectTypeOf( + useLoaderData, + ).returns.toEqualTypeOf<{ data: string }>() +}) + +test('when using useLoaderDeps from a route with loaderDeps', () => { + expectTypeOf(useLoaderDeps) + .parameter(0) + .toHaveProperty('from') + .toEqualTypeOf< + | '__root__' + | '/' + | '/blog' + | '/blog/' + | '/posts' + | '/blog/$slug' + | '/blog/stats' + | '/blog/' + | '/posts/' + | '/posts/$postId/deep' + | '/posts/$postId/' + >() + + expectTypeOf( + useLoaderDeps, + ).returns.toEqualTypeOf<{ dep: number }>() +}) + +test('when using useMatch from a route', () => { + expectTypeOf(useMatch) + .parameter(0) + .toHaveProperty('from') + .toEqualTypeOf< + | '__root__' + | '/' + | '/blog' + | '/blog/' + | '/posts' + | '/blog/$slug' + | '/blog/stats' + | '/blog/' + | '/posts/' + | '/posts/$postId/deep' + | '/posts/$postId/' + >() + + expectTypeOf( + useMatch, + ).returns.toEqualTypeOf< + MakeRouteMatch + >() +}) + +test('when using useParams from a route', () => { + expectTypeOf(useParams) + .parameter(0) + .toHaveProperty('from') + .toEqualTypeOf< + | '__root__' + | '/' + | '/blog' + | '/blog/' + | '/posts' + | '/blog/$slug' + | '/blog/stats' + | '/blog/' + | '/posts/' + | '/posts/$postId/deep' + | '/posts/$postId/' + >() + + expectTypeOf( + useParams, + ).returns.toEqualTypeOf<{ postId: string }>() +}) + +test('when using useRouteContext from a route', () => { + expectTypeOf(useRouteContext) + .parameter(0) + .toHaveProperty('from') + .toEqualTypeOf< + | '__root__' + | '/' + | '/blog' + | '/blog/' + | '/posts' + | '/blog/$slug' + | '/blog/stats' + | '/blog/' + | '/posts/' + | '/posts/$postId/deep' + | '/posts/$postId/' + >() + + expectTypeOf( + useRouteContext, + ).returns.toEqualTypeOf<{ someContext: string }>() +}) diff --git a/packages/router-generator/tests/generator/no-duplicate-route-segment/routeTree.nonnested.snapshot.ts b/packages/router-generator/tests/generator/no-duplicate-route-segment/routeTree.nonnested.snapshot.ts new file mode 100644 index 00000000000..7eeca506226 --- /dev/null +++ b/packages/router-generator/tests/generator/no-duplicate-route-segment/routeTree.nonnested.snapshot.ts @@ -0,0 +1,112 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { createFileRoute } from '@tanstack/react-router' + +import { Route as rootRouteImport } from './routes/__root' +import { Route as FooLayoutRouteRouteImport } from './routes/foo/_layout/route' +import { Route as FooLayoutIndexRouteImport } from './routes/foo/_layout/index' + +const FooRouteImport = createFileRoute('/foo')() + +const FooRoute = FooRouteImport.update({ + id: '/foo', + path: '/foo', + getParentRoute: () => rootRouteImport, +} as any) +const FooLayoutRouteRoute = FooLayoutRouteRouteImport.update({ + id: '/_layout', + getParentRoute: () => FooRoute, +} as any) +const FooLayoutIndexRoute = FooLayoutIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => FooLayoutRouteRoute, +} as any) + +export interface FileRoutesByFullPath { + '/foo': typeof FooLayoutRouteRouteWithChildren + '/foo/': typeof FooLayoutIndexRoute +} +export interface FileRoutesByTo { + '/foo': typeof FooLayoutIndexRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/foo': typeof FooRouteWithChildren + '/foo/_layout': typeof FooLayoutRouteRouteWithChildren + '/foo/_layout/': typeof FooLayoutIndexRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/foo' | '/foo/' + fileRoutesByTo: FileRoutesByTo + to: '/foo' + id: '__root__' | '/foo' | '/foo/_layout' | '/foo/_layout/' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + FooRoute: typeof FooRouteWithChildren +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/foo': { + id: '/foo' + path: '/foo' + fullPath: '/foo' + preLoaderRoute: typeof FooRouteImport + parentRoute: typeof rootRouteImport + } + '/foo/_layout': { + id: '/foo/_layout' + path: '/foo' + fullPath: '/foo' + preLoaderRoute: typeof FooLayoutRouteRouteImport + parentRoute: typeof FooRoute + } + '/foo/_layout/': { + id: '/foo/_layout/' + path: '/' + fullPath: '/foo/' + preLoaderRoute: typeof FooLayoutIndexRouteImport + parentRoute: typeof FooLayoutRouteRoute + } + } +} + +interface FooLayoutRouteRouteChildren { + FooLayoutIndexRoute: typeof FooLayoutIndexRoute +} + +const FooLayoutRouteRouteChildren: FooLayoutRouteRouteChildren = { + FooLayoutIndexRoute: FooLayoutIndexRoute, +} + +const FooLayoutRouteRouteWithChildren = FooLayoutRouteRoute._addFileChildren( + FooLayoutRouteRouteChildren, +) + +interface FooRouteChildren { + FooLayoutRouteRoute: typeof FooLayoutRouteRouteWithChildren +} + +const FooRouteChildren: FooRouteChildren = { + FooLayoutRouteRoute: FooLayoutRouteRouteWithChildren, +} + +const FooRouteWithChildren = FooRoute._addFileChildren(FooRouteChildren) + +const rootRouteChildren: RootRouteChildren = { + FooRoute: FooRouteWithChildren, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts b/packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts new file mode 100644 index 00000000000..e4f872ce3a9 --- /dev/null +++ b/packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts @@ -0,0 +1,77 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/__root' +import { Route as IndexRouteImport } from './routes/index' +import { Route as NestedIndexRouteImport } from './routes/nested/index' +import { Route as NestedChildRouteImport } from './routes/nested/child' + +const IndexRoute = IndexRouteImport.update({ + id: '/',path: '/',getParentRoute: () => rootRouteImport + }as any) +const NestedIndexRoute = NestedIndexRouteImport.update({ + id: '/nested/',path: '/nested/',getParentRoute: () => rootRouteImport + }as any) +const NestedChildRoute = NestedChildRouteImport.update({ + id: '/nested/child',path: '/nested/child',getParentRoute: () => rootRouteImport + }as any) + +export interface FileRoutesByFullPath { +'/': typeof IndexRoute,'/nested/child': typeof NestedChildRoute,'/nested': typeof NestedIndexRoute +} +export interface FileRoutesByTo { +'/': typeof IndexRoute,'/nested/child': typeof NestedChildRoute,'/nested': typeof NestedIndexRoute +} +export interface FileRoutesById { +'__root__': typeof rootRouteImport, +'/': typeof IndexRoute,'/nested/child': typeof NestedChildRoute,'/nested/': typeof NestedIndexRoute +} +export interface FileRouteTypes { +fileRoutesByFullPath: FileRoutesByFullPath +fullPaths: '/'|'/nested/child'|'/nested' +fileRoutesByTo: FileRoutesByTo +to: '/'|'/nested/child'|'/nested' +id: '__root__'|'/'|'/nested/child'|'/nested/' +fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { +IndexRoute: typeof IndexRoute,NestedChildRoute: typeof NestedChildRoute,NestedIndexRoute: typeof NestedIndexRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } +'/nested/': { + id: '/nested/' + path: '/nested' + fullPath: '/nested' + preLoaderRoute: typeof NestedIndexRouteImport + parentRoute: typeof rootRouteImport + } +'/nested/child': { + id: '/nested/child' + path: '/nested/child' + fullPath: '/nested/child' + preLoaderRoute: typeof NestedChildRouteImport + parentRoute: typeof rootRouteImport + } + } +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute,NestedChildRoute: NestedChildRoute,NestedIndexRoute: NestedIndexRoute +} +export const routeTree = rootRouteImport._addFileChildren(rootRouteChildren)._addFileTypes() \ No newline at end of file diff --git a/packages/router-generator/tests/generator/numbers-in-path/routeTree.nonnested.snapshot.ts b/packages/router-generator/tests/generator/numbers-in-path/routeTree.nonnested.snapshot.ts new file mode 100644 index 00000000000..754b730b7ae --- /dev/null +++ b/packages/router-generator/tests/generator/numbers-in-path/routeTree.nonnested.snapshot.ts @@ -0,0 +1,131 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/__root' +import { Route as AboutRouteImport } from './routes/about' +import { Route as R03RouteImport } from './routes/03' +import { Route as IndexRouteImport } from './routes/index' +import { Route as R02IndexRouteImport } from './routes/02.index' +import { Route as R01ExampleIndexRouteImport } from './routes/01-example/index' + +const AboutRoute = AboutRouteImport.update({ + id: '/about', + path: '/about', + getParentRoute: () => rootRouteImport, +} as any) +const R03Route = R03RouteImport.update({ + id: '/03', + path: '/03', + getParentRoute: () => rootRouteImport, +} as any) +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) +const R02IndexRoute = R02IndexRouteImport.update({ + id: '/02/', + path: '/02/', + getParentRoute: () => rootRouteImport, +} as any) +const R01ExampleIndexRoute = R01ExampleIndexRouteImport.update({ + id: '/01-example/', + path: '/01-example/', + getParentRoute: () => rootRouteImport, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute + '/03': typeof R03Route + '/about': typeof AboutRoute + '/01-example': typeof R01ExampleIndexRoute + '/02': typeof R02IndexRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute + '/03': typeof R03Route + '/about': typeof AboutRoute + '/01-example': typeof R01ExampleIndexRoute + '/02': typeof R02IndexRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute + '/03': typeof R03Route + '/about': typeof AboutRoute + '/01-example/': typeof R01ExampleIndexRoute + '/02/': typeof R02IndexRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' | '/03' | '/about' | '/01-example' | '/02' + fileRoutesByTo: FileRoutesByTo + to: '/' | '/03' | '/about' | '/01-example' | '/02' + id: '__root__' | '/' | '/03' | '/about' | '/01-example/' | '/02/' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute + R03Route: typeof R03Route + AboutRoute: typeof AboutRoute + R01ExampleIndexRoute: typeof R01ExampleIndexRoute + R02IndexRoute: typeof R02IndexRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/about': { + id: '/about' + path: '/about' + fullPath: '/about' + preLoaderRoute: typeof AboutRouteImport + parentRoute: typeof rootRouteImport + } + '/03': { + id: '/03' + path: '/03' + fullPath: '/03' + preLoaderRoute: typeof R03RouteImport + parentRoute: typeof rootRouteImport + } + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + '/02/': { + id: '/02/' + path: '/02' + fullPath: '/02' + preLoaderRoute: typeof R02IndexRouteImport + parentRoute: typeof rootRouteImport + } + '/01-example/': { + id: '/01-example/' + path: '/01-example' + fullPath: '/01-example' + preLoaderRoute: typeof R01ExampleIndexRouteImport + parentRoute: typeof rootRouteImport + } + } +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, + R03Route: R03Route, + AboutRoute: AboutRoute, + R01ExampleIndexRoute: R01ExampleIndexRoute, + R02IndexRoute: R02IndexRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/packages/router-generator/tests/generator/only-root/routeTree.nonnested.generated.snapshot.ts b/packages/router-generator/tests/generator/only-root/routeTree.nonnested.generated.snapshot.ts new file mode 100644 index 00000000000..7766df98323 --- /dev/null +++ b/packages/router-generator/tests/generator/only-root/routeTree.nonnested.generated.snapshot.ts @@ -0,0 +1,35 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './../routes/__root' + +export interface FileRoutesByFullPath {} +export interface FileRoutesByTo {} +export interface FileRoutesById { + __root__: typeof rootRouteImport +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: never + fileRoutesByTo: FileRoutesByTo + to: never + id: '__root__' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren {} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath {} +} + +const rootRouteChildren: RootRouteChildren = {} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/packages/router-generator/tests/generator/only-root/routeTree.nonnested.snapshot.ts b/packages/router-generator/tests/generator/only-root/routeTree.nonnested.snapshot.ts new file mode 100644 index 00000000000..d9b0e2be238 --- /dev/null +++ b/packages/router-generator/tests/generator/only-root/routeTree.nonnested.snapshot.ts @@ -0,0 +1,35 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/__root' + +export interface FileRoutesByFullPath {} +export interface FileRoutesByTo {} +export interface FileRoutesById { + __root__: typeof rootRouteImport +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: never + fileRoutesByTo: FileRoutesByTo + to: never + id: '__root__' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren {} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath {} +} + +const rootRouteChildren: RootRouteChildren = {} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/packages/router-generator/tests/generator/path-above-route-in-group/routeTree.nonnested.snapshot.ts b/packages/router-generator/tests/generator/path-above-route-in-group/routeTree.nonnested.snapshot.ts new file mode 100644 index 00000000000..1c216c5bcd4 --- /dev/null +++ b/packages/router-generator/tests/generator/path-above-route-in-group/routeTree.nonnested.snapshot.ts @@ -0,0 +1,87 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/__root' +import { Route as ABcRouteRouteImport } from './routes/a/$b/(c)/route' +import { Route as ABcDEIndexRouteImport } from './routes/a/$b/(c)/d/e/index' + +const ABcRouteRoute = ABcRouteRouteImport.update({ + id: '/a/$b/(c)', + path: '/a/$b/', + getParentRoute: () => rootRouteImport, +} as any) +const ABcDEIndexRoute = ABcDEIndexRouteImport.update({ + id: '/d/e/', + path: '/d/e/', + getParentRoute: () => ABcRouteRoute, +} as any) + +export interface FileRoutesByFullPath { + '/a/$b': typeof ABcRouteRouteWithChildren + '/a/$b/d/e': typeof ABcDEIndexRoute +} +export interface FileRoutesByTo { + '/a/$b': typeof ABcRouteRouteWithChildren + '/a/$b/d/e': typeof ABcDEIndexRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/a/$b/(c)': typeof ABcRouteRouteWithChildren + '/a/$b/(c)/d/e/': typeof ABcDEIndexRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/a/$b' | '/a/$b/d/e' + fileRoutesByTo: FileRoutesByTo + to: '/a/$b' | '/a/$b/d/e' + id: '__root__' | '/a/$b/(c)' | '/a/$b/(c)/d/e/' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + ABcRouteRoute: typeof ABcRouteRouteWithChildren +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/a/$b/(c)': { + id: '/a/$b/(c)' + path: '/a/$b' + fullPath: '/a/$b' + preLoaderRoute: typeof ABcRouteRouteImport + parentRoute: typeof rootRouteImport + } + '/a/$b/(c)/d/e/': { + id: '/a/$b/(c)/d/e/' + path: '/d/e' + fullPath: '/a/$b/d/e' + preLoaderRoute: typeof ABcDEIndexRouteImport + parentRoute: typeof ABcRouteRoute + } + } +} + +interface ABcRouteRouteChildren { + ABcDEIndexRoute: typeof ABcDEIndexRoute +} + +const ABcRouteRouteChildren: ABcRouteRouteChildren = { + ABcDEIndexRoute: ABcDEIndexRoute, +} + +const ABcRouteRouteWithChildren = ABcRouteRoute._addFileChildren( + ABcRouteRouteChildren, +) + +const rootRouteChildren: RootRouteChildren = { + ABcRouteRoute: ABcRouteRouteWithChildren, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/packages/router-generator/tests/generator/prefix-suffix/routeTree.nonnested.snapshot.ts b/packages/router-generator/tests/generator/prefix-suffix/routeTree.nonnested.snapshot.ts new file mode 100644 index 00000000000..5f7f854d9f1 --- /dev/null +++ b/packages/router-generator/tests/generator/prefix-suffix/routeTree.nonnested.snapshot.ts @@ -0,0 +1,129 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/__root' +import { Route as WildcardChar123Char125suffixRouteImport } from './routes/wildcard/{$}suffix' +import { Route as WildcardChar123Char125DotsuffixRouteImport } from './routes/wildcard/{$}[.]suffix' +import { Route as WildcardPrefixChar123Char125RouteImport } from './routes/wildcard/prefix{$}' +import { Route as WildcardSplatRouteImport } from './routes/wildcard/$' + +const WildcardChar123Char125suffixRoute = + WildcardChar123Char125suffixRouteImport.update({ + id: '/wildcard/{$}suffix', + path: '/wildcard/{$}suffix', + getParentRoute: () => rootRouteImport, + } as any) +const WildcardChar123Char125DotsuffixRoute = + WildcardChar123Char125DotsuffixRouteImport.update({ + id: '/wildcard/{$}.suffix', + path: '/wildcard/{$}.suffix', + getParentRoute: () => rootRouteImport, + } as any) +const WildcardPrefixChar123Char125Route = + WildcardPrefixChar123Char125RouteImport.update({ + id: '/wildcard/prefix{$}', + path: '/wildcard/prefix{$}', + getParentRoute: () => rootRouteImport, + } as any) +const WildcardSplatRoute = WildcardSplatRouteImport.update({ + id: '/wildcard/$', + path: '/wildcard/$', + getParentRoute: () => rootRouteImport, +} as any) + +export interface FileRoutesByFullPath { + '/wildcard/$': typeof WildcardSplatRoute + '/wildcard/prefix{$}': typeof WildcardPrefixChar123Char125Route + '/wildcard/{$}.suffix': typeof WildcardChar123Char125DotsuffixRoute + '/wildcard/{$}suffix': typeof WildcardChar123Char125suffixRoute +} +export interface FileRoutesByTo { + '/wildcard/$': typeof WildcardSplatRoute + '/wildcard/prefix{$}': typeof WildcardPrefixChar123Char125Route + '/wildcard/{$}.suffix': typeof WildcardChar123Char125DotsuffixRoute + '/wildcard/{$}suffix': typeof WildcardChar123Char125suffixRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/wildcard/$': typeof WildcardSplatRoute + '/wildcard/prefix{$}': typeof WildcardPrefixChar123Char125Route + '/wildcard/{$}.suffix': typeof WildcardChar123Char125DotsuffixRoute + '/wildcard/{$}suffix': typeof WildcardChar123Char125suffixRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: + | '/wildcard/$' + | '/wildcard/prefix{$}' + | '/wildcard/{$}.suffix' + | '/wildcard/{$}suffix' + fileRoutesByTo: FileRoutesByTo + to: + | '/wildcard/$' + | '/wildcard/prefix{$}' + | '/wildcard/{$}.suffix' + | '/wildcard/{$}suffix' + id: + | '__root__' + | '/wildcard/$' + | '/wildcard/prefix{$}' + | '/wildcard/{$}.suffix' + | '/wildcard/{$}suffix' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + WildcardSplatRoute: typeof WildcardSplatRoute + WildcardPrefixChar123Char125Route: typeof WildcardPrefixChar123Char125Route + WildcardChar123Char125DotsuffixRoute: typeof WildcardChar123Char125DotsuffixRoute + WildcardChar123Char125suffixRoute: typeof WildcardChar123Char125suffixRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/wildcard/{$}suffix': { + id: '/wildcard/{$}suffix' + path: '/wildcard/{$}suffix' + fullPath: '/wildcard/{$}suffix' + preLoaderRoute: typeof WildcardChar123Char125suffixRouteImport + parentRoute: typeof rootRouteImport + } + '/wildcard/{$}.suffix': { + id: '/wildcard/{$}.suffix' + path: '/wildcard/{$}.suffix' + fullPath: '/wildcard/{$}.suffix' + preLoaderRoute: typeof WildcardChar123Char125DotsuffixRouteImport + parentRoute: typeof rootRouteImport + } + '/wildcard/prefix{$}': { + id: '/wildcard/prefix{$}' + path: '/wildcard/prefix{$}' + fullPath: '/wildcard/prefix{$}' + preLoaderRoute: typeof WildcardPrefixChar123Char125RouteImport + parentRoute: typeof rootRouteImport + } + '/wildcard/$': { + id: '/wildcard/$' + path: '/wildcard/$' + fullPath: '/wildcard/$' + preLoaderRoute: typeof WildcardSplatRouteImport + parentRoute: typeof rootRouteImport + } + } +} + +const rootRouteChildren: RootRouteChildren = { + WildcardSplatRoute: WildcardSplatRoute, + WildcardPrefixChar123Char125Route: WildcardPrefixChar123Char125Route, + WildcardChar123Char125DotsuffixRoute: WildcardChar123Char125DotsuffixRoute, + WildcardChar123Char125suffixRoute: WildcardChar123Char125suffixRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/packages/router-generator/tests/generator/route-groups/routeTree.nonnested.snapshot.ts b/packages/router-generator/tests/generator/route-groups/routeTree.nonnested.snapshot.ts new file mode 100644 index 00000000000..bf4addbd35f --- /dev/null +++ b/packages/router-generator/tests/generator/route-groups/routeTree.nonnested.snapshot.ts @@ -0,0 +1,345 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { createFileRoute } from '@tanstack/react-router' + +import { Route as rootRouteImport } from './routes/__root' +import { Route as barBarRouteImport } from './routes/(bar)/_bar' +import { Route as fooAsdfLayoutRouteImport } from './routes/(foo)/asdf/_layout' +import { Route as barBarHelloRouteImport } from './routes/(bar)/_bar.hello' +import { Route as fooAsdfLayoutFooRouteImport } from './routes/(foo)/asdf/_layout.foo' +import { Route as fooAsdfbarIdRouteImport } from './routes/(foo)/asdf/(bar)/$id' +import { Route as fooAsdfanotherGroupLayoutRouteImport } from './routes/(foo)/asdf/(another-group)/_layout' +import { Route as fooAsdfbarLayoutAboutRouteImport } from './routes/(foo)/asdf/(bar)/_layout.about' +import { Route as fooAsdfanotherGroupLayoutBazRouteImport } from './routes/(foo)/asdf/(another-group)/_layout.baz' + +const barRouteImport = createFileRoute('/(bar)')() +const fooAsdfRouteImport = createFileRoute('/(foo)/asdf')() +const fooAsdfanotherGroupRouteImport = createFileRoute( + '/(foo)/asdf/(another-group)', +)() +const fooAsdfbarLayoutXyzLazyRouteImport = createFileRoute( + '/(foo)/asdf/(bar)/_layout/xyz', +)() + +const barRoute = barRouteImport.update({ + id: '/(bar)', + getParentRoute: () => rootRouteImport, +} as any) +const fooAsdfRoute = fooAsdfRouteImport.update({ + id: '/(foo)/asdf', + path: '/asdf', + getParentRoute: () => rootRouteImport, +} as any) +const barBarRoute = barBarRouteImport.update({ + id: '/_bar', + getParentRoute: () => barRoute, +} as any) +const fooAsdfanotherGroupRoute = fooAsdfanotherGroupRouteImport.update({ + id: '/(another-group)', + getParentRoute: () => fooAsdfRoute, +} as any) +const fooAsdfLayoutRoute = fooAsdfLayoutRouteImport.update({ + id: '/_layout', + getParentRoute: () => fooAsdfRoute, +} as any) +const barBarHelloRoute = barBarHelloRouteImport.update({ + id: '/hello', + path: '/hello', + getParentRoute: () => barBarRoute, +} as any) +const fooAsdfLayoutFooRoute = fooAsdfLayoutFooRouteImport.update({ + id: '/foo', + path: '/foo', + getParentRoute: () => fooAsdfLayoutRoute, +} as any) +const fooAsdfbarIdRoute = fooAsdfbarIdRouteImport.update({ + id: '/(bar)/$id', + path: '/$id', + getParentRoute: () => fooAsdfRoute, +} as any) +const fooAsdfanotherGroupLayoutRoute = + fooAsdfanotherGroupLayoutRouteImport.update({ + id: '/_layout', + getParentRoute: () => fooAsdfanotherGroupRoute, + } as any) +const fooAsdfbarLayoutXyzLazyRoute = fooAsdfbarLayoutXyzLazyRouteImport + .update({ + id: '/(bar)/_layout/xyz', + path: '/xyz', + getParentRoute: () => fooAsdfRoute, + } as any) + .lazy(() => + import('./routes/(foo)/asdf/(bar)/_layout.xyz.lazy').then((d) => d.Route), + ) +const fooAsdfbarLayoutAboutRoute = fooAsdfbarLayoutAboutRouteImport.update({ + id: '/(bar)/_layout/about', + path: '/about', + getParentRoute: () => fooAsdfRoute, +} as any) +const fooAsdfanotherGroupLayoutBazRoute = + fooAsdfanotherGroupLayoutBazRouteImport.update({ + id: '/baz', + path: '/baz', + getParentRoute: () => fooAsdfanotherGroupLayoutRoute, + } as any) + +export interface FileRoutesByFullPath { + '/': typeof barBarRouteWithChildren + '/hello': typeof barBarHelloRoute + '/asdf': typeof fooAsdfLayoutRouteWithChildren + '/asdf/': typeof fooAsdfanotherGroupLayoutRouteWithChildren + '/asdf/$id': typeof fooAsdfbarIdRoute + '/asdf/foo': typeof fooAsdfLayoutFooRoute + '/asdf/baz': typeof fooAsdfanotherGroupLayoutBazRoute + '/asdf/about': typeof fooAsdfbarLayoutAboutRoute + '/asdf/xyz': typeof fooAsdfbarLayoutXyzLazyRoute +} +export interface FileRoutesByTo { + '/': typeof barBarRouteWithChildren + '/hello': typeof barBarHelloRoute + '/asdf': typeof fooAsdfanotherGroupLayoutRouteWithChildren + '/asdf/$id': typeof fooAsdfbarIdRoute + '/asdf/foo': typeof fooAsdfLayoutFooRoute + '/asdf/baz': typeof fooAsdfanotherGroupLayoutBazRoute + '/asdf/about': typeof fooAsdfbarLayoutAboutRoute + '/asdf/xyz': typeof fooAsdfbarLayoutXyzLazyRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/(bar)': typeof barRouteWithChildren + '/(bar)/_bar': typeof barBarRouteWithChildren + '/(bar)/_bar/hello': typeof barBarHelloRoute + '/(foo)/asdf': typeof fooAsdfRouteWithChildren + '/(foo)/asdf/_layout': typeof fooAsdfLayoutRouteWithChildren + '/(foo)/asdf/(another-group)': typeof fooAsdfanotherGroupRouteWithChildren + '/(foo)/asdf/(another-group)/_layout': typeof fooAsdfanotherGroupLayoutRouteWithChildren + '/(foo)/asdf/(bar)/$id': typeof fooAsdfbarIdRoute + '/(foo)/asdf/_layout/foo': typeof fooAsdfLayoutFooRoute + '/(foo)/asdf/(another-group)/_layout/baz': typeof fooAsdfanotherGroupLayoutBazRoute + '/(foo)/asdf/(bar)/_layout/about': typeof fooAsdfbarLayoutAboutRoute + '/(foo)/asdf/(bar)/_layout/xyz': typeof fooAsdfbarLayoutXyzLazyRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: + | '/' + | '/hello' + | '/asdf' + | '/asdf/' + | '/asdf/$id' + | '/asdf/foo' + | '/asdf/baz' + | '/asdf/about' + | '/asdf/xyz' + fileRoutesByTo: FileRoutesByTo + to: + | '/' + | '/hello' + | '/asdf' + | '/asdf/$id' + | '/asdf/foo' + | '/asdf/baz' + | '/asdf/about' + | '/asdf/xyz' + id: + | '__root__' + | '/(bar)' + | '/(bar)/_bar' + | '/(bar)/_bar/hello' + | '/(foo)/asdf' + | '/(foo)/asdf/_layout' + | '/(foo)/asdf/(another-group)' + | '/(foo)/asdf/(another-group)/_layout' + | '/(foo)/asdf/(bar)/$id' + | '/(foo)/asdf/_layout/foo' + | '/(foo)/asdf/(another-group)/_layout/baz' + | '/(foo)/asdf/(bar)/_layout/about' + | '/(foo)/asdf/(bar)/_layout/xyz' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + barRoute: typeof barRouteWithChildren + fooAsdfRoute: typeof fooAsdfRouteWithChildren +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/(bar)': { + id: '/(bar)' + path: '/' + fullPath: '/' + preLoaderRoute: typeof barRouteImport + parentRoute: typeof rootRouteImport + } + '/(foo)/asdf': { + id: '/(foo)/asdf' + path: '/asdf' + fullPath: '/asdf' + preLoaderRoute: typeof fooAsdfRouteImport + parentRoute: typeof rootRouteImport + } + '/(bar)/_bar': { + id: '/(bar)/_bar' + path: '/' + fullPath: '/' + preLoaderRoute: typeof barBarRouteImport + parentRoute: typeof barRoute + } + '/(foo)/asdf/(another-group)': { + id: '/(foo)/asdf/(another-group)' + path: '/' + fullPath: '/asdf/' + preLoaderRoute: typeof fooAsdfanotherGroupRouteImport + parentRoute: typeof fooAsdfRoute + } + '/(foo)/asdf/_layout': { + id: '/(foo)/asdf/_layout' + path: '/asdf' + fullPath: '/asdf' + preLoaderRoute: typeof fooAsdfLayoutRouteImport + parentRoute: typeof fooAsdfRoute + } + '/(bar)/_bar/hello': { + id: '/(bar)/_bar/hello' + path: '/hello' + fullPath: '/hello' + preLoaderRoute: typeof barBarHelloRouteImport + parentRoute: typeof barBarRoute + } + '/(foo)/asdf/_layout/foo': { + id: '/(foo)/asdf/_layout/foo' + path: '/foo' + fullPath: '/asdf/foo' + preLoaderRoute: typeof fooAsdfLayoutFooRouteImport + parentRoute: typeof fooAsdfLayoutRoute + } + '/(foo)/asdf/(bar)/$id': { + id: '/(foo)/asdf/(bar)/$id' + path: '/$id' + fullPath: '/asdf/$id' + preLoaderRoute: typeof fooAsdfbarIdRouteImport + parentRoute: typeof fooAsdfRoute + } + '/(foo)/asdf/(another-group)/_layout': { + id: '/(foo)/asdf/(another-group)/_layout' + path: '/' + fullPath: '/asdf/' + preLoaderRoute: typeof fooAsdfanotherGroupLayoutRouteImport + parentRoute: typeof fooAsdfanotherGroupRoute + } + '/(foo)/asdf/(bar)/_layout/xyz': { + id: '/(foo)/asdf/(bar)/_layout/xyz' + path: '/xyz' + fullPath: '/asdf/xyz' + preLoaderRoute: typeof fooAsdfbarLayoutXyzLazyRouteImport + parentRoute: typeof fooAsdfRoute + } + '/(foo)/asdf/(bar)/_layout/about': { + id: '/(foo)/asdf/(bar)/_layout/about' + path: '/about' + fullPath: '/asdf/about' + preLoaderRoute: typeof fooAsdfbarLayoutAboutRouteImport + parentRoute: typeof fooAsdfRoute + } + '/(foo)/asdf/(another-group)/_layout/baz': { + id: '/(foo)/asdf/(another-group)/_layout/baz' + path: '/baz' + fullPath: '/asdf/baz' + preLoaderRoute: typeof fooAsdfanotherGroupLayoutBazRouteImport + parentRoute: typeof fooAsdfanotherGroupLayoutRoute + } + } +} + +interface barBarRouteChildren { + barBarHelloRoute: typeof barBarHelloRoute +} + +const barBarRouteChildren: barBarRouteChildren = { + barBarHelloRoute: barBarHelloRoute, +} + +const barBarRouteWithChildren = + barBarRoute._addFileChildren(barBarRouteChildren) + +interface barRouteChildren { + barBarRoute: typeof barBarRouteWithChildren +} + +const barRouteChildren: barRouteChildren = { + barBarRoute: barBarRouteWithChildren, +} + +const barRouteWithChildren = barRoute._addFileChildren(barRouteChildren) + +interface fooAsdfLayoutRouteChildren { + fooAsdfLayoutFooRoute: typeof fooAsdfLayoutFooRoute +} + +const fooAsdfLayoutRouteChildren: fooAsdfLayoutRouteChildren = { + fooAsdfLayoutFooRoute: fooAsdfLayoutFooRoute, +} + +const fooAsdfLayoutRouteWithChildren = fooAsdfLayoutRoute._addFileChildren( + fooAsdfLayoutRouteChildren, +) + +interface fooAsdfanotherGroupLayoutRouteChildren { + fooAsdfanotherGroupLayoutBazRoute: typeof fooAsdfanotherGroupLayoutBazRoute +} + +const fooAsdfanotherGroupLayoutRouteChildren: fooAsdfanotherGroupLayoutRouteChildren = + { + fooAsdfanotherGroupLayoutBazRoute: fooAsdfanotherGroupLayoutBazRoute, + } + +const fooAsdfanotherGroupLayoutRouteWithChildren = + fooAsdfanotherGroupLayoutRoute._addFileChildren( + fooAsdfanotherGroupLayoutRouteChildren, + ) + +interface fooAsdfanotherGroupRouteChildren { + fooAsdfanotherGroupLayoutRoute: typeof fooAsdfanotherGroupLayoutRouteWithChildren +} + +const fooAsdfanotherGroupRouteChildren: fooAsdfanotherGroupRouteChildren = { + fooAsdfanotherGroupLayoutRoute: fooAsdfanotherGroupLayoutRouteWithChildren, +} + +const fooAsdfanotherGroupRouteWithChildren = + fooAsdfanotherGroupRoute._addFileChildren(fooAsdfanotherGroupRouteChildren) + +interface fooAsdfRouteChildren { + fooAsdfLayoutRoute: typeof fooAsdfLayoutRouteWithChildren + fooAsdfanotherGroupRoute: typeof fooAsdfanotherGroupRouteWithChildren + fooAsdfbarIdRoute: typeof fooAsdfbarIdRoute + fooAsdfbarLayoutAboutRoute: typeof fooAsdfbarLayoutAboutRoute + fooAsdfbarLayoutXyzLazyRoute: typeof fooAsdfbarLayoutXyzLazyRoute +} + +const fooAsdfRouteChildren: fooAsdfRouteChildren = { + fooAsdfLayoutRoute: fooAsdfLayoutRouteWithChildren, + fooAsdfanotherGroupRoute: fooAsdfanotherGroupRouteWithChildren, + fooAsdfbarIdRoute: fooAsdfbarIdRoute, + fooAsdfbarLayoutAboutRoute: fooAsdfbarLayoutAboutRoute, + fooAsdfbarLayoutXyzLazyRoute: fooAsdfbarLayoutXyzLazyRoute, +} + +const fooAsdfRouteWithChildren = + fooAsdfRoute._addFileChildren(fooAsdfRouteChildren) + +const rootRouteChildren: RootRouteChildren = { + barRoute: barRouteWithChildren, + fooAsdfRoute: fooAsdfRouteWithChildren, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/packages/router-generator/tests/generator/routeFileIgnore/routeTree.nonnested.snapshot.ts b/packages/router-generator/tests/generator/routeFileIgnore/routeTree.nonnested.snapshot.ts new file mode 100644 index 00000000000..33a4093b9e8 --- /dev/null +++ b/packages/router-generator/tests/generator/routeFileIgnore/routeTree.nonnested.snapshot.ts @@ -0,0 +1,77 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/__root' +import { Route as BlogRouteRouteImport } from './routes/blog.route' +import { Route as IndexRouteImport } from './routes/index' + +const BlogRouteRoute = BlogRouteRouteImport.update({ + id: '/blog', + path: '/blog', + getParentRoute: () => rootRouteImport, +} as any) +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute + '/blog': typeof BlogRouteRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute + '/blog': typeof BlogRouteRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute + '/blog': typeof BlogRouteRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' | '/blog' + fileRoutesByTo: FileRoutesByTo + to: '/' | '/blog' + id: '__root__' | '/' | '/blog' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute + BlogRouteRoute: typeof BlogRouteRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/blog': { + id: '/blog' + path: '/blog' + fullPath: '/blog' + preLoaderRoute: typeof BlogRouteRouteImport + parentRoute: typeof rootRouteImport + } + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + } +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, + BlogRouteRoute: BlogRouteRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/packages/router-generator/tests/generator/routeFilePrefix/routeTree.nonnested.snapshot.ts b/packages/router-generator/tests/generator/routeFilePrefix/routeTree.nonnested.snapshot.ts new file mode 100644 index 00000000000..46ee215c03d --- /dev/null +++ b/packages/router-generator/tests/generator/routeFilePrefix/routeTree.nonnested.snapshot.ts @@ -0,0 +1,77 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/r&__root' +import { Route as BlogRouteRouteImport } from './routes/r&blog.route' +import { Route as IndexRouteImport } from './routes/r&index' + +const BlogRouteRoute = BlogRouteRouteImport.update({ + id: '/blog', + path: '/blog', + getParentRoute: () => rootRouteImport, +} as any) +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute + '/blog': typeof BlogRouteRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute + '/blog': typeof BlogRouteRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute + '/blog': typeof BlogRouteRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' | '/blog' + fileRoutesByTo: FileRoutesByTo + to: '/' | '/blog' + id: '__root__' | '/' | '/blog' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute + BlogRouteRoute: typeof BlogRouteRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/blog': { + id: '/blog' + path: '/blog' + fullPath: '/blog' + preLoaderRoute: typeof BlogRouteRouteImport + parentRoute: typeof rootRouteImport + } + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + } +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, + BlogRouteRoute: BlogRouteRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/packages/router-generator/tests/generator/single-level/routeTree.nonnested.snapshot.ts b/packages/router-generator/tests/generator/single-level/routeTree.nonnested.snapshot.ts new file mode 100644 index 00000000000..b60f0b934fc --- /dev/null +++ b/packages/router-generator/tests/generator/single-level/routeTree.nonnested.snapshot.ts @@ -0,0 +1,77 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/__root' +import { Route as PostsRouteImport } from './routes/posts' +import { Route as IndexRouteImport } from './routes/index' + +const PostsRoute = PostsRouteImport.update({ + id: '/posts', + path: '/posts', + getParentRoute: () => rootRouteImport, +} as any) +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute + '/posts': typeof PostsRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute + '/posts': typeof PostsRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute + '/posts': typeof PostsRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' | '/posts' + fileRoutesByTo: FileRoutesByTo + to: '/' | '/posts' + id: '__root__' | '/' | '/posts' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute + PostsRoute: typeof PostsRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteImport + parentRoute: typeof rootRouteImport + } + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + } +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, + PostsRoute: PostsRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/packages/router-generator/tests/generator/types-disabled/routeTree.nonnested.snapshot.js b/packages/router-generator/tests/generator/types-disabled/routeTree.nonnested.snapshot.js new file mode 100644 index 00000000000..9c410eea0f4 --- /dev/null +++ b/packages/router-generator/tests/generator/types-disabled/routeTree.nonnested.snapshot.js @@ -0,0 +1,49 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/__root' +import { Route as PostsRouteImport } from './routes/posts' +import { Route as IndexRouteImport } from './routes/index' +import { Route as UsersUserIdRouteImport } from './routes/users.$userId' +import { Route as PostsPostIdRouteImport } from './routes/posts/$postId' + +const PostsRoute = PostsRouteImport.update({ + id: '/posts', + path: '/posts', + getParentRoute: () => rootRouteImport, +}) +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +}) +const UsersUserIdRoute = UsersUserIdRouteImport.update({ + id: '/users/$userId', + path: '/users/$userId', + getParentRoute: () => rootRouteImport, +}) +const PostsPostIdRoute = PostsPostIdRouteImport.update({ + id: '/$postId', + path: '/$postId', + getParentRoute: () => PostsRoute, +}) + +const PostsRouteChildren = { + PostsPostIdRoute: PostsPostIdRoute, +} + +const PostsRouteWithChildren = PostsRoute._addFileChildren(PostsRouteChildren) + +const rootRouteChildren = { + IndexRoute: IndexRoute, + PostsRoute: PostsRouteWithChildren, + UsersUserIdRoute: UsersUserIdRoute, +} +export const routeTree = rootRouteImport._addFileChildren(rootRouteChildren) diff --git a/packages/router-generator/tests/generator/types-disabled/routes/__root.tsx b/packages/router-generator/tests/generator/types-disabled/routes/__root.tsx index f89644e82d4..d1f1a340c3c 100644 --- a/packages/router-generator/tests/generator/types-disabled/routes/__root.tsx +++ b/packages/router-generator/tests/generator/types-disabled/routes/__root.tsx @@ -1,3 +1,5 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck import * as React from 'react' import { Outlet, createRootRoute } from '@tanstack/react-router' diff --git a/packages/router-generator/tests/generator/types-disabled/routes/index.tsx b/packages/router-generator/tests/generator/types-disabled/routes/index.tsx index 9587aa2a4a9..76fd762dcd3 100644 --- a/packages/router-generator/tests/generator/types-disabled/routes/index.tsx +++ b/packages/router-generator/tests/generator/types-disabled/routes/index.tsx @@ -1,3 +1,5 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck import { createFileRoute } from '@tanstack/react-router' import * as React from 'react' diff --git a/packages/router-generator/tests/generator/types-disabled/routes/posts.tsx b/packages/router-generator/tests/generator/types-disabled/routes/posts.tsx index 6adcc5a3420..cf1a3f1e919 100644 --- a/packages/router-generator/tests/generator/types-disabled/routes/posts.tsx +++ b/packages/router-generator/tests/generator/types-disabled/routes/posts.tsx @@ -1,3 +1,5 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck import { createFileRoute } from '@tanstack/react-router' import * as React from 'react' diff --git a/packages/router-generator/tests/generator/types-disabled/routes/posts/$postId.tsx b/packages/router-generator/tests/generator/types-disabled/routes/posts/$postId.tsx index 2f6078c7881..94dbb1e65ad 100644 --- a/packages/router-generator/tests/generator/types-disabled/routes/posts/$postId.tsx +++ b/packages/router-generator/tests/generator/types-disabled/routes/posts/$postId.tsx @@ -1,3 +1,5 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck import { createFileRoute } from '@tanstack/react-router' import * as React from 'react' diff --git a/packages/router-generator/tests/generator/types-disabled/routes/users.$userId.tsx b/packages/router-generator/tests/generator/types-disabled/routes/users.$userId.tsx index 9e61fabd90a..83c4b6578c4 100644 --- a/packages/router-generator/tests/generator/types-disabled/routes/users.$userId.tsx +++ b/packages/router-generator/tests/generator/types-disabled/routes/users.$userId.tsx @@ -1,3 +1,5 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck import { createFileRoute } from '@tanstack/react-router' import * as React from 'react' diff --git a/packages/router-generator/tests/generator/virtual-config-file-default-export/routeTree.nonnested.snapshot.ts b/packages/router-generator/tests/generator/virtual-config-file-default-export/routeTree.nonnested.snapshot.ts new file mode 100644 index 00000000000..ab31f63e997 --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-config-file-default-export/routeTree.nonnested.snapshot.ts @@ -0,0 +1,292 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/root' +import { Route as layoutRouteImport } from './routes/layout' +import { Route as indexRouteImport } from './routes/index' +import { Route as dbDashboardRouteImport } from './routes/db/dashboard' +import { Route as pagesRouteImport } from './routes/pages' +import { Route as HelloIndexRouteImport } from './routes/subtree/index' +import { Route as dbDashboardInvoicesRouteImport } from './routes/db/dashboard-invoices' +import { Route as dbDashboardIndexRouteImport } from './routes/db/dashboard-index' +import { Route as HelloFooIndexRouteImport } from './routes/subtree/foo/index' +import { Route as HelloFooIdRouteImport } from './routes/subtree/foo/$id' +import { Route as dbInvoiceDetailRouteImport } from './routes/db/invoice-detail' +import { Route as dbInvoicesIndexRouteImport } from './routes/db/invoices-index' + +const layoutRoute = layoutRouteImport.update({ + id: '/_layout', + getParentRoute: () => rootRouteImport, +} as any) +const indexRoute = indexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) +const dbDashboardRoute = dbDashboardRouteImport.update({ + id: '/dashboard', + path: '/dashboard', + getParentRoute: () => layoutRoute, +} as any) +const pagesRoute = pagesRouteImport.update({ + id: '/$lang/', + path: '/$lang/', + getParentRoute: () => rootRouteImport, +} as any) +const HelloIndexRoute = HelloIndexRouteImport.update({ + id: '/hello/', + path: '/hello/', + getParentRoute: () => layoutRoute, +} as any) +const dbDashboardInvoicesRoute = dbDashboardInvoicesRouteImport.update({ + id: '/invoices', + path: '/invoices', + getParentRoute: () => dbDashboardRoute, +} as any) +const dbDashboardIndexRoute = dbDashboardIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => dbDashboardRoute, +} as any) +const HelloFooIndexRoute = HelloFooIndexRouteImport.update({ + id: '/hello/foo/', + path: '/hello/foo/', + getParentRoute: () => layoutRoute, +} as any) +const HelloFooIdRoute = HelloFooIdRouteImport.update({ + id: '/hello/foo/$id', + path: '/hello/foo/$id', + getParentRoute: () => layoutRoute, +} as any) +const dbInvoiceDetailRoute = dbInvoiceDetailRouteImport.update({ + id: '/$id', + path: '/$id', + getParentRoute: () => dbDashboardInvoicesRoute, +} as any) +const dbInvoicesIndexRoute = dbInvoicesIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => dbDashboardInvoicesRoute, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof indexRoute + '/$lang': typeof pagesRoute + '/dashboard': typeof dbDashboardRouteWithChildren + '/dashboard/': typeof dbDashboardIndexRoute + '/dashboard/invoices': typeof dbDashboardInvoicesRouteWithChildren + '/hello': typeof HelloIndexRoute + '/dashboard/invoices/': typeof dbInvoicesIndexRoute + '/dashboard/invoices/$id': typeof dbInvoiceDetailRoute + '/hello/foo/$id': typeof HelloFooIdRoute + '/hello/foo': typeof HelloFooIndexRoute +} +export interface FileRoutesByTo { + '/': typeof indexRoute + '/$lang': typeof pagesRoute + '/dashboard': typeof dbDashboardIndexRoute + '/hello': typeof HelloIndexRoute + '/dashboard/invoices': typeof dbInvoicesIndexRoute + '/dashboard/invoices/$id': typeof dbInvoiceDetailRoute + '/hello/foo/$id': typeof HelloFooIdRoute + '/hello/foo': typeof HelloFooIndexRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof indexRoute + '/_layout': typeof layoutRouteWithChildren + '/$lang/': typeof pagesRoute + '/_layout/dashboard': typeof dbDashboardRouteWithChildren + '/_layout/dashboard/': typeof dbDashboardIndexRoute + '/_layout/dashboard/invoices': typeof dbDashboardInvoicesRouteWithChildren + '/_layout/hello/': typeof HelloIndexRoute + '/_layout/dashboard/invoices/': typeof dbInvoicesIndexRoute + '/_layout/dashboard/invoices/$id': typeof dbInvoiceDetailRoute + '/_layout/hello/foo/$id': typeof HelloFooIdRoute + '/_layout/hello/foo/': typeof HelloFooIndexRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: + | '/' + | '/$lang' + | '/dashboard' + | '/dashboard/' + | '/dashboard/invoices' + | '/hello' + | '/dashboard/invoices/' + | '/dashboard/invoices/$id' + | '/hello/foo/$id' + | '/hello/foo' + fileRoutesByTo: FileRoutesByTo + to: + | '/' + | '/$lang' + | '/dashboard' + | '/hello' + | '/dashboard/invoices' + | '/dashboard/invoices/$id' + | '/hello/foo/$id' + | '/hello/foo' + id: + | '__root__' + | '/' + | '/_layout' + | '/$lang/' + | '/_layout/dashboard' + | '/_layout/dashboard/' + | '/_layout/dashboard/invoices' + | '/_layout/hello/' + | '/_layout/dashboard/invoices/' + | '/_layout/dashboard/invoices/$id' + | '/_layout/hello/foo/$id' + | '/_layout/hello/foo/' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + indexRoute: typeof indexRoute + layoutRoute: typeof layoutRouteWithChildren + pagesRoute: typeof pagesRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/_layout': { + id: '/_layout' + path: '' + fullPath: '' + preLoaderRoute: typeof layoutRouteImport + parentRoute: typeof rootRouteImport + } + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof indexRouteImport + parentRoute: typeof rootRouteImport + } + '/_layout/dashboard': { + id: '/_layout/dashboard' + path: '/dashboard' + fullPath: '/dashboard' + preLoaderRoute: typeof dbDashboardRouteImport + parentRoute: typeof layoutRoute + } + '/$lang/': { + id: '/$lang/' + path: '/$lang' + fullPath: '/$lang' + preLoaderRoute: typeof pagesRouteImport + parentRoute: typeof rootRouteImport + } + '/_layout/hello/': { + id: '/_layout/hello/' + path: '/hello' + fullPath: '/hello' + preLoaderRoute: typeof HelloIndexRouteImport + parentRoute: typeof layoutRoute + } + '/_layout/dashboard/invoices': { + id: '/_layout/dashboard/invoices' + path: '/invoices' + fullPath: '/dashboard/invoices' + preLoaderRoute: typeof dbDashboardInvoicesRouteImport + parentRoute: typeof dbDashboardRoute + } + '/_layout/dashboard/': { + id: '/_layout/dashboard/' + path: '/' + fullPath: '/dashboard/' + preLoaderRoute: typeof dbDashboardIndexRouteImport + parentRoute: typeof dbDashboardRoute + } + '/_layout/hello/foo/': { + id: '/_layout/hello/foo/' + path: '/hello/foo' + fullPath: '/hello/foo' + preLoaderRoute: typeof HelloFooIndexRouteImport + parentRoute: typeof layoutRoute + } + '/_layout/hello/foo/$id': { + id: '/_layout/hello/foo/$id' + path: '/hello/foo/$id' + fullPath: '/hello/foo/$id' + preLoaderRoute: typeof HelloFooIdRouteImport + parentRoute: typeof layoutRoute + } + '/_layout/dashboard/invoices/$id': { + id: '/_layout/dashboard/invoices/$id' + path: '/$id' + fullPath: '/dashboard/invoices/$id' + preLoaderRoute: typeof dbInvoiceDetailRouteImport + parentRoute: typeof dbDashboardInvoicesRoute + } + '/_layout/dashboard/invoices/': { + id: '/_layout/dashboard/invoices/' + path: '/' + fullPath: '/dashboard/invoices/' + preLoaderRoute: typeof dbInvoicesIndexRouteImport + parentRoute: typeof dbDashboardInvoicesRoute + } + } +} + +interface dbDashboardInvoicesRouteChildren { + dbInvoicesIndexRoute: typeof dbInvoicesIndexRoute + dbInvoiceDetailRoute: typeof dbInvoiceDetailRoute +} + +const dbDashboardInvoicesRouteChildren: dbDashboardInvoicesRouteChildren = { + dbInvoicesIndexRoute: dbInvoicesIndexRoute, + dbInvoiceDetailRoute: dbInvoiceDetailRoute, +} + +const dbDashboardInvoicesRouteWithChildren = + dbDashboardInvoicesRoute._addFileChildren(dbDashboardInvoicesRouteChildren) + +interface dbDashboardRouteChildren { + dbDashboardIndexRoute: typeof dbDashboardIndexRoute + dbDashboardInvoicesRoute: typeof dbDashboardInvoicesRouteWithChildren +} + +const dbDashboardRouteChildren: dbDashboardRouteChildren = { + dbDashboardIndexRoute: dbDashboardIndexRoute, + dbDashboardInvoicesRoute: dbDashboardInvoicesRouteWithChildren, +} + +const dbDashboardRouteWithChildren = dbDashboardRoute._addFileChildren( + dbDashboardRouteChildren, +) + +interface layoutRouteChildren { + dbDashboardRoute: typeof dbDashboardRouteWithChildren + HelloIndexRoute: typeof HelloIndexRoute + HelloFooIdRoute: typeof HelloFooIdRoute + HelloFooIndexRoute: typeof HelloFooIndexRoute +} + +const layoutRouteChildren: layoutRouteChildren = { + dbDashboardRoute: dbDashboardRouteWithChildren, + HelloIndexRoute: HelloIndexRoute, + HelloFooIdRoute: HelloFooIdRoute, + HelloFooIndexRoute: HelloFooIndexRoute, +} + +const layoutRouteWithChildren = + layoutRoute._addFileChildren(layoutRouteChildren) + +const rootRouteChildren: RootRouteChildren = { + indexRoute: indexRoute, + layoutRoute: layoutRouteWithChildren, + pagesRoute: pagesRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/packages/router-generator/tests/generator/virtual-config-file-named-export/routeTree.nonnested.snapshot.ts b/packages/router-generator/tests/generator/virtual-config-file-named-export/routeTree.nonnested.snapshot.ts new file mode 100644 index 00000000000..ab31f63e997 --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-config-file-named-export/routeTree.nonnested.snapshot.ts @@ -0,0 +1,292 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/root' +import { Route as layoutRouteImport } from './routes/layout' +import { Route as indexRouteImport } from './routes/index' +import { Route as dbDashboardRouteImport } from './routes/db/dashboard' +import { Route as pagesRouteImport } from './routes/pages' +import { Route as HelloIndexRouteImport } from './routes/subtree/index' +import { Route as dbDashboardInvoicesRouteImport } from './routes/db/dashboard-invoices' +import { Route as dbDashboardIndexRouteImport } from './routes/db/dashboard-index' +import { Route as HelloFooIndexRouteImport } from './routes/subtree/foo/index' +import { Route as HelloFooIdRouteImport } from './routes/subtree/foo/$id' +import { Route as dbInvoiceDetailRouteImport } from './routes/db/invoice-detail' +import { Route as dbInvoicesIndexRouteImport } from './routes/db/invoices-index' + +const layoutRoute = layoutRouteImport.update({ + id: '/_layout', + getParentRoute: () => rootRouteImport, +} as any) +const indexRoute = indexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) +const dbDashboardRoute = dbDashboardRouteImport.update({ + id: '/dashboard', + path: '/dashboard', + getParentRoute: () => layoutRoute, +} as any) +const pagesRoute = pagesRouteImport.update({ + id: '/$lang/', + path: '/$lang/', + getParentRoute: () => rootRouteImport, +} as any) +const HelloIndexRoute = HelloIndexRouteImport.update({ + id: '/hello/', + path: '/hello/', + getParentRoute: () => layoutRoute, +} as any) +const dbDashboardInvoicesRoute = dbDashboardInvoicesRouteImport.update({ + id: '/invoices', + path: '/invoices', + getParentRoute: () => dbDashboardRoute, +} as any) +const dbDashboardIndexRoute = dbDashboardIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => dbDashboardRoute, +} as any) +const HelloFooIndexRoute = HelloFooIndexRouteImport.update({ + id: '/hello/foo/', + path: '/hello/foo/', + getParentRoute: () => layoutRoute, +} as any) +const HelloFooIdRoute = HelloFooIdRouteImport.update({ + id: '/hello/foo/$id', + path: '/hello/foo/$id', + getParentRoute: () => layoutRoute, +} as any) +const dbInvoiceDetailRoute = dbInvoiceDetailRouteImport.update({ + id: '/$id', + path: '/$id', + getParentRoute: () => dbDashboardInvoicesRoute, +} as any) +const dbInvoicesIndexRoute = dbInvoicesIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => dbDashboardInvoicesRoute, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof indexRoute + '/$lang': typeof pagesRoute + '/dashboard': typeof dbDashboardRouteWithChildren + '/dashboard/': typeof dbDashboardIndexRoute + '/dashboard/invoices': typeof dbDashboardInvoicesRouteWithChildren + '/hello': typeof HelloIndexRoute + '/dashboard/invoices/': typeof dbInvoicesIndexRoute + '/dashboard/invoices/$id': typeof dbInvoiceDetailRoute + '/hello/foo/$id': typeof HelloFooIdRoute + '/hello/foo': typeof HelloFooIndexRoute +} +export interface FileRoutesByTo { + '/': typeof indexRoute + '/$lang': typeof pagesRoute + '/dashboard': typeof dbDashboardIndexRoute + '/hello': typeof HelloIndexRoute + '/dashboard/invoices': typeof dbInvoicesIndexRoute + '/dashboard/invoices/$id': typeof dbInvoiceDetailRoute + '/hello/foo/$id': typeof HelloFooIdRoute + '/hello/foo': typeof HelloFooIndexRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof indexRoute + '/_layout': typeof layoutRouteWithChildren + '/$lang/': typeof pagesRoute + '/_layout/dashboard': typeof dbDashboardRouteWithChildren + '/_layout/dashboard/': typeof dbDashboardIndexRoute + '/_layout/dashboard/invoices': typeof dbDashboardInvoicesRouteWithChildren + '/_layout/hello/': typeof HelloIndexRoute + '/_layout/dashboard/invoices/': typeof dbInvoicesIndexRoute + '/_layout/dashboard/invoices/$id': typeof dbInvoiceDetailRoute + '/_layout/hello/foo/$id': typeof HelloFooIdRoute + '/_layout/hello/foo/': typeof HelloFooIndexRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: + | '/' + | '/$lang' + | '/dashboard' + | '/dashboard/' + | '/dashboard/invoices' + | '/hello' + | '/dashboard/invoices/' + | '/dashboard/invoices/$id' + | '/hello/foo/$id' + | '/hello/foo' + fileRoutesByTo: FileRoutesByTo + to: + | '/' + | '/$lang' + | '/dashboard' + | '/hello' + | '/dashboard/invoices' + | '/dashboard/invoices/$id' + | '/hello/foo/$id' + | '/hello/foo' + id: + | '__root__' + | '/' + | '/_layout' + | '/$lang/' + | '/_layout/dashboard' + | '/_layout/dashboard/' + | '/_layout/dashboard/invoices' + | '/_layout/hello/' + | '/_layout/dashboard/invoices/' + | '/_layout/dashboard/invoices/$id' + | '/_layout/hello/foo/$id' + | '/_layout/hello/foo/' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + indexRoute: typeof indexRoute + layoutRoute: typeof layoutRouteWithChildren + pagesRoute: typeof pagesRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/_layout': { + id: '/_layout' + path: '' + fullPath: '' + preLoaderRoute: typeof layoutRouteImport + parentRoute: typeof rootRouteImport + } + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof indexRouteImport + parentRoute: typeof rootRouteImport + } + '/_layout/dashboard': { + id: '/_layout/dashboard' + path: '/dashboard' + fullPath: '/dashboard' + preLoaderRoute: typeof dbDashboardRouteImport + parentRoute: typeof layoutRoute + } + '/$lang/': { + id: '/$lang/' + path: '/$lang' + fullPath: '/$lang' + preLoaderRoute: typeof pagesRouteImport + parentRoute: typeof rootRouteImport + } + '/_layout/hello/': { + id: '/_layout/hello/' + path: '/hello' + fullPath: '/hello' + preLoaderRoute: typeof HelloIndexRouteImport + parentRoute: typeof layoutRoute + } + '/_layout/dashboard/invoices': { + id: '/_layout/dashboard/invoices' + path: '/invoices' + fullPath: '/dashboard/invoices' + preLoaderRoute: typeof dbDashboardInvoicesRouteImport + parentRoute: typeof dbDashboardRoute + } + '/_layout/dashboard/': { + id: '/_layout/dashboard/' + path: '/' + fullPath: '/dashboard/' + preLoaderRoute: typeof dbDashboardIndexRouteImport + parentRoute: typeof dbDashboardRoute + } + '/_layout/hello/foo/': { + id: '/_layout/hello/foo/' + path: '/hello/foo' + fullPath: '/hello/foo' + preLoaderRoute: typeof HelloFooIndexRouteImport + parentRoute: typeof layoutRoute + } + '/_layout/hello/foo/$id': { + id: '/_layout/hello/foo/$id' + path: '/hello/foo/$id' + fullPath: '/hello/foo/$id' + preLoaderRoute: typeof HelloFooIdRouteImport + parentRoute: typeof layoutRoute + } + '/_layout/dashboard/invoices/$id': { + id: '/_layout/dashboard/invoices/$id' + path: '/$id' + fullPath: '/dashboard/invoices/$id' + preLoaderRoute: typeof dbInvoiceDetailRouteImport + parentRoute: typeof dbDashboardInvoicesRoute + } + '/_layout/dashboard/invoices/': { + id: '/_layout/dashboard/invoices/' + path: '/' + fullPath: '/dashboard/invoices/' + preLoaderRoute: typeof dbInvoicesIndexRouteImport + parentRoute: typeof dbDashboardInvoicesRoute + } + } +} + +interface dbDashboardInvoicesRouteChildren { + dbInvoicesIndexRoute: typeof dbInvoicesIndexRoute + dbInvoiceDetailRoute: typeof dbInvoiceDetailRoute +} + +const dbDashboardInvoicesRouteChildren: dbDashboardInvoicesRouteChildren = { + dbInvoicesIndexRoute: dbInvoicesIndexRoute, + dbInvoiceDetailRoute: dbInvoiceDetailRoute, +} + +const dbDashboardInvoicesRouteWithChildren = + dbDashboardInvoicesRoute._addFileChildren(dbDashboardInvoicesRouteChildren) + +interface dbDashboardRouteChildren { + dbDashboardIndexRoute: typeof dbDashboardIndexRoute + dbDashboardInvoicesRoute: typeof dbDashboardInvoicesRouteWithChildren +} + +const dbDashboardRouteChildren: dbDashboardRouteChildren = { + dbDashboardIndexRoute: dbDashboardIndexRoute, + dbDashboardInvoicesRoute: dbDashboardInvoicesRouteWithChildren, +} + +const dbDashboardRouteWithChildren = dbDashboardRoute._addFileChildren( + dbDashboardRouteChildren, +) + +interface layoutRouteChildren { + dbDashboardRoute: typeof dbDashboardRouteWithChildren + HelloIndexRoute: typeof HelloIndexRoute + HelloFooIdRoute: typeof HelloFooIdRoute + HelloFooIndexRoute: typeof HelloFooIndexRoute +} + +const layoutRouteChildren: layoutRouteChildren = { + dbDashboardRoute: dbDashboardRouteWithChildren, + HelloIndexRoute: HelloIndexRoute, + HelloFooIdRoute: HelloFooIdRoute, + HelloFooIndexRoute: HelloFooIndexRoute, +} + +const layoutRouteWithChildren = + layoutRoute._addFileChildren(layoutRouteChildren) + +const rootRouteChildren: RootRouteChildren = { + indexRoute: indexRoute, + layoutRoute: layoutRouteWithChildren, + pagesRoute: pagesRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/packages/router-generator/tests/generator/virtual-inside-nested/routeTree.nonnested.snapshot.ts b/packages/router-generator/tests/generator/virtual-inside-nested/routeTree.nonnested.snapshot.ts new file mode 100644 index 00000000000..174f18465b8 --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-inside-nested/routeTree.nonnested.snapshot.ts @@ -0,0 +1,121 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/__root' +import { Route as IndexRouteImport } from './routes/index' +import { Route as FooBarRouteImport } from './routes/foo/bar' +import { Route as fooBarDetailsRouteImport } from './routes/foo/bar/details' +import { Route as fooBarHomeRouteImport } from './routes/foo/bar/home' + +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) +const FooBarRoute = FooBarRouteImport.update({ + id: '/foo/bar', + path: '/foo/bar', + getParentRoute: () => rootRouteImport, +} as any) +const fooBarDetailsRoute = fooBarDetailsRouteImport.update({ + id: '/$id', + path: '/$id', + getParentRoute: () => FooBarRoute, +} as any) +const fooBarHomeRoute = fooBarHomeRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => FooBarRoute, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute + '/foo/bar': typeof FooBarRouteWithChildren + '/foo/bar/': typeof fooBarHomeRoute + '/foo/bar/$id': typeof fooBarDetailsRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute + '/foo/bar': typeof fooBarHomeRoute + '/foo/bar/$id': typeof fooBarDetailsRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute + '/foo/bar': typeof FooBarRouteWithChildren + '/foo/bar/': typeof fooBarHomeRoute + '/foo/bar/$id': typeof fooBarDetailsRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' | '/foo/bar' | '/foo/bar/' | '/foo/bar/$id' + fileRoutesByTo: FileRoutesByTo + to: '/' | '/foo/bar' | '/foo/bar/$id' + id: '__root__' | '/' | '/foo/bar' | '/foo/bar/' | '/foo/bar/$id' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute + FooBarRoute: typeof FooBarRouteWithChildren +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + '/foo/bar': { + id: '/foo/bar' + path: '/foo/bar' + fullPath: '/foo/bar' + preLoaderRoute: typeof FooBarRouteImport + parentRoute: typeof rootRouteImport + } + '/foo/bar/$id': { + id: '/foo/bar/$id' + path: '/$id' + fullPath: '/foo/bar/$id' + preLoaderRoute: typeof fooBarDetailsRouteImport + parentRoute: typeof FooBarRoute + } + '/foo/bar/': { + id: '/foo/bar/' + path: '/' + fullPath: '/foo/bar/' + preLoaderRoute: typeof fooBarHomeRouteImport + parentRoute: typeof FooBarRoute + } + } +} + +interface FooBarRouteChildren { + fooBarHomeRoute: typeof fooBarHomeRoute + fooBarDetailsRoute: typeof fooBarDetailsRoute +} + +const FooBarRouteChildren: FooBarRouteChildren = { + fooBarHomeRoute: fooBarHomeRoute, + fooBarDetailsRoute: fooBarDetailsRoute, +} + +const FooBarRouteWithChildren = + FooBarRoute._addFileChildren(FooBarRouteChildren) + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, + FooBarRoute: FooBarRouteWithChildren, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/packages/router-generator/tests/generator/virtual/routeTree.nonnested.snapshot.ts b/packages/router-generator/tests/generator/virtual/routeTree.nonnested.snapshot.ts new file mode 100644 index 00000000000..ab31f63e997 --- /dev/null +++ b/packages/router-generator/tests/generator/virtual/routeTree.nonnested.snapshot.ts @@ -0,0 +1,292 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/root' +import { Route as layoutRouteImport } from './routes/layout' +import { Route as indexRouteImport } from './routes/index' +import { Route as dbDashboardRouteImport } from './routes/db/dashboard' +import { Route as pagesRouteImport } from './routes/pages' +import { Route as HelloIndexRouteImport } from './routes/subtree/index' +import { Route as dbDashboardInvoicesRouteImport } from './routes/db/dashboard-invoices' +import { Route as dbDashboardIndexRouteImport } from './routes/db/dashboard-index' +import { Route as HelloFooIndexRouteImport } from './routes/subtree/foo/index' +import { Route as HelloFooIdRouteImport } from './routes/subtree/foo/$id' +import { Route as dbInvoiceDetailRouteImport } from './routes/db/invoice-detail' +import { Route as dbInvoicesIndexRouteImport } from './routes/db/invoices-index' + +const layoutRoute = layoutRouteImport.update({ + id: '/_layout', + getParentRoute: () => rootRouteImport, +} as any) +const indexRoute = indexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) +const dbDashboardRoute = dbDashboardRouteImport.update({ + id: '/dashboard', + path: '/dashboard', + getParentRoute: () => layoutRoute, +} as any) +const pagesRoute = pagesRouteImport.update({ + id: '/$lang/', + path: '/$lang/', + getParentRoute: () => rootRouteImport, +} as any) +const HelloIndexRoute = HelloIndexRouteImport.update({ + id: '/hello/', + path: '/hello/', + getParentRoute: () => layoutRoute, +} as any) +const dbDashboardInvoicesRoute = dbDashboardInvoicesRouteImport.update({ + id: '/invoices', + path: '/invoices', + getParentRoute: () => dbDashboardRoute, +} as any) +const dbDashboardIndexRoute = dbDashboardIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => dbDashboardRoute, +} as any) +const HelloFooIndexRoute = HelloFooIndexRouteImport.update({ + id: '/hello/foo/', + path: '/hello/foo/', + getParentRoute: () => layoutRoute, +} as any) +const HelloFooIdRoute = HelloFooIdRouteImport.update({ + id: '/hello/foo/$id', + path: '/hello/foo/$id', + getParentRoute: () => layoutRoute, +} as any) +const dbInvoiceDetailRoute = dbInvoiceDetailRouteImport.update({ + id: '/$id', + path: '/$id', + getParentRoute: () => dbDashboardInvoicesRoute, +} as any) +const dbInvoicesIndexRoute = dbInvoicesIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => dbDashboardInvoicesRoute, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof indexRoute + '/$lang': typeof pagesRoute + '/dashboard': typeof dbDashboardRouteWithChildren + '/dashboard/': typeof dbDashboardIndexRoute + '/dashboard/invoices': typeof dbDashboardInvoicesRouteWithChildren + '/hello': typeof HelloIndexRoute + '/dashboard/invoices/': typeof dbInvoicesIndexRoute + '/dashboard/invoices/$id': typeof dbInvoiceDetailRoute + '/hello/foo/$id': typeof HelloFooIdRoute + '/hello/foo': typeof HelloFooIndexRoute +} +export interface FileRoutesByTo { + '/': typeof indexRoute + '/$lang': typeof pagesRoute + '/dashboard': typeof dbDashboardIndexRoute + '/hello': typeof HelloIndexRoute + '/dashboard/invoices': typeof dbInvoicesIndexRoute + '/dashboard/invoices/$id': typeof dbInvoiceDetailRoute + '/hello/foo/$id': typeof HelloFooIdRoute + '/hello/foo': typeof HelloFooIndexRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof indexRoute + '/_layout': typeof layoutRouteWithChildren + '/$lang/': typeof pagesRoute + '/_layout/dashboard': typeof dbDashboardRouteWithChildren + '/_layout/dashboard/': typeof dbDashboardIndexRoute + '/_layout/dashboard/invoices': typeof dbDashboardInvoicesRouteWithChildren + '/_layout/hello/': typeof HelloIndexRoute + '/_layout/dashboard/invoices/': typeof dbInvoicesIndexRoute + '/_layout/dashboard/invoices/$id': typeof dbInvoiceDetailRoute + '/_layout/hello/foo/$id': typeof HelloFooIdRoute + '/_layout/hello/foo/': typeof HelloFooIndexRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: + | '/' + | '/$lang' + | '/dashboard' + | '/dashboard/' + | '/dashboard/invoices' + | '/hello' + | '/dashboard/invoices/' + | '/dashboard/invoices/$id' + | '/hello/foo/$id' + | '/hello/foo' + fileRoutesByTo: FileRoutesByTo + to: + | '/' + | '/$lang' + | '/dashboard' + | '/hello' + | '/dashboard/invoices' + | '/dashboard/invoices/$id' + | '/hello/foo/$id' + | '/hello/foo' + id: + | '__root__' + | '/' + | '/_layout' + | '/$lang/' + | '/_layout/dashboard' + | '/_layout/dashboard/' + | '/_layout/dashboard/invoices' + | '/_layout/hello/' + | '/_layout/dashboard/invoices/' + | '/_layout/dashboard/invoices/$id' + | '/_layout/hello/foo/$id' + | '/_layout/hello/foo/' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + indexRoute: typeof indexRoute + layoutRoute: typeof layoutRouteWithChildren + pagesRoute: typeof pagesRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/_layout': { + id: '/_layout' + path: '' + fullPath: '' + preLoaderRoute: typeof layoutRouteImport + parentRoute: typeof rootRouteImport + } + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof indexRouteImport + parentRoute: typeof rootRouteImport + } + '/_layout/dashboard': { + id: '/_layout/dashboard' + path: '/dashboard' + fullPath: '/dashboard' + preLoaderRoute: typeof dbDashboardRouteImport + parentRoute: typeof layoutRoute + } + '/$lang/': { + id: '/$lang/' + path: '/$lang' + fullPath: '/$lang' + preLoaderRoute: typeof pagesRouteImport + parentRoute: typeof rootRouteImport + } + '/_layout/hello/': { + id: '/_layout/hello/' + path: '/hello' + fullPath: '/hello' + preLoaderRoute: typeof HelloIndexRouteImport + parentRoute: typeof layoutRoute + } + '/_layout/dashboard/invoices': { + id: '/_layout/dashboard/invoices' + path: '/invoices' + fullPath: '/dashboard/invoices' + preLoaderRoute: typeof dbDashboardInvoicesRouteImport + parentRoute: typeof dbDashboardRoute + } + '/_layout/dashboard/': { + id: '/_layout/dashboard/' + path: '/' + fullPath: '/dashboard/' + preLoaderRoute: typeof dbDashboardIndexRouteImport + parentRoute: typeof dbDashboardRoute + } + '/_layout/hello/foo/': { + id: '/_layout/hello/foo/' + path: '/hello/foo' + fullPath: '/hello/foo' + preLoaderRoute: typeof HelloFooIndexRouteImport + parentRoute: typeof layoutRoute + } + '/_layout/hello/foo/$id': { + id: '/_layout/hello/foo/$id' + path: '/hello/foo/$id' + fullPath: '/hello/foo/$id' + preLoaderRoute: typeof HelloFooIdRouteImport + parentRoute: typeof layoutRoute + } + '/_layout/dashboard/invoices/$id': { + id: '/_layout/dashboard/invoices/$id' + path: '/$id' + fullPath: '/dashboard/invoices/$id' + preLoaderRoute: typeof dbInvoiceDetailRouteImport + parentRoute: typeof dbDashboardInvoicesRoute + } + '/_layout/dashboard/invoices/': { + id: '/_layout/dashboard/invoices/' + path: '/' + fullPath: '/dashboard/invoices/' + preLoaderRoute: typeof dbInvoicesIndexRouteImport + parentRoute: typeof dbDashboardInvoicesRoute + } + } +} + +interface dbDashboardInvoicesRouteChildren { + dbInvoicesIndexRoute: typeof dbInvoicesIndexRoute + dbInvoiceDetailRoute: typeof dbInvoiceDetailRoute +} + +const dbDashboardInvoicesRouteChildren: dbDashboardInvoicesRouteChildren = { + dbInvoicesIndexRoute: dbInvoicesIndexRoute, + dbInvoiceDetailRoute: dbInvoiceDetailRoute, +} + +const dbDashboardInvoicesRouteWithChildren = + dbDashboardInvoicesRoute._addFileChildren(dbDashboardInvoicesRouteChildren) + +interface dbDashboardRouteChildren { + dbDashboardIndexRoute: typeof dbDashboardIndexRoute + dbDashboardInvoicesRoute: typeof dbDashboardInvoicesRouteWithChildren +} + +const dbDashboardRouteChildren: dbDashboardRouteChildren = { + dbDashboardIndexRoute: dbDashboardIndexRoute, + dbDashboardInvoicesRoute: dbDashboardInvoicesRouteWithChildren, +} + +const dbDashboardRouteWithChildren = dbDashboardRoute._addFileChildren( + dbDashboardRouteChildren, +) + +interface layoutRouteChildren { + dbDashboardRoute: typeof dbDashboardRouteWithChildren + HelloIndexRoute: typeof HelloIndexRoute + HelloFooIdRoute: typeof HelloFooIdRoute + HelloFooIndexRoute: typeof HelloFooIndexRoute +} + +const layoutRouteChildren: layoutRouteChildren = { + dbDashboardRoute: dbDashboardRouteWithChildren, + HelloIndexRoute: HelloIndexRoute, + HelloFooIdRoute: HelloFooIdRoute, + HelloFooIndexRoute: HelloFooIndexRoute, +} + +const layoutRouteWithChildren = + layoutRoute._addFileChildren(layoutRouteChildren) + +const rootRouteChildren: RootRouteChildren = { + indexRoute: indexRoute, + layoutRoute: layoutRouteWithChildren, + pagesRoute: pagesRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/packages/router-generator/tests/utils.test.ts b/packages/router-generator/tests/utils.test.ts index a2a995ffb76..6376993aa91 100644 --- a/packages/router-generator/tests/utils.test.ts +++ b/packages/router-generator/tests/utils.test.ts @@ -18,31 +18,59 @@ describe('cleanPath', () => { describe('determineInitialRoutePath', () => { it('removes dots and adds slashes', () => { - expect(determineInitialRoutePath('test.test')).toBe('/test/test') + expect(determineInitialRoutePath('test.test')).toStrictEqual({ + routePath: '/test/test', + isExperimentalNonNestedPath: false, + cleanedRoutePath: '/test/test', + }) }) it('keeps leading slash', () => { - expect(determineInitialRoutePath('/test.test')).toBe('/test/test') + expect(determineInitialRoutePath('/test.test')).toStrictEqual({ + routePath: '/test/test', + isExperimentalNonNestedPath: false, + cleanedRoutePath: '/test/test', + }) }) it('keeps trailing slash', () => { - expect(determineInitialRoutePath('test.test/')).toBe('/test/test/') + expect(determineInitialRoutePath('test.test/')).toStrictEqual({ + routePath: '/test/test/', + isExperimentalNonNestedPath: false, + cleanedRoutePath: '/test/test/', + }) }) it('removes dots and adds slashes with leading and trailing slashes', () => { - expect(determineInitialRoutePath('/test.test/')).toBe('/test/test/') + expect(determineInitialRoutePath('/test.test/')).toStrictEqual({ + routePath: '/test/test/', + isExperimentalNonNestedPath: false, + cleanedRoutePath: '/test/test/', + }) }) it("returns '/' if path is empty", () => { - expect(determineInitialRoutePath('')).toBe('/') + expect(determineInitialRoutePath('')).toStrictEqual({ + routePath: '/', + isExperimentalNonNestedPath: false, + cleanedRoutePath: '/', + }) }) it("returns '/' if path is '.'", () => { - expect(determineInitialRoutePath('.')).toBe('/') + expect(determineInitialRoutePath('.')).toStrictEqual({ + routePath: '/', + isExperimentalNonNestedPath: false, + cleanedRoutePath: '/', + }) }) it("returns '/' if path is './'", () => { - expect(determineInitialRoutePath('./')).toBe('/') + expect(determineInitialRoutePath('./')).toStrictEqual({ + routePath: '/', + isExperimentalNonNestedPath: false, + cleanedRoutePath: '/', + }) }) }) diff --git a/packages/router-generator/vite.config.ts b/packages/router-generator/vite.config.ts index 5389f0f7391..1474004cd45 100644 --- a/packages/router-generator/vite.config.ts +++ b/packages/router-generator/vite.config.ts @@ -7,7 +7,10 @@ const config = defineConfig({ name: packageJson.name, dir: './tests', watch: false, - typecheck: { enabled: true }, + typecheck: { + enabled: true, + exclude: ['./tests/generator/types-disabled/**/*.tsx'], + }, }, }) From ee640dec99ae5516c83555d571a5dfb9877443bd Mon Sep 17 00:00:00 2001 From: Nico Lynzaad Date: Mon, 6 Oct 2025 04:59:08 +0200 Subject: [PATCH 04/25] update basic-file-based-e2e --- .../basic-file-based/package.json | 8 ++- .../basic-file-based/playwright.config.ts | 18 +++++- .../basic-file-based/src/routeTree.gen.ts | 55 ++----------------- .../params-ps/non-nested/$foo_/$bar.tsx | 6 +- .../src/routes/params-ps/non-nested/route.tsx | 5 +- .../src/routes/posts_.$postId.edit.tsx | 15 +++-- .../tests/non-nested-paths.spec.ts | 9 ++- .../basic-file-based/tests/params.spec.ts | 20 +++++-- .../utils/useExperimentalNonNestedPaths.ts | 6 ++ .../basic-file-based/vite.config.js | 10 +++- 10 files changed, 87 insertions(+), 65 deletions(-) create mode 100644 e2e/react-router/basic-file-based/tests/utils/useExperimentalNonNestedPaths.ts diff --git a/e2e/react-router/basic-file-based/package.json b/e2e/react-router/basic-file-based/package.json index 5a145f3610d..071ee248f34 100644 --- a/e2e/react-router/basic-file-based/package.json +++ b/e2e/react-router/basic-file-based/package.json @@ -4,11 +4,17 @@ "type": "module", "scripts": { "dev": "vite --port 3000", + "dev:nonnested": "MODE=nonnested VITE_MODE=nonnested vite --port 3000", "dev:e2e": "vite", "build": "vite build && tsc --noEmit", + "build:nonnested": "MODE=nonnested VITE_MODE=nonnested vite build && tsc --noEmit", "serve": "vite preview", + "serve:nonnested": "MODE=nonnested VITE_MODE=nonnested vite preview", "start": "vite", - "test:e2e": "rm -rf port*.txt; playwright test --project=chromium" + "start:nonnested": "MODE=nonnested VITE_MODE=nonnested vite", + "test:e2e": "pnpm run test:e2e:default && pnpm run test:e2e:nonnested", + "test:e2e:default": "rm -rf port*.txt; playwright test --project=chromium", + "test:e2e:nonnested": "rm -rf port*.txt; MODE=nonnested playwright test --project=chromium" }, "dependencies": { "@tanstack/react-router": "workspace:^", diff --git a/e2e/react-router/basic-file-based/playwright.config.ts b/e2e/react-router/basic-file-based/playwright.config.ts index 4dc2271f01e..718e5b30f81 100644 --- a/e2e/react-router/basic-file-based/playwright.config.ts +++ b/e2e/react-router/basic-file-based/playwright.config.ts @@ -4,10 +4,19 @@ import { getTestServerPort, } from '@tanstack/router-e2e-utils' import packageJson from './package.json' with { type: 'json' } +import { useExperimentalNonNestedPaths } from './tests/utils/useExperimentalNonNestedPaths' const PORT = await getTestServerPort(packageJson.name) const EXTERNAL_PORT = await getDummyServerPort(packageJson.name) const baseURL = `http://localhost:${PORT}` +const experimentalNonNestedPathsModeCommand = `pnpm build:nonnested && pnpm serve:nonnested --port ${PORT}` +const defaultCommand = `pnpm build && pnpm serve --port ${PORT}` +const command = useExperimentalNonNestedPaths + ? experimentalNonNestedPathsModeCommand + : defaultCommand + +console.info('Running with mode: ', process.env.MODE || 'default') + /** * See https://playwright.dev/docs/test-configuration. */ @@ -26,10 +35,17 @@ export default defineConfig({ }, webServer: { - command: `VITE_NODE_ENV="test" VITE_SERVER_PORT=${PORT} VITE_EXTERNAL_PORT=${EXTERNAL_PORT} pnpm build && VITE_SERVER_PORT=${PORT} pnpm serve --port ${PORT}`, + command, url: baseURL, reuseExistingServer: !process.env.CI, stdout: 'pipe', + env: { + MODE: process.env.MODE || '', + VITE_NODE_ENV: 'test', + VITE_EXTERNAL_PORT: String(EXTERNAL_PORT), + VITE_SERVER_PORT: String(PORT), + PORT: String(PORT), + }, }, projects: [ diff --git a/e2e/react-router/basic-file-based/src/routeTree.gen.ts b/e2e/react-router/basic-file-based/src/routeTree.gen.ts index 5c4c85f21c7..561c8592537 100644 --- a/e2e/react-router/basic-file-based/src/routeTree.gen.ts +++ b/e2e/react-router/basic-file-based/src/routeTree.gen.ts @@ -8,8 +8,6 @@ // You should NOT make any changes in this file as it will be overwritten. // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. -import { createFileRoute } from '@tanstack/react-router' - import { Route as rootRouteImport } from './routes/__root' import { Route as Char45824Char54620Char48124Char44397RouteImport } from './routes/대한민국' import { Route as RemountDepsRouteImport } from './routes/remountDeps' @@ -99,12 +97,6 @@ import { Route as RelativeLinkPathPathIndexRouteImport } from './routes/relative import { Route as RelativeLinkNestedDeepIndexRouteImport } from './routes/relative/link/nested/deep/index' import { Route as ParamsPsNamedFooBarBazRouteImport } from './routes/params-ps/named/$foo/$bar.$baz' -const groupRouteImport = createFileRoute('/(group)')() - -const groupRoute = groupRouteImport.update({ - id: '/(group)', - getParentRoute: () => rootRouteImport, -} as any) const Char45824Char54620Char48124Char44397Route = Char45824Char54620Char48124Char44397RouteImport.update({ id: '/대한민국', @@ -572,7 +564,7 @@ const ParamsPsNamedFooBarBazRoute = ParamsPsNamedFooBarBazRouteImport.update({ } as any) export interface FileRoutesByFullPath { - '/': typeof groupLayoutRouteWithChildren + '/': typeof IndexRoute '/non-nested': typeof NonNestedRouteRouteWithChildren '/search-params': typeof SearchParamsRouteRouteWithChildren '/anchor': typeof AnchorRoute @@ -658,7 +650,7 @@ export interface FileRoutesByFullPath { '/relative/useNavigate/path/$path': typeof RelativeUseNavigatePathPathIndexRoute } export interface FileRoutesByTo { - '/': typeof groupLayoutRouteWithChildren + '/': typeof IndexRoute '/non-nested': typeof NonNestedRouteRouteWithChildren '/anchor': typeof AnchorRoute '/component-types-test': typeof ComponentTypesTestRoute @@ -758,7 +750,6 @@ export interface FileRoutesById { '/relative/link': typeof RelativeLinkRouteRouteWithChildren '/relative/useNavigate': typeof RelativeUseNavigateRouteRouteWithChildren '/(another-group)/onlyrouteinside': typeof anotherGroupOnlyrouteinsideRoute - '/(group)': typeof groupRouteWithChildren '/(group)/_layout': typeof groupLayoutRouteWithChildren '/(group)/inside': typeof groupInsideRoute '/(group)/lazyinside': typeof groupLazyinsideRoute @@ -1015,7 +1006,6 @@ export interface FileRouteTypes { | '/relative/link' | '/relative/useNavigate' | '/(another-group)/onlyrouteinside' - | '/(group)' | '/(group)/_layout' | '/(group)/inside' | '/(group)/lazyinside' @@ -1102,7 +1092,6 @@ export interface RootRouteChildren { RelativeLinkRouteRoute: typeof RelativeLinkRouteRouteWithChildren RelativeUseNavigateRouteRoute: typeof RelativeUseNavigateRouteRouteWithChildren anotherGroupOnlyrouteinsideRoute: typeof anotherGroupOnlyrouteinsideRoute - groupRoute: typeof groupRouteWithChildren RedirectTargetRoute: typeof RedirectTargetRouteWithChildren StructuralSharingEnabledRoute: typeof StructuralSharingEnabledRoute ParamsPsIndexRoute: typeof ParamsPsIndexRoute @@ -1125,13 +1114,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/(group)': { - id: '/(group)' - path: '/' - fullPath: '/' - preLoaderRoute: typeof groupRouteImport - parentRoute: typeof rootRouteImport - } '/대한민국': { id: '/대한민국' path: '/대한민국' @@ -1302,8 +1284,8 @@ declare module '@tanstack/react-router' { } '/(group)/_layout': { id: '/(group)/_layout' - path: '/' - fullPath: '/' + path: '' + fullPath: '' preLoaderRoute: typeof groupLayoutRouteImport parentRoute: typeof groupRoute } @@ -2017,34 +1999,6 @@ const RelativeUseNavigateRouteRouteWithChildren = RelativeUseNavigateRouteRouteChildren, ) -interface groupLayoutRouteChildren { - groupLayoutInsidelayoutRoute: typeof groupLayoutInsidelayoutRoute -} - -const groupLayoutRouteChildren: groupLayoutRouteChildren = { - groupLayoutInsidelayoutRoute: groupLayoutInsidelayoutRoute, -} - -const groupLayoutRouteWithChildren = groupLayoutRoute._addFileChildren( - groupLayoutRouteChildren, -) - -interface groupRouteChildren { - groupLayoutRoute: typeof groupLayoutRouteWithChildren - groupInsideRoute: typeof groupInsideRoute - groupLazyinsideRoute: typeof groupLazyinsideRoute - groupSubfolderInsideRoute: typeof groupSubfolderInsideRoute -} - -const groupRouteChildren: groupRouteChildren = { - groupLayoutRoute: groupLayoutRouteWithChildren, - groupInsideRoute: groupInsideRoute, - groupLazyinsideRoute: groupLazyinsideRoute, - groupSubfolderInsideRoute: groupSubfolderInsideRoute, -} - -const groupRouteWithChildren = groupRoute._addFileChildren(groupRouteChildren) - interface RedirectTargetRouteChildren { RedirectTargetViaBeforeLoadRoute: typeof RedirectTargetViaBeforeLoadRoute RedirectTargetViaLoaderRoute: typeof RedirectTargetViaLoaderRoute @@ -2106,7 +2060,6 @@ const rootRouteChildren: RootRouteChildren = { RelativeLinkRouteRoute: RelativeLinkRouteRouteWithChildren, RelativeUseNavigateRouteRoute: RelativeUseNavigateRouteRouteWithChildren, anotherGroupOnlyrouteinsideRoute: anotherGroupOnlyrouteinsideRoute, - groupRoute: groupRouteWithChildren, RedirectTargetRoute: RedirectTargetRouteWithChildren, StructuralSharingEnabledRoute: StructuralSharingEnabledRoute, ParamsPsIndexRoute: ParamsPsIndexRoute, diff --git a/e2e/react-router/basic-file-based/src/routes/params-ps/non-nested/$foo_/$bar.tsx b/e2e/react-router/basic-file-based/src/routes/params-ps/non-nested/$foo_/$bar.tsx index 4f1406030d7..3a30f4b9c00 100644 --- a/e2e/react-router/basic-file-based/src/routes/params-ps/non-nested/$foo_/$bar.tsx +++ b/e2e/react-router/basic-file-based/src/routes/params-ps/non-nested/$foo_/$bar.tsx @@ -1,11 +1,15 @@ import { createFileRoute, useParams } from '@tanstack/react-router' +import { useExperimentalNonNestedPaths } from '../../../../../tests/utils/useExperimentalNonNestedPaths' export const Route = createFileRoute('/params-ps/non-nested/$foo_/$bar')({ component: RouteComponent, }) function RouteComponent() { - const fooParams = useParams({ from: '/params-ps/non-nested/$foo_' }) + const fooParams = useParams({ + // @ts-expect-error path is updated with new Experimental Non Nested Paths to not include the trailing underscore + from: `/params-ps/non-nested/${useExperimentalNonNestedPaths ? '$foo' : '$foo_'}`, + }) const routeParams = Route.useParams() return ( diff --git a/e2e/react-router/basic-file-based/src/routes/params-ps/non-nested/route.tsx b/e2e/react-router/basic-file-based/src/routes/params-ps/non-nested/route.tsx index 93fd9362182..67045ab460f 100644 --- a/e2e/react-router/basic-file-based/src/routes/params-ps/non-nested/route.tsx +++ b/e2e/react-router/basic-file-based/src/routes/params-ps/non-nested/route.tsx @@ -8,13 +8,16 @@ function RouteComponent() { return (

Non-nested path params

-
    +

    /params-ps/non-nested/route

    +
    +
    • /params-ps/non-nested/foo/bar diff --git a/e2e/react-router/basic-file-based/src/routes/posts_.$postId.edit.tsx b/e2e/react-router/basic-file-based/src/routes/posts_.$postId.edit.tsx index 7ddf02cc21a..345079caca4 100644 --- a/e2e/react-router/basic-file-based/src/routes/posts_.$postId.edit.tsx +++ b/e2e/react-router/basic-file-based/src/routes/posts_.$postId.edit.tsx @@ -1,15 +1,22 @@ -import { createFileRoute } from '@tanstack/react-router' -import { getRouteApi, useParams } from '@tanstack/react-router' +import { createFileRoute, getRouteApi, useParams } from '@tanstack/react-router' +import { useExperimentalNonNestedPaths } from '../../tests/utils/useExperimentalNonNestedPaths' export const Route = createFileRoute('/posts_/$postId/edit')({ component: PostEditPage, }) -const api = getRouteApi('/posts_/$postId/edit') +const api = getRouteApi( + // @ts-expect-error path is updated with new Experimental Non Nested Paths to not include the trailing underscore + `/${useExperimentalNonNestedPaths ? 'posts' : 'posts_'}/$postId/edit`, +) function PostEditPage() { const paramsViaApi = api.useParams() - const paramsViaHook = useParams({ from: '/posts_/$postId/edit' }) + const paramsViaHook = useParams({ + // @ts-expect-error path is updated with new Experimental Non Nested Paths to not include the trailing underscore + from: `/${useExperimentalNonNestedPaths ? 'posts' : 'posts_'}/$postId/edit`, + }) + const paramsViaRouteHook = Route.useParams() return ( diff --git a/e2e/react-router/basic-file-based/tests/non-nested-paths.spec.ts b/e2e/react-router/basic-file-based/tests/non-nested-paths.spec.ts index 2fdede5549a..1efc6d1e878 100644 --- a/e2e/react-router/basic-file-based/tests/non-nested-paths.spec.ts +++ b/e2e/react-router/basic-file-based/tests/non-nested-paths.spec.ts @@ -1,4 +1,5 @@ import { expect, test } from '@playwright/test' +import { useExperimentalNonNestedPaths } from './utils/useExperimentalNonNestedPaths' const testCases: Array<{ name: string @@ -184,7 +185,13 @@ test.describe('Non-nested paths', () => { await expect(pathRouteHeading).not.toBeVisible() await expect(barHeading).toBeVisible() const bar2ParamValue = await barParams.innerText() - expect(JSON.parse(bar2ParamValue)).toEqual(paramValue2) + if (useExperimentalNonNestedPaths || testPathDesc !== 'named') { + expect(JSON.parse(bar2ParamValue)).toEqual(paramValue2) + } else { + // this is a bug with named path params and non-nested paths + // that is resolved in the new experimental flag + expect(JSON.parse(bar2ParamValue)).toEqual(paramValue) + } }) }) }, diff --git a/e2e/react-router/basic-file-based/tests/params.spec.ts b/e2e/react-router/basic-file-based/tests/params.spec.ts index 76f32a935a8..98085768783 100644 --- a/e2e/react-router/basic-file-based/tests/params.spec.ts +++ b/e2e/react-router/basic-file-based/tests/params.spec.ts @@ -1,4 +1,5 @@ import { expect, test } from '@playwright/test' +import { useExperimentalNonNestedPaths } from './utils/useExperimentalNonNestedPaths' import type { Page } from '@playwright/test' test.beforeEach(async ({ page }) => { @@ -65,6 +66,8 @@ test.describe('params operations + non-nested routes', () => { const fooBarLink = page.getByTestId('l-to-non-nested-foo-bar') + const foo2Bar2Link = page.getByTestId('l-to-non-nested-foo2-bar2') + await expect(fooBarLink).toHaveAttribute( 'href', '/params-ps/non-nested/foo/bar', @@ -85,12 +88,11 @@ test.describe('params operations + non-nested routes', () => { const paramsObj = JSON.parse(paramsText) expect(paramsObj).toEqual({ foo: 'foo', bar: 'bar' }) - const foo2Bar2Link = page.getByTestId('l-to-non-nested-foo2-bar2') - await expect(foo2Bar2Link).toHaveAttribute( 'href', '/params-ps/non-nested/foo2/bar2', ) + await foo2Bar2Link.click() await page.waitForURL('/params-ps/non-nested/foo2/bar2') const pagePathname2 = new URL(page.url()).pathname @@ -99,12 +101,22 @@ test.describe('params operations + non-nested routes', () => { const foo2ParamsValue = page.getByTestId('foo-params-value') const foo2ParamsText = await foo2ParamsValue.innerText() const foo2ParamsObj = JSON.parse(foo2ParamsText) - expect(foo2ParamsObj).toEqual({ foo: 'foo2' }) + if (useExperimentalNonNestedPaths) { + expect(foo2ParamsObj).toEqual({ foo: 'foo2' }) + } else { + // this is a bug that is resolved in the new experimental flag + expect(foo2ParamsObj).toEqual({ foo: 'foo' }) + } const params2Value = page.getByTestId('foo-bar-params-value') const params2Text = await params2Value.innerText() const params2Obj = JSON.parse(params2Text) - expect(params2Obj).toEqual({ foo: 'foo2', bar: 'bar2' }) + if (useExperimentalNonNestedPaths) { + expect(params2Obj).toEqual({ foo: 'foo2', bar: 'bar2' }) + } else { + // this is a bug that is resolved in the new experimental flag + expect(params2Obj).toEqual({ foo: 'foo', bar: 'bar2' }) + } }) }) diff --git a/e2e/react-router/basic-file-based/tests/utils/useExperimentalNonNestedPaths.ts b/e2e/react-router/basic-file-based/tests/utils/useExperimentalNonNestedPaths.ts new file mode 100644 index 00000000000..94803d84f4d --- /dev/null +++ b/e2e/react-router/basic-file-based/tests/utils/useExperimentalNonNestedPaths.ts @@ -0,0 +1,6 @@ +export const useExperimentalNonNestedPaths = + typeof process !== 'undefined' + ? typeof process.env.MODE !== 'undefined' + ? process.env.MODE === 'nonnested' + : process.env.VITE_MODE === 'nonnested' + : import.meta.env.VITE_MODE === 'nonnested' diff --git a/e2e/react-router/basic-file-based/vite.config.js b/e2e/react-router/basic-file-based/vite.config.js index 172112dd72e..50a470ed014 100644 --- a/e2e/react-router/basic-file-based/vite.config.js +++ b/e2e/react-router/basic-file-based/vite.config.js @@ -4,5 +4,13 @@ import { tanstackRouter } from '@tanstack/router-plugin/vite' // https://vitejs.dev/config/ export default defineConfig({ - plugins: [tanstackRouter({ target: 'react' }), react()], + plugins: [ + tanstackRouter({ + target: 'react', + experimental: { + nonNestedPaths: process.env.MODE === 'nonnested', + }, + }), + react(), + ], }) From 329360d1881afcd14bb77803197619dc05976db2 Mon Sep 17 00:00:00 2001 From: Nico Lynzaad Date: Tue, 7 Oct 2025 11:17:40 +0200 Subject: [PATCH 05/25] cleanup --- .../basic-file-based/package.json | 2 +- .../basic-file-based/src/routeTree.gen.ts | 55 ++- .../src/filesystem/physical/getRouteNodes.ts | 16 +- packages/router-generator/src/generator.ts | 19 +- packages/router-generator/src/utils.ts | 182 ++++------ .../deny-route-group-config.nonnested.test.ts | 73 ---- .../tests/deny-route-group-config.test.ts | 24 +- .../tests/generator.nonnested.test.ts | 329 ------------------ .../router-generator/tests/generator.test.ts | 137 +++++--- packages/router-generator/tests/utils.test.ts | 167 ++++++++- 10 files changed, 396 insertions(+), 608 deletions(-) delete mode 100644 packages/router-generator/tests/deny-route-group-config.nonnested.test.ts delete mode 100644 packages/router-generator/tests/generator.nonnested.test.ts diff --git a/e2e/react-router/basic-file-based/package.json b/e2e/react-router/basic-file-based/package.json index 071ee248f34..f39ea4ed1c0 100644 --- a/e2e/react-router/basic-file-based/package.json +++ b/e2e/react-router/basic-file-based/package.json @@ -12,7 +12,7 @@ "serve:nonnested": "MODE=nonnested VITE_MODE=nonnested vite preview", "start": "vite", "start:nonnested": "MODE=nonnested VITE_MODE=nonnested vite", - "test:e2e": "pnpm run test:e2e:default && pnpm run test:e2e:nonnested", + "test:e2e": "pnpm run test:e2e:nonnested && pnpm run test:e2e:default", "test:e2e:default": "rm -rf port*.txt; playwright test --project=chromium", "test:e2e:nonnested": "rm -rf port*.txt; MODE=nonnested playwright test --project=chromium" }, diff --git a/e2e/react-router/basic-file-based/src/routeTree.gen.ts b/e2e/react-router/basic-file-based/src/routeTree.gen.ts index 561c8592537..5c4c85f21c7 100644 --- a/e2e/react-router/basic-file-based/src/routeTree.gen.ts +++ b/e2e/react-router/basic-file-based/src/routeTree.gen.ts @@ -8,6 +8,8 @@ // You should NOT make any changes in this file as it will be overwritten. // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. +import { createFileRoute } from '@tanstack/react-router' + import { Route as rootRouteImport } from './routes/__root' import { Route as Char45824Char54620Char48124Char44397RouteImport } from './routes/대한민국' import { Route as RemountDepsRouteImport } from './routes/remountDeps' @@ -97,6 +99,12 @@ import { Route as RelativeLinkPathPathIndexRouteImport } from './routes/relative import { Route as RelativeLinkNestedDeepIndexRouteImport } from './routes/relative/link/nested/deep/index' import { Route as ParamsPsNamedFooBarBazRouteImport } from './routes/params-ps/named/$foo/$bar.$baz' +const groupRouteImport = createFileRoute('/(group)')() + +const groupRoute = groupRouteImport.update({ + id: '/(group)', + getParentRoute: () => rootRouteImport, +} as any) const Char45824Char54620Char48124Char44397Route = Char45824Char54620Char48124Char44397RouteImport.update({ id: '/대한민국', @@ -564,7 +572,7 @@ const ParamsPsNamedFooBarBazRoute = ParamsPsNamedFooBarBazRouteImport.update({ } as any) export interface FileRoutesByFullPath { - '/': typeof IndexRoute + '/': typeof groupLayoutRouteWithChildren '/non-nested': typeof NonNestedRouteRouteWithChildren '/search-params': typeof SearchParamsRouteRouteWithChildren '/anchor': typeof AnchorRoute @@ -650,7 +658,7 @@ export interface FileRoutesByFullPath { '/relative/useNavigate/path/$path': typeof RelativeUseNavigatePathPathIndexRoute } export interface FileRoutesByTo { - '/': typeof IndexRoute + '/': typeof groupLayoutRouteWithChildren '/non-nested': typeof NonNestedRouteRouteWithChildren '/anchor': typeof AnchorRoute '/component-types-test': typeof ComponentTypesTestRoute @@ -750,6 +758,7 @@ export interface FileRoutesById { '/relative/link': typeof RelativeLinkRouteRouteWithChildren '/relative/useNavigate': typeof RelativeUseNavigateRouteRouteWithChildren '/(another-group)/onlyrouteinside': typeof anotherGroupOnlyrouteinsideRoute + '/(group)': typeof groupRouteWithChildren '/(group)/_layout': typeof groupLayoutRouteWithChildren '/(group)/inside': typeof groupInsideRoute '/(group)/lazyinside': typeof groupLazyinsideRoute @@ -1006,6 +1015,7 @@ export interface FileRouteTypes { | '/relative/link' | '/relative/useNavigate' | '/(another-group)/onlyrouteinside' + | '/(group)' | '/(group)/_layout' | '/(group)/inside' | '/(group)/lazyinside' @@ -1092,6 +1102,7 @@ export interface RootRouteChildren { RelativeLinkRouteRoute: typeof RelativeLinkRouteRouteWithChildren RelativeUseNavigateRouteRoute: typeof RelativeUseNavigateRouteRouteWithChildren anotherGroupOnlyrouteinsideRoute: typeof anotherGroupOnlyrouteinsideRoute + groupRoute: typeof groupRouteWithChildren RedirectTargetRoute: typeof RedirectTargetRouteWithChildren StructuralSharingEnabledRoute: typeof StructuralSharingEnabledRoute ParamsPsIndexRoute: typeof ParamsPsIndexRoute @@ -1114,6 +1125,13 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { + '/(group)': { + id: '/(group)' + path: '/' + fullPath: '/' + preLoaderRoute: typeof groupRouteImport + parentRoute: typeof rootRouteImport + } '/대한민국': { id: '/대한민국' path: '/대한민국' @@ -1284,8 +1302,8 @@ declare module '@tanstack/react-router' { } '/(group)/_layout': { id: '/(group)/_layout' - path: '' - fullPath: '' + path: '/' + fullPath: '/' preLoaderRoute: typeof groupLayoutRouteImport parentRoute: typeof groupRoute } @@ -1999,6 +2017,34 @@ const RelativeUseNavigateRouteRouteWithChildren = RelativeUseNavigateRouteRouteChildren, ) +interface groupLayoutRouteChildren { + groupLayoutInsidelayoutRoute: typeof groupLayoutInsidelayoutRoute +} + +const groupLayoutRouteChildren: groupLayoutRouteChildren = { + groupLayoutInsidelayoutRoute: groupLayoutInsidelayoutRoute, +} + +const groupLayoutRouteWithChildren = groupLayoutRoute._addFileChildren( + groupLayoutRouteChildren, +) + +interface groupRouteChildren { + groupLayoutRoute: typeof groupLayoutRouteWithChildren + groupInsideRoute: typeof groupInsideRoute + groupLazyinsideRoute: typeof groupLazyinsideRoute + groupSubfolderInsideRoute: typeof groupSubfolderInsideRoute +} + +const groupRouteChildren: groupRouteChildren = { + groupLayoutRoute: groupLayoutRouteWithChildren, + groupInsideRoute: groupInsideRoute, + groupLazyinsideRoute: groupLazyinsideRoute, + groupSubfolderInsideRoute: groupSubfolderInsideRoute, +} + +const groupRouteWithChildren = groupRoute._addFileChildren(groupRouteChildren) + interface RedirectTargetRouteChildren { RedirectTargetViaBeforeLoadRoute: typeof RedirectTargetViaBeforeLoadRoute RedirectTargetViaLoaderRoute: typeof RedirectTargetViaLoaderRoute @@ -2060,6 +2106,7 @@ const rootRouteChildren: RootRouteChildren = { RelativeLinkRouteRoute: RelativeLinkRouteRouteWithChildren, RelativeUseNavigateRouteRoute: RelativeUseNavigateRouteRouteWithChildren, anotherGroupOnlyrouteinsideRoute: anotherGroupOnlyrouteinsideRoute, + groupRoute: groupRouteWithChildren, RedirectTargetRoute: RedirectTargetRouteWithChildren, StructuralSharingEnabledRoute: StructuralSharingEnabledRoute, ParamsPsIndexRoute: ParamsPsIndexRoute, diff --git a/packages/router-generator/src/filesystem/physical/getRouteNodes.ts b/packages/router-generator/src/filesystem/physical/getRouteNodes.ts index 8bc3dad7f83..9360fceee28 100644 --- a/packages/router-generator/src/filesystem/physical/getRouteNodes.ts +++ b/packages/router-generator/src/filesystem/physical/getRouteNodes.ts @@ -134,7 +134,7 @@ export async function getRouteNodes( const filePathNoExt = removeExt(filePath) const { routePath: initialRoutePath, - cleanedRoutePath: originalRoutePath, + originalRoutePath, isExperimentalNonNestedPath, } = determineInitialRoutePath(filePathNoExt, config) let routePath = initialRoutePath @@ -149,11 +149,7 @@ export async function getRouteNodes( throw new Error(errorMessage) } - const meta = getRouteMeta( - routePath, - config, - isExperimentalNonNestedPath, - ) + const meta = getRouteMeta(routePath, config) const variableName = meta.variableName let routeType: FsRouteType = meta.fsRouteType @@ -232,13 +228,11 @@ export async function getRouteNodes( * * @param routePath - The determined initial routePath. * @param config - The user configuration object. - * @param isExperimentalNonNestedPath - true if route for which meta is generated is a non-nested path. * @returns An object containing the type of the route and the variable name derived from the route path. */ export function getRouteMeta( routePath: string, config: Pick, - isExperimentalNonNestedPath?: boolean, ): { // `__root` is can be more easily determined by filtering down to routePath === /${rootPathId} // `pathless` is needs to determined after `lazy` has been cleaned up from the routePath @@ -277,11 +271,7 @@ export function getRouteMeta( fsRouteType = 'errorComponent' } - const variableName = routePathToVariable( - routePath, - config as Config, - isExperimentalNonNestedPath, - ) + const variableName = routePathToVariable(routePath) return { fsRouteType, variableName } } diff --git a/packages/router-generator/src/generator.ts b/packages/router-generator/src/generator.ts index 3681ea30806..1d33176f6d6 100644 --- a/packages/router-generator/src/generator.ts +++ b/packages/router-generator/src/generator.ts @@ -33,6 +33,7 @@ import { removeGroups, removeLastSegmentFromPath, removeLayoutSegments, + removeLeadingUnderscores, removeUnderscores, replaceBackslash, resetRegex, @@ -1199,13 +1200,16 @@ ${acc.routeTree.map((child) => `${child.variableName}Route: typeof ${getResolved // Do not remove this as we need to set the lastIndex to 0 as it // is necessary to reset the regex's index when using the global flag // otherwise it might not match the next time it's used + const useExperimentalNonNestedPaths = + config?.experimental?.nonNestedPaths ?? false + resetRegex(this.routeGroupPatternRegex) let parentRoute = hasParentRoute( acc.routeNodes, node, node.routePath, - config?.experimental?.nonNestedPaths, + useExperimentalNonNestedPaths, node.originalRoutePath, ) @@ -1235,12 +1239,15 @@ ${acc.routeTree.map((child) => `${child.variableName}Route: typeof ${getResolved lastRouteSegment.startsWith('_') || split.every((part) => this.routeGroupPatternRegex.test(part)) + // with new nonNestedPaths feature we can be sure any remaining trailing underscores are escaped and should remain + // TODO with new major we can remove check and only remove leading underscores node.cleanedPath = removeGroups( - removeUnderscores( - removeLayoutSegments(node.path), - config, - node._isExperimentalNonNestedPath, - ) ?? '', + (useExperimentalNonNestedPaths + ? removeLeadingUnderscores( + removeLayoutSegments(node.path ?? ''), + config?.routeToken ?? '', + ) + : removeUnderscores(removeLayoutSegments(node.path))) ?? '', ) if ( diff --git a/packages/router-generator/src/utils.ts b/packages/router-generator/src/utils.ts index dde7e0384cb..992e68f7850 100644 --- a/packages/router-generator/src/utils.ts +++ b/packages/router-generator/src/utils.ts @@ -54,8 +54,6 @@ export function removeTrailingSlash(s: string) { const BRACKET_CONTENT_RE = /\[(.*?)\]/g -const BRACKET_CONTENT_RE_NON_NESTED = /\[([^\]]*[^_\]]|)\]/g - export function determineInitialRoutePath( routePath: string, config?: Pick, @@ -74,12 +72,41 @@ export function determineInitialRoutePath( '$', '%', ]) - const useExperimentalNonNestedPaths = - config?.experimental?.nonNestedPaths ?? false - const parts = routePath.split(/(? { // Check if any disallowed characters are used in brackets @@ -97,12 +124,6 @@ export function determineInitialRoutePath( } } - if (useExperimentalNonNestedPaths) { - // we don't want to escape any brackets yet that have a trailing underscore in its contents - // we do this lower down after we have identified the non-nested paths - return part.replace(BRACKET_CONTENT_RE_NON_NESTED, '$1') - } - // Since this split segment is safe at this point, we can // remove the brackets and replace them with the content inside return part.replace(BRACKET_CONTENT_RE, '$1') @@ -112,82 +133,20 @@ export function determineInitialRoutePath( // matching internals of router-core, we'd perform those changes here // on the `escapedParts` array before it is joined back together in // `final` - const cleanedRoutePath = cleanPath(`/${escapedParts.join('/')}`) || '' - - let finalRoutePath = cleanedRoutePath - - // check if the route path is a valid non-nested path, - const isExperimentalNonNestedPath = isValidNonNestedPath( - finalRoutePath, - config, - ) - - if (useExperimentalNonNestedPaths && config) { - // then clean up the trailing underscores and escape the remaining brackets - if (finalRoutePath !== `/${rootPathId}`) { - const routeTokenToExclude = - config.routeToken.slice(-1) === '_' - ? config.routeToken.slice(0, -1) - : config.routeToken - - // The regex finds either bracketed content or an underscore - const regex = new RegExp(`_(?=\\/)|(? { - // If the match is an underscore, remove it. - // Otherwise, the match was a bracketed section, so return it unchanged. - return match === '_' ? '' : match - }) - .replace(BRACKET_CONTENT_RE, '$1') - - finalRoutePath = cleanPath(finalEscapedPath) || '' - } - } + const finalRoutePath = cleanPath(`/${escapedParts.join('/')}`) || '' return { routePath: finalRoutePath, isExperimentalNonNestedPath, - cleanedRoutePath, + originalRoutePath, } } -export function escapeNonNestedPaths( - routePath: string, - config?: Pick, -) { - if (routePath === `/${rootPathId}`) return routePath - - // The regex finds either bracketed content or an underscore - const routeTokenToExclude = - config?.routeToken.slice(-1) === '_' - ? config.routeToken.slice(0, -1) - : config?.routeToken - - const regex = new RegExp(`_(?=\\/)|(? { - // If the match is an underscore, remove it. - // Otherwise, the match was a bracketed section, so return it unchanged. - return match === '_' ? '' : match - }) - .replace(BRACKET_CONTENT_RE, '$1') - - return cleanPath(cleanedRoutePath) || '' -} - export function replaceBackslash(s: string) { return s.replaceAll(/\\/gi, '/') } -export function routePathToVariable( - routePath: string, - config?: Pick, - isExperimentalNonNestedPath?: boolean, -): string { +export function routePathToVariable(routePath: string): string { const toVariableSafeChar = (char: string): string => { if (/[a-zA-Z0-9_]/.test(char)) { return char // Keep alphanumeric characters and underscores as is @@ -213,7 +172,7 @@ export function routePathToVariable( } return ( - removeUnderscores(routePath, config, isExperimentalNonNestedPath) + removeUnderscores(routePath) ?.replace(/\/\$\//g, '/splat/') .replace(/\$$/g, 'splat') .replace(/\$\{\$\}/g, 'splat') @@ -229,35 +188,40 @@ export function routePathToVariable( ) } -export function removeUnderscores( - s?: string, - config?: Pick, - isExperimentalNonNestedPath?: boolean, -) { - if (!s) return s - - if (!config?.experimental?.nonNestedPaths) { - return s.replaceAll(/(^_|_$)/gi, '').replaceAll(/(\/_|_\/)/gi, '/') - } +export function removeUnderscores(s?: string) { + return s?.replaceAll(/(^_|_$)/gi, '').replaceAll(/(\/_|_\/)/gi, '/') +} +export function removeLeadingUnderscores(s: string, routeToken: string) { const parts = s.split('/') if (parts.length === 0) return s - // Escape any remaining characters that are in square brackets - const escapedParts = parts.map((part) => { - // Since this split segment is safe at this point, we can - // remove the brackets and replace them with the content inside - if (!isExperimentalNonNestedPath && !s.endsWith(`/${config.routeToken}`)) { - return part.replaceAll(/(^_)/gi, '') - } + const routeTokenToExclude = + routeToken[0] === '_' ? routeToken.slice(1) : routeToken - return part.replaceAll(/(^_|_$)/gi, '') - }) + const leadingUnderscoreRegex = + routeToken[0] === '_' + ? new RegExp(`(?<=^|\\/)_(?!${routeTokenToExclude})`, 'g') + : new RegExp(`(?<=^|\\/)_`, 'g') + + return s.replaceAll(leadingUnderscoreRegex, '') +} - const final = cleanPath(`/${escapedParts.join('/')}`) || '' +export function removeTrailingUnderscores(s: string, routeToken: string) { + const parts = s.split('/') + + if (parts.length === 0) return s - return final + const routeTokenToExclude = + routeToken.slice(-1) === '_' ? routeToken.slice(0, -1) : routeToken + + const trailingUnderscoreRegex = + routeToken[0] === '_' + ? new RegExp(`(? d.variableName, ]).filter((d) => d.routePath !== `/${rootPathId}`) - const filteredNodes = - useExperimentalNonNestedPaths && node._isExperimentalNonNestedPath - ? [] - : [...sortedNodes] + const filteredNodes = node._isExperimentalNonNestedPath + ? [] + : [...sortedNodes] - if (useExperimentalNonNestedPaths && node._isExperimentalNonNestedPath) { + if (node._isExperimentalNonNestedPath) { const nonNestedSegments = getNonNestedSegments( originalRoutePathToCheck ?? '', ) @@ -521,12 +484,15 @@ export const inferFullPath = ( routeNode: RouteNode, config?: Pick, ): string => { + // with new nonNestedPaths feature we can be sure any remaining trailing underscores are escaped and should remain + // TODO with new major we can remove check and only remove leading underscores const fullPath = removeGroups( - removeUnderscores( - removeLayoutSegments(routeNode.routePath), - config, - routeNode._isExperimentalNonNestedPath, - ) ?? '', + (config?.experimental?.nonNestedPaths + ? removeLeadingUnderscores( + removeLayoutSegments(routeNode.routePath), + config.routeToken, + ) + : removeUnderscores(removeLayoutSegments(routeNode.routePath))) ?? '', ) return routeNode.cleanedPath === '/' ? fullPath : fullPath.replace(/\/$/, '') diff --git a/packages/router-generator/tests/deny-route-group-config.nonnested.test.ts b/packages/router-generator/tests/deny-route-group-config.nonnested.test.ts deleted file mode 100644 index fe5d9e1eec1..00000000000 --- a/packages/router-generator/tests/deny-route-group-config.nonnested.test.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { join } from 'node:path' -import { describe, expect, it } from 'vitest' - -import { Generator, getConfig } from '../src' -import type { Config } from '../src' - -function makeFolderDir(folder: string) { - return join(process.cwd(), 'tests', 'deny-route-group-config', folder) -} - -function setupConfig( - folder: string, - inlineConfig: Partial> = {}, -) { - const { generatedRouteTree = '/routeTree.nonnested.gen.ts', ...rest } = - inlineConfig - const dir = makeFolderDir(folder) - - const config = getConfig({ - disableLogging: true, - routesDirectory: dir, - generatedRouteTree: dir + generatedRouteTree, - experimental: { - nonNestedPaths: true, - }, - ...rest, - }) - return config -} - -type TestCases = Array<{ - folder: string - expectedError: string -}> - -describe('deny-route-group-config throws', () => { - it.each([ - { - folder: 'flat-flat', - expectedError: - 'A route configuration for a route group was found at `(group).tsx`. This is not supported. Did you mean to use a layout/pathless route instead?', - }, - { - folder: 'nested-flat', - expectedError: - 'A route configuration for a route group was found at `(group).tsx`. This is not supported. Did you mean to use a layout/pathless route instead?', - }, - { - folder: 'flat-nested', - expectedError: - 'A route configuration for a route group was found at `nested/(group).tsx`. This is not supported. Did you mean to use a layout/pathless route instead?', - }, - { - folder: 'nested-nested', - expectedError: - 'A route configuration for a route group was found at `nested/(group).tsx`. This is not supported. Did you mean to use a layout/pathless route instead?', - }, - ] satisfies TestCases)( - 'should throw an error for the folder: $folder', - async ({ folder, expectedError }) => { - const config = setupConfig(folder) - const folderRoot = makeFolderDir(folder) - - const generator = new Generator({ config, root: folderRoot }) - try { - await generator.run() - } catch (error) { - expect(error).toBeInstanceOf(Error) - expect((error as Error).message.startsWith(expectedError)).toBeTruthy() - } - }, - ) -}) diff --git a/packages/router-generator/tests/deny-route-group-config.test.ts b/packages/router-generator/tests/deny-route-group-config.test.ts index 0d5611bb28b..c2573c635d7 100644 --- a/packages/router-generator/tests/deny-route-group-config.test.ts +++ b/packages/router-generator/tests/deny-route-group-config.test.ts @@ -10,15 +10,22 @@ function makeFolderDir(folder: string) { function setupConfig( folder: string, + nonNested: boolean, inlineConfig: Partial> = {}, ) { - const { generatedRouteTree = '/routeTree.gen.ts', ...rest } = inlineConfig + const { + generatedRouteTree = `/routeTree.${nonNested ? 'nonnested.' : ''}gen.ts`, + ...rest + } = inlineConfig const dir = makeFolderDir(folder) const config = getConfig({ disableLogging: true, routesDirectory: dir, generatedRouteTree: dir + generatedRouteTree, + experimental: { + nonNestedPaths: nonNested, + }, ...rest, }) return config @@ -27,10 +34,11 @@ function setupConfig( type TestCases = Array<{ folder: string expectedError: string + nonNested: boolean }> describe('deny-route-group-config throws', () => { - it.each([ + const testCases = [ { folder: 'flat-flat', expectedError: @@ -51,10 +59,16 @@ describe('deny-route-group-config throws', () => { expectedError: 'A route configuration for a route group was found at `nested/(group).tsx`. This is not supported. Did you mean to use a layout/pathless route instead?', }, - ] satisfies TestCases)( + ].reduce((accum: TestCases, curr) => { + accum.push({ ...curr, nonNested: true }) + accum.push({ ...curr, nonNested: false }) + return accum + }, []) + + it.each(testCases)( 'should throw an error for the folder: $folder', - async ({ folder, expectedError }) => { - const config = setupConfig(folder) + async ({ folder, expectedError, nonNested }) => { + const config = setupConfig(folder, nonNested) const folderRoot = makeFolderDir(folder) const generator = new Generator({ config, root: folderRoot }) diff --git a/packages/router-generator/tests/generator.nonnested.test.ts b/packages/router-generator/tests/generator.nonnested.test.ts deleted file mode 100644 index d74f12e8fca..00000000000 --- a/packages/router-generator/tests/generator.nonnested.test.ts +++ /dev/null @@ -1,329 +0,0 @@ -import fs from 'node:fs/promises' -import { dirname, join, relative } from 'node:path' -import { describe, expect, it } from 'vitest' - -import { - index, - layout, - physical, - rootRoute, - route, -} from '@tanstack/virtual-file-routes' -import { Generator, getConfig } from '../src' -import type { Config } from '../src' - -function makeFolderDir(folder: string) { - return join(process.cwd(), 'tests', 'generator', folder) -} - -async function readDir(...paths: Array) { - const folders = await fs.readdir( - join(process.cwd(), 'tests', 'generator', ...paths), - ) - return folders -} - -async function traverseDirectory( - dir: string, - handleFile: (filePath: string) => void | Promise, -) { - const files = await fs.readdir(dir, { withFileTypes: true }) - - for (const file of files) { - const filePath = join(dir, file.name) - - if (file.isDirectory()) { - await traverseDirectory(filePath, handleFile) - } else { - await handleFile(filePath) - } - } -} - -function setupConfig( - folder: string, - inlineConfig: Partial> = {}, -) { - const { generatedRouteTree = '/routeTree.nonnested.gen.ts', ...rest } = - inlineConfig - const dir = makeFolderDir(folder) - - const config = getConfig({ - disableLogging: true, - routesDirectory: dir + '/routes', - generatedRouteTree: dir + generatedRouteTree, - experimental: { - nonNestedPaths: true, - }, - ...rest, - }) - return config -} - -async function getRouteTreeFileText(config: Config) { - const location = config.generatedRouteTree - const text = await fs.readFile(location, 'utf-8') - return text -} - -function rewriteConfigByFolderName(folderName: string, config: Config) { - switch (folderName) { - case 'append-and-prepend': - config.routeTreeFileHeader = ['// prepend1', '// prepend2'] - config.routeTreeFileFooter = ['// append1', '// append2'] - break - case 'no-formatted-route-tree': - config.enableRouteTreeFormatting = false - break - case 'custom-tokens': - config.indexToken = '_1nd3x' - config.routeToken = '_r0ut3_' - break - case 'virtual': - { - const virtualRouteConfig = rootRoute('root.tsx', [ - index('index.tsx'), - route('$lang', [index('pages.tsx')]), - layout('layout.tsx', [ - route('/dashboard', 'db/dashboard.tsx', [ - index('db/dashboard-index.tsx'), - route('/invoices', 'db/dashboard-invoices.tsx', [ - index('db/invoices-index.tsx'), - route('$id', 'db/invoice-detail.tsx'), - ]), - ]), - physical('/hello', 'subtree'), - ]), - ]) - config.virtualRouteConfig = virtualRouteConfig - } - break - case 'virtual-config-file-named-export': - config.virtualRouteConfig = './routes.ts' - break - case 'virtual-config-file-default-export': - config.virtualRouteConfig = './routes.ts' - break - case 'types-disabled': - config.disableTypes = true - config.generatedRouteTree = - makeFolderDir(folderName) + '/routeTree.nonnested.gen.js' - break - case 'custom-scaffolding': - config.customScaffolding = { - routeTemplate: [ - 'import * as React from "react";\n', - '%%tsrImports%%\n\n', - '%%tsrExportStart%%{\n component: RouteComponent\n }%%tsrExportEnd%%\n\n', - 'function RouteComponent() { return "Hello %%tsrPath%%!" };\n', - ].join(''), - lazyRouteTemplate: [ - 'import React, { useState } from "react";\n', - '%%tsrImports%%\n\n', - '%%tsrExportStart%%{\n component: RouteComponent\n }%%tsrExportEnd%%\n\n', - 'function RouteComponent() { return "Hello %%tsrPath%%!" };\n', - ].join(''), - } - break - case 'file-modification-verboseFileRoutes-true': - config.verboseFileRoutes = true - break - case 'file-modification-verboseFileRoutes-false': - config.verboseFileRoutes = false - break - // these two folders contain type tests which are executed separately - case 'nested-verboseFileRoutes-true': - config.verboseFileRoutes = true - break - case 'nested-verboseFileRoutes-false': - config.verboseFileRoutes = false - break - case 'routeFileIgnore': - config.routeFileIgnorePattern = 'ignoredPattern' - config.routeFileIgnorePrefix = 'imIgnored' - break - case 'routeFilePrefix': - config.routeFileIgnorePattern = 'ignoredPattern' - config.routeFilePrefix = 'r&' - break - default: - break - } -} - -async function preprocess(folderName: string) { - if (folderName.startsWith('file-modification')) { - const templateVerbosePath = join( - makeFolderDir(folderName), - 'template-verbose.tsx', - ) - const templatePath = join(makeFolderDir(folderName), 'template.tsx') - const lazyTemplatePath = join( - makeFolderDir(folderName), - 'template.lazy.tsx', - ) - - const makeRoutePath = (file: string) => - join(makeFolderDir(folderName), 'routes', '(test)', file) - const makeEmptyFile = async (file: string) => { - const fh = await fs.open(makeRoutePath(file), 'w') - await fh.close() - } - - await fs.copyFile(templateVerbosePath, makeRoutePath('foo.bar.tsx')) - await fs.copyFile(templatePath, makeRoutePath('foo.tsx')) - await fs.copyFile(lazyTemplatePath, makeRoutePath('initiallyLazy.tsx')) - await fs.copyFile(templatePath, makeRoutePath('bar.lazy.tsx')) - await makeEmptyFile('initiallyEmpty.tsx') - await makeEmptyFile('initiallyEmpty.lazy.tsx') - } else if (folderName === 'custom-scaffolding') { - const makeEmptyFile = async (...file: Array) => { - const filePath = join(makeFolderDir(folderName), 'routes', ...file) - const dir = dirname(filePath) - await fs.mkdir(dir, { recursive: true }) - const fh = await fs.open(filePath, 'w') - await fh.close() - } - - await makeEmptyFile('__root.tsx') - await makeEmptyFile('index.tsx') - await makeEmptyFile('foo.lazy.tsx') - await makeEmptyFile('api', 'bar.tsx') - } -} - -async function postprocess(folderName: string) { - switch (folderName) { - case 'file-modification-verboseFileRoutes-false': - case 'file-modification-verboseFileRoutes-true': { - const routeFiles = await readDir(folderName, 'routes', '(test)') - await Promise.all( - routeFiles - .filter((r) => r.endsWith('.tsx')) - .map(async (routeFile) => { - const text = await fs.readFile( - join(makeFolderDir(folderName), 'routes', '(test)', routeFile), - 'utf-8', - ) - await expect(text).toMatchFileSnapshot( - join('generator', folderName, 'snapshots', routeFile), - ) - }), - ) - break - } - case 'custom-scaffolding': { - const startDir = join(makeFolderDir(folderName), 'routes') - await traverseDirectory(startDir, async (filePath) => { - const relativePath = relative(startDir, filePath) - if (filePath.endsWith('.tsx')) { - await expect( - await fs.readFile(filePath, 'utf-8'), - ).toMatchFileSnapshot( - join('generator', folderName, 'snapshots', relativePath), - ) - } - }) - } - } -} - -function shouldThrow(folderName: string) { - if (folderName === 'duplicate-fullPath') { - return `Conflicting configuration paths were found for the following routes: "/", "/".` - } - return undefined -} - -describe('generator works', async () => { - const folderNames = await readDir() - - it.each(folderNames.map((folder) => [folder]))( - 'should wire-up the routes for a "%s" tree', - async (folderName) => { - const folderRoot = makeFolderDir(folderName) - - const config = await setupConfig(folderName) - - rewriteConfigByFolderName(folderName, config) - - await preprocess(folderName) - const generator = new Generator({ config, root: folderRoot }) - const error = shouldThrow(folderName) - if (error) { - try { - await generator.run() - } catch (e) { - expect(e).toBeInstanceOf(Error) - expect((e as Error).message.startsWith(error)).toBeTruthy() - } - } else { - await generator.run() - - const generatedRouteTree = await getRouteTreeFileText(config) - - let snapshotPath = `routeTree.nonnested.snapshot.${config.disableTypes ? 'js' : 'ts'}` - - await expect(generatedRouteTree).toMatchFileSnapshot( - join('generator', folderName, snapshotPath), - ) - } - - await postprocess(folderName) - }, - ) - - it('should create directory for routeTree if it does not exist', async () => { - const folderName = 'only-root' - const folderRoot = makeFolderDir(folderName) - await fs.rm(join(folderRoot, 'generated'), { recursive: true, force: true }) - let pathCreated = false - - const config = await setupConfig(folderName) - - rewriteConfigByFolderName(folderName, config) - - await preprocess(folderName) - - config.generatedRouteTree = join( - folderRoot, - 'generated', - '/routeTree.nonnested.gen.ts', - ) - const generator = new Generator({ config, root: folderRoot }) - const error = shouldThrow(folderName) - if (error) { - try { - await generator.run() - } catch (e) { - expect(e).toBeInstanceOf(Error) - expect((e as Error).message.startsWith(error)).toBeTruthy() - } - } else { - await generator.run() - - const generatedRouteTree = await getRouteTreeFileText(config) - - await expect(generatedRouteTree).toMatchFileSnapshot( - join( - 'generator', - folderName, - `routeTree.nonnested.generated.${config.disableTypes ? 'js' : 'ts'}`, - ), - ) - - pathCreated = await fs.access(dirname(config.generatedRouteTree)).then( - () => true, - () => false, - ) - - await expect(pathCreated).toBe(true) - } - - await postprocess(folderName) - - if (pathCreated) { - await fs.rm(dirname(config.generatedRouteTree), { recursive: true }) - } - }) -}) diff --git a/packages/router-generator/tests/generator.test.ts b/packages/router-generator/tests/generator.test.ts index 1c4d921f145..92f6cff0a6c 100644 --- a/packages/router-generator/tests/generator.test.ts +++ b/packages/router-generator/tests/generator.test.ts @@ -42,15 +42,22 @@ async function traverseDirectory( function setupConfig( folder: string, + nonNested: boolean, inlineConfig: Partial> = {}, ) { - const { generatedRouteTree = '/routeTree.gen.ts', ...rest } = inlineConfig + const { + generatedRouteTree = `/routeTree.${nonNested ? 'nonnested.' : ''}gen.ts`, + ...rest + } = inlineConfig const dir = makeFolderDir(folder) const config = getConfig({ disableLogging: true, routesDirectory: dir + '/routes', generatedRouteTree: dir + generatedRouteTree, + experimental: { + nonNestedPaths: nonNested, + }, ...rest, }) return config @@ -62,7 +69,11 @@ async function getRouteTreeFileText(config: Config) { return text } -function rewriteConfigByFolderName(folderName: string, config: Config) { +function rewriteConfigByFolderName( + folderName: string, + config: Config, + nonNested: boolean, +) { switch (folderName) { case 'append-and-prepend': config.routeTreeFileHeader = ['// prepend1', '// prepend2'] @@ -103,7 +114,8 @@ function rewriteConfigByFolderName(folderName: string, config: Config) { case 'types-disabled': config.disableTypes = true config.generatedRouteTree = - makeFolderDir(folderName) + '/routeTree.gen.js' + makeFolderDir(folderName) + + `/routeTree.${nonNested ? 'nonnested.' : ''}.gen.js` break case 'custom-scaffolding': config.customScaffolding = { @@ -234,14 +246,31 @@ function shouldThrow(folderName: string) { describe('generator works', async () => { const folderNames = await readDir() - it.each(folderNames.map((folder) => [folder]))( + const testCases = folderNames.reduce( + (accum: Array<{ folderName: string; nonNested: boolean }>, folderName) => { + accum.push({ + folderName, + nonNested: true, + }) + + accum.push({ + folderName, + nonNested: false, + }) + + return accum + }, + [], + ) + + it.each(testCases)( 'should wire-up the routes for a "%s" tree', - async (folderName) => { + async ({ folderName, nonNested }) => { const folderRoot = makeFolderDir(folderName) - const config = await setupConfig(folderName) + const config = await setupConfig(folderName, nonNested) - rewriteConfigByFolderName(folderName, config) + rewriteConfigByFolderName(folderName, config, nonNested) await preprocess(folderName) const generator = new Generator({ config, root: folderRoot }) @@ -258,12 +287,10 @@ describe('generator works', async () => { const generatedRouteTree = await getRouteTreeFileText(config) + const snapshotPath = `routeTree.${nonNested ? 'nonnested.' : ''}snapshot.${config.disableTypes ? 'js' : 'ts'}` + await expect(generatedRouteTree).toMatchFileSnapshot( - join( - 'generator', - folderName, - `routeTree.snapshot.${config.disableTypes ? 'js' : 'ts'}`, - ), + join('generator', folderName, snapshotPath), ) } @@ -271,55 +298,59 @@ describe('generator works', async () => { }, ) - it('should create directory for routeTree if it does not exist', async () => { - const folderName = 'only-root' - const folderRoot = makeFolderDir(folderName) - let pathCreated = false + it.each(testCases)( + 'should create directory for routeTree if it does not exist', + async ({ nonNested }) => { + const folderName = 'only-root' + const folderRoot = makeFolderDir(folderName) + let pathCreated = false - const config = await setupConfig(folderName) + const config = await setupConfig(folderName, nonNested) - rewriteConfigByFolderName(folderName, config) + rewriteConfigByFolderName(folderName, config, nonNested) - await preprocess(folderName) - config.generatedRouteTree = join( - folderRoot, - 'generated', - '/routeTree.gen.ts', - ) - const generator = new Generator({ config, root: folderRoot }) - const error = shouldThrow(folderName) - if (error) { - try { + await preprocess(folderName) + config.generatedRouteTree = join( + folderRoot, + 'generated', + `/routeTree.${nonNested ? 'nonnested.' : ''}gen.ts`, + ) + const generator = new Generator({ config, root: folderRoot }) + + const error = shouldThrow(folderName) + if (error) { + try { + await generator.run() + } catch (e) { + expect(e).toBeInstanceOf(Error) + expect((e as Error).message.startsWith(error)).toBeTruthy() + } + } else { await generator.run() - } catch (e) { - expect(e).toBeInstanceOf(Error) - expect((e as Error).message.startsWith(error)).toBeTruthy() - } - } else { - await generator.run() - const generatedRouteTree = await getRouteTreeFileText(config) + const generatedRouteTree = await getRouteTreeFileText(config) - await expect(generatedRouteTree).toMatchFileSnapshot( - join( - 'generator', - folderName, - `routeTree.generated.snapshot.${config.disableTypes ? 'js' : 'ts'}`, - ), - ) + await expect(generatedRouteTree).toMatchFileSnapshot( + join( + 'generator', + folderName, + `routeTree.${nonNested ? 'nonnested.' : ''}generated.snapshot.${config.disableTypes ? 'js' : 'ts'}`, + ), + ) - pathCreated = await fs.access(dirname(config.generatedRouteTree)).then( - () => true, - () => false, - ) + pathCreated = await fs.access(dirname(config.generatedRouteTree)).then( + () => true, + () => false, + ) - await expect(pathCreated).toBe(true) - } + await expect(pathCreated).toBe(true) + } - await postprocess(folderName) + await postprocess(folderName) - if (pathCreated) { - await fs.rm(dirname(config.generatedRouteTree), { recursive: true }) - } - }) + if (pathCreated) { + await fs.rm(dirname(config.generatedRouteTree), { recursive: true }) + } + }, + ) }) diff --git a/packages/router-generator/tests/utils.test.ts b/packages/router-generator/tests/utils.test.ts index 6376993aa91..7d03baddb4a 100644 --- a/packages/router-generator/tests/utils.test.ts +++ b/packages/router-generator/tests/utils.test.ts @@ -1,10 +1,12 @@ -import { describe, expect, it } from 'vitest' +import { describe, expect, it, vi } from 'vitest' import { cleanPath, determineInitialRoutePath, mergeImportDeclarations, multiSortBy, removeExt, + removeLeadingUnderscores, + removeTrailingUnderscores, removeUnderscores, routePathToVariable, } from '../src/utils' @@ -16,60 +18,156 @@ describe('cleanPath', () => { }) }) -describe('determineInitialRoutePath', () => { +describe.each([ + { nonNested: true, mode: 'experimental nonNestedPaths' }, + { nonNested: false, mode: 'default' }, +])('determineInitialRoutePath - $mode', ({ nonNested }) => { + const config = { + experimental: { + nonNestedPaths: nonNested, + }, + routeToken: 'route', + indexToken: 'index', + } + it('removes dots and adds slashes', () => { - expect(determineInitialRoutePath('test.test')).toStrictEqual({ + expect(determineInitialRoutePath('test.test', config)).toStrictEqual({ routePath: '/test/test', isExperimentalNonNestedPath: false, - cleanedRoutePath: '/test/test', + originalRoutePath: '/test/test', }) }) it('keeps leading slash', () => { - expect(determineInitialRoutePath('/test.test')).toStrictEqual({ + expect(determineInitialRoutePath('/test.test', config)).toStrictEqual({ routePath: '/test/test', isExperimentalNonNestedPath: false, - cleanedRoutePath: '/test/test', + originalRoutePath: '/test/test', }) }) it('keeps trailing slash', () => { - expect(determineInitialRoutePath('test.test/')).toStrictEqual({ + expect(determineInitialRoutePath('test.test/', config)).toStrictEqual({ routePath: '/test/test/', isExperimentalNonNestedPath: false, - cleanedRoutePath: '/test/test/', + originalRoutePath: '/test/test/', }) }) it('removes dots and adds slashes with leading and trailing slashes', () => { - expect(determineInitialRoutePath('/test.test/')).toStrictEqual({ + expect(determineInitialRoutePath('/test.test/', config)).toStrictEqual({ routePath: '/test/test/', isExperimentalNonNestedPath: false, - cleanedRoutePath: '/test/test/', + originalRoutePath: '/test/test/', }) }) it("returns '/' if path is empty", () => { - expect(determineInitialRoutePath('')).toStrictEqual({ + expect(determineInitialRoutePath('', config)).toStrictEqual({ routePath: '/', isExperimentalNonNestedPath: false, - cleanedRoutePath: '/', + originalRoutePath: '/', }) }) it("returns '/' if path is '.'", () => { - expect(determineInitialRoutePath('.')).toStrictEqual({ + expect(determineInitialRoutePath('.', config)).toStrictEqual({ routePath: '/', isExperimentalNonNestedPath: false, - cleanedRoutePath: '/', + originalRoutePath: '/', }) }) it("returns '/' if path is './'", () => { - expect(determineInitialRoutePath('./')).toStrictEqual({ + expect(determineInitialRoutePath('./', config)).toStrictEqual({ routePath: '/', isExperimentalNonNestedPath: false, - cleanedRoutePath: '/', + originalRoutePath: '/', + }) + }) + + it('errors on disallowed escaped character', () => { + const consoleSpy = vi.spyOn(console, 'error') + + expect(() => determineInitialRoutePath('/a[/]', config)).toThrowError() + + expect(consoleSpy).toBeCalledWith( + 'Error: Disallowed character "/" found in square brackets in route path "/a[/]".\n' + + 'You cannot use any of the following characters in square brackets: /, \\, ?, #, :, *, <, >, |, !, $, %\n' + + 'Please remove and/or replace them.', + ) + + consoleSpy.mockRestore() + }) + + it('escapes characters correctly', () => { + expect(determineInitialRoutePath('/a[.]', config)).toStrictEqual({ + routePath: '/a.', + isExperimentalNonNestedPath: false, + originalRoutePath: '/a[.]', + }) + + expect(determineInitialRoutePath('/a[_]', config)).toStrictEqual({ + routePath: '/a_', + isExperimentalNonNestedPath: false, + originalRoutePath: '/a[_]', + }) + }) + + // this is changed with experimental non-nested paths. + // currently trailing underscores are not removed + // with experimental non-nested paths this is removed to allow escaped '_' to be processed correctly later + it('should handle trailing underscores correctly', () => { + expect(determineInitialRoutePath('a_', config)).toStrictEqual({ + routePath: `/a${nonNested ? '' : '_'}`, + isExperimentalNonNestedPath: nonNested, + originalRoutePath: '/a_', + }) + + expect(determineInitialRoutePath('a_.route', config)).toStrictEqual({ + routePath: `/a${nonNested ? '' : '_'}/route`, + isExperimentalNonNestedPath: nonNested, + originalRoutePath: '/a_/route', + }) + + expect(determineInitialRoutePath('a_.b.c', config)).toStrictEqual({ + routePath: `/a${nonNested ? '' : '_'}/b/c`, + isExperimentalNonNestedPath: nonNested, + originalRoutePath: '/a_/b/c', + }) + + expect(determineInitialRoutePath('a.b_.c.d', config)).toStrictEqual({ + routePath: `/a/b${nonNested ? '' : '_'}/c/d`, + isExperimentalNonNestedPath: nonNested, + originalRoutePath: '/a/b_/c/d', + }) + + expect(determineInitialRoutePath('a_.route.b', config)).toStrictEqual({ + routePath: `/a${nonNested ? '' : '_'}/route/b`, + isExperimentalNonNestedPath: false, + originalRoutePath: '/a_/route/b', + }) + + expect( + determineInitialRoutePath('/a_/_route_/b_/c/d[_]', { + ...config, + routeToken: 'route', + }), + ).toStrictEqual({ + routePath: `/a${nonNested ? '' : '_'}/_route${nonNested ? '' : '_'}/b${nonNested ? '' : '_'}/c/d_`, + isExperimentalNonNestedPath: nonNested, + originalRoutePath: '/a_/_route_/b_/c/d[_]', + }) + + expect( + determineInitialRoutePath('/a_/_route_/b_/c/d[_]', { + ...config, + routeToken: '_route_', + }), + ).toStrictEqual({ + routePath: `/a${nonNested ? '' : '_'}/_route_/b${nonNested ? '' : '_'}/c/d_`, + isExperimentalNonNestedPath: nonNested, + originalRoutePath: '/a_/_route_/b_/c/d[_]', }) }) }) @@ -141,6 +239,42 @@ describe('removeUnderscores', () => { }) }) +describe('removeLeadingUnderscores', () => { + it('removes leading underscore when nonnested path', () => { + expect(removeLeadingUnderscores('_test', 'route')).toBe('test') + + expect(removeLeadingUnderscores('/_test/abc/route/_d', 'route')).toBe( + '/test/abc/route/d', + ) + + expect(removeLeadingUnderscores('/_test_/abc/_route_/d_/_e', 'route')).toBe( + '/test_/abc/route_/d_/e', + ) + + expect( + removeLeadingUnderscores('/_test_/abc/_route_/d_/_e', '_route_'), + ).toBe('/test_/abc/_route_/d_/e') + }) +}) + +describe('removeTrailingUnderscores', () => { + it('removes leading underscore when nonnested path', () => { + expect(removeTrailingUnderscores('test_', 'route')).toBe('test') + + expect(removeTrailingUnderscores('/_test_/abc_/route/_d', 'route')).toBe( + '/_test/abc/route/_d', + ) + + expect( + removeTrailingUnderscores('/_test_/abc/_route_/d_/_e', 'route'), + ).toBe('/_test/abc/_route/d/_e') + + expect( + removeTrailingUnderscores('/_test_/abc/_route_/d_/_e', '_route_'), + ).toBe('/_test/abc/_route_/d/_e') + }) +}) + describe('routePathToVariable', () => { it.each([ ['/test/$/index', 'TestSplatIndex'], @@ -149,6 +283,7 @@ describe('routePathToVariable', () => { ['/test/index', 'TestIndex'], ['/test', 'Test'], ['/test/', 'Test'], + ['/r0ut3', 'R0ut3'], ])(`converts "%s" to "%s"`, (routePath, expected) => { expect(routePathToVariable(routePath)).toBe(expected) }) From 2374d637d9771a28b553f282363f10b30e4b47dc Mon Sep 17 00:00:00 2001 From: Nico Lynzaad Date: Tue, 7 Oct 2025 11:33:32 +0200 Subject: [PATCH 06/25] cleanup --- .../src/routes/params-ps/non-nested/route.tsx | 5 +---- .../src/filesystem/physical/getRouteNodes.ts | 1 + packages/router-generator/src/utils.ts | 13 +++++++------ .../nested-verboseFileRoutes-false/tests.test-d.ts | 7 +++---- 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/e2e/react-router/basic-file-based/src/routes/params-ps/non-nested/route.tsx b/e2e/react-router/basic-file-based/src/routes/params-ps/non-nested/route.tsx index 67045ab460f..93fd9362182 100644 --- a/e2e/react-router/basic-file-based/src/routes/params-ps/non-nested/route.tsx +++ b/e2e/react-router/basic-file-based/src/routes/params-ps/non-nested/route.tsx @@ -8,16 +8,13 @@ function RouteComponent() { return (

      Non-nested path params

      -

      /params-ps/non-nested/route

      -
      -
        +
        • /params-ps/non-nested/foo/bar diff --git a/packages/router-generator/src/filesystem/physical/getRouteNodes.ts b/packages/router-generator/src/filesystem/physical/getRouteNodes.ts index 9360fceee28..a3275c18dc8 100644 --- a/packages/router-generator/src/filesystem/physical/getRouteNodes.ts +++ b/packages/router-generator/src/filesystem/physical/getRouteNodes.ts @@ -137,6 +137,7 @@ export async function getRouteNodes( originalRoutePath, isExperimentalNonNestedPath, } = determineInitialRoutePath(filePathNoExt, config) + let routePath = initialRoutePath if (routeFilePrefix) { diff --git a/packages/router-generator/src/utils.ts b/packages/router-generator/src/utils.ts index 992e68f7850..dcd704ad397 100644 --- a/packages/router-generator/src/utils.ts +++ b/packages/router-generator/src/utils.ts @@ -133,10 +133,11 @@ export function determineInitialRoutePath( // matching internals of router-core, we'd perform those changes here // on the `escapedParts` array before it is joined back together in // `final` - const finalRoutePath = cleanPath(`/${escapedParts.join('/')}`) || '' + + const final = cleanPath(`/${escapedParts.join('/')}`) || '' return { - routePath: finalRoutePath, + routePath: final, isExperimentalNonNestedPath, originalRoutePath, } @@ -829,13 +830,13 @@ function isValidNonNestedPath( } for (const segment of segments.slice(0, -1).reverse()) { - if (segment.endsWith('_') && segment !== config.routeToken) { - return true - } - if (segment === config.routeToken) { return false } + + if (segment.endsWith('_')) { + return true + } } return false diff --git a/packages/router-generator/tests/generator/nested-verboseFileRoutes-false/tests.test-d.ts b/packages/router-generator/tests/generator/nested-verboseFileRoutes-false/tests.test-d.ts index 877082339fc..a349a48c5e5 100644 --- a/packages/router-generator/tests/generator/nested-verboseFileRoutes-false/tests.test-d.ts +++ b/packages/router-generator/tests/generator/nested-verboseFileRoutes-false/tests.test-d.ts @@ -1,6 +1,7 @@ import { - Link, createRouter, + Link, + MakeRouteMatch, redirect, useLoaderData, useLoaderDeps, @@ -10,10 +11,8 @@ import { useRouteContext, useSearch, } from '@tanstack/react-router' -import { expectTypeOf, test } from 'vitest' +import { test, expectTypeOf } from 'vitest' import { routeTree } from './routeTree.gen' -import type { - MakeRouteMatch} from '@tanstack/react-router'; const defaultRouter = createRouter({ routeTree, From 1ce638a598cfc56ea426989e6dd4d00f3a654ed0 Mon Sep 17 00:00:00 2001 From: Nico Lynzaad Date: Tue, 7 Oct 2025 22:18:31 +0200 Subject: [PATCH 07/25] add tests for isValidNonNestedPaths --- packages/router-generator/src/utils.ts | 2 +- packages/router-generator/tests/utils.test.ts | 35 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/packages/router-generator/src/utils.ts b/packages/router-generator/src/utils.ts index dcd704ad397..1f6e1a7b903 100644 --- a/packages/router-generator/src/utils.ts +++ b/packages/router-generator/src/utils.ts @@ -800,7 +800,7 @@ export function getImportForRouteNode( * @param config The `router-generator` configuration object * @returns Boolean indicating if the route is a pathless layout route */ -function isValidNonNestedPath( +export function isValidNonNestedPath( normalizedRoutePath: string, config?: Pick, ): boolean { diff --git a/packages/router-generator/tests/utils.test.ts b/packages/router-generator/tests/utils.test.ts index 7d03baddb4a..38fb04b6abc 100644 --- a/packages/router-generator/tests/utils.test.ts +++ b/packages/router-generator/tests/utils.test.ts @@ -2,6 +2,7 @@ import { describe, expect, it, vi } from 'vitest' import { cleanPath, determineInitialRoutePath, + isValidNonNestedPath, mergeImportDeclarations, multiSortBy, removeExt, @@ -360,3 +361,37 @@ describe('mergeImportDeclarations', () => { ]) }) }) + +describe('isValidNonNestedPath', () => { + const config = { + experimental: { + nonNestedPaths: true, + }, + routeToken: 'route', + indexToken: 'index', + } + + it('should identify valid nested paths', () => { + expect(isValidNonNestedPath('/a_', config)).toBe(true) + expect(isValidNonNestedPath('/a/b_', config)).toBe(true) + expect(isValidNonNestedPath('/a_/route', config)).toBe(true) + expect(isValidNonNestedPath('/a/route/b_', config)).toBe(true) + expect(isValidNonNestedPath('/a_/b', config)).toBe(true) + }) + + it('should identify invalid nested paths', () => { + expect(isValidNonNestedPath('/a', config)).toBe(false) + expect(isValidNonNestedPath('/a/b', config)).toBe(false) + expect(isValidNonNestedPath('/a/route/false', config)).toBe(false) + expect(isValidNonNestedPath('/a_/route/b', config)).toBe(false) + }) + + it('should return false if not enabled', () => { + expect( + isValidNonNestedPath('/a_', { + ...config, + experimental: { nonNestedPaths: false }, + }), + ).toBe(false) + }) +}) From f0a12a6665809781445eb4a89d4146b05ba3a669 Mon Sep 17 00:00:00 2001 From: Nico Lynzaad Date: Tue, 7 Oct 2025 22:19:25 +0200 Subject: [PATCH 08/25] resolve eslint issues --- packages/router-generator/tests/utils.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/router-generator/tests/utils.test.ts b/packages/router-generator/tests/utils.test.ts index 38fb04b6abc..f8b47a285e0 100644 --- a/packages/router-generator/tests/utils.test.ts +++ b/packages/router-generator/tests/utils.test.ts @@ -292,7 +292,7 @@ describe('routePathToVariable', () => { describe('mergeImportDeclarations', () => { it('merges imports with the same source but different specifiers', () => { - const imports: ImportDeclaration[] = [ + const imports: Array = [ { source: 'moduleA', specifiers: [{ imported: 'A' }] }, { source: 'moduleA', specifiers: [{ imported: 'B' }] }, ] @@ -308,7 +308,7 @@ describe('mergeImportDeclarations', () => { }) it('merges imports with overlapping specifiers', () => { - const imports: ImportDeclaration[] = [ + const imports: Array = [ { source: 'moduleA', specifiers: [{ imported: 'A' }] }, { source: 'moduleA', specifiers: [{ imported: 'A' }, { imported: 'B' }] }, ] @@ -324,7 +324,7 @@ describe('mergeImportDeclarations', () => { }) it('does not merge imports with mixed import kinds for the same source', () => { - const imports: ImportDeclaration[] = [ + const imports: Array = [ { source: 'moduleA', importKind: 'type', @@ -346,7 +346,7 @@ describe('mergeImportDeclarations', () => { }) it('removes duplicate specifiers', () => { - const imports: ImportDeclaration[] = [ + const imports: Array = [ { source: 'moduleA', specifiers: [{ imported: 'A' }] }, { source: 'moduleA', specifiers: [{ imported: 'A' }] }, ] From 4fddb0c0cd1f4c897bfbcf19d8ac599974e5fa6b Mon Sep 17 00:00:00 2001 From: Nico Lynzaad Date: Tue, 7 Oct 2025 23:57:49 +0200 Subject: [PATCH 09/25] align naming to documentation --- .../basic-file-based/playwright.config.ts | 5 +- .../params-ps/non-nested/$foo_/$bar.tsx | 4 +- .../src/routes/posts_.$postId.edit.tsx | 6 +- .../tests/non-nested-paths.spec.ts | 4 +- .../basic-file-based/tests/params.spec.ts | 7 +- ...s.ts => useExperimentalNonNestedRoutes.ts} | 2 +- .../basic-file-based/vite.config.js | 2 +- packages/router-generator/src/config.ts | 2 +- .../src/filesystem/physical/getRouteNodes.ts | 4 +- packages/router-generator/src/generator.ts | 8 +-- packages/router-generator/src/types.ts | 2 +- packages/router-generator/src/utils.ts | 22 +++---- .../tests/deny-route-group-config.test.ts | 2 +- .../router-generator/tests/generator.test.ts | 2 +- packages/router-generator/tests/utils.test.ts | 66 +++++++++---------- 15 files changed, 69 insertions(+), 69 deletions(-) rename e2e/react-router/basic-file-based/tests/utils/{useExperimentalNonNestedPaths.ts => useExperimentalNonNestedRoutes.ts} (82%) diff --git a/e2e/react-router/basic-file-based/playwright.config.ts b/e2e/react-router/basic-file-based/playwright.config.ts index 718e5b30f81..1f24d09fb85 100644 --- a/e2e/react-router/basic-file-based/playwright.config.ts +++ b/e2e/react-router/basic-file-based/playwright.config.ts @@ -4,14 +4,14 @@ import { getTestServerPort, } from '@tanstack/router-e2e-utils' import packageJson from './package.json' with { type: 'json' } -import { useExperimentalNonNestedPaths } from './tests/utils/useExperimentalNonNestedPaths' +import { useExperimentalNonNestedRoutes } from './tests/utils/useExperimentalNonNestedRoutes' const PORT = await getTestServerPort(packageJson.name) const EXTERNAL_PORT = await getDummyServerPort(packageJson.name) const baseURL = `http://localhost:${PORT}` const experimentalNonNestedPathsModeCommand = `pnpm build:nonnested && pnpm serve:nonnested --port ${PORT}` const defaultCommand = `pnpm build && pnpm serve --port ${PORT}` -const command = useExperimentalNonNestedPaths +const command = useExperimentalNonNestedRoutes ? experimentalNonNestedPathsModeCommand : defaultCommand @@ -41,6 +41,7 @@ export default defineConfig({ stdout: 'pipe', env: { MODE: process.env.MODE || '', + VITE_MODE: process.env.MODE || '', VITE_NODE_ENV: 'test', VITE_EXTERNAL_PORT: String(EXTERNAL_PORT), VITE_SERVER_PORT: String(PORT), diff --git a/e2e/react-router/basic-file-based/src/routes/params-ps/non-nested/$foo_/$bar.tsx b/e2e/react-router/basic-file-based/src/routes/params-ps/non-nested/$foo_/$bar.tsx index 3a30f4b9c00..e9af3da4656 100644 --- a/e2e/react-router/basic-file-based/src/routes/params-ps/non-nested/$foo_/$bar.tsx +++ b/e2e/react-router/basic-file-based/src/routes/params-ps/non-nested/$foo_/$bar.tsx @@ -1,5 +1,5 @@ import { createFileRoute, useParams } from '@tanstack/react-router' -import { useExperimentalNonNestedPaths } from '../../../../../tests/utils/useExperimentalNonNestedPaths' +import { useExperimentalNonNestedRoutes } from '../../../../../tests/utils/useExperimentalNonNestedRoutes' export const Route = createFileRoute('/params-ps/non-nested/$foo_/$bar')({ component: RouteComponent, @@ -8,7 +8,7 @@ export const Route = createFileRoute('/params-ps/non-nested/$foo_/$bar')({ function RouteComponent() { const fooParams = useParams({ // @ts-expect-error path is updated with new Experimental Non Nested Paths to not include the trailing underscore - from: `/params-ps/non-nested/${useExperimentalNonNestedPaths ? '$foo' : '$foo_'}`, + from: `/params-ps/non-nested/${useExperimentalNonNestedRoutes ? '$foo' : '$foo_'}`, }) const routeParams = Route.useParams() diff --git a/e2e/react-router/basic-file-based/src/routes/posts_.$postId.edit.tsx b/e2e/react-router/basic-file-based/src/routes/posts_.$postId.edit.tsx index 345079caca4..b747dac6cd7 100644 --- a/e2e/react-router/basic-file-based/src/routes/posts_.$postId.edit.tsx +++ b/e2e/react-router/basic-file-based/src/routes/posts_.$postId.edit.tsx @@ -1,5 +1,5 @@ import { createFileRoute, getRouteApi, useParams } from '@tanstack/react-router' -import { useExperimentalNonNestedPaths } from '../../tests/utils/useExperimentalNonNestedPaths' +import { useExperimentalNonNestedRoutes } from '../../tests/utils/useExperimentalNonNestedRoutes' export const Route = createFileRoute('/posts_/$postId/edit')({ component: PostEditPage, @@ -7,14 +7,14 @@ export const Route = createFileRoute('/posts_/$postId/edit')({ const api = getRouteApi( // @ts-expect-error path is updated with new Experimental Non Nested Paths to not include the trailing underscore - `/${useExperimentalNonNestedPaths ? 'posts' : 'posts_'}/$postId/edit`, + `/${useExperimentalNonNestedRoutes ? 'posts' : 'posts_'}/$postId/edit`, ) function PostEditPage() { const paramsViaApi = api.useParams() const paramsViaHook = useParams({ // @ts-expect-error path is updated with new Experimental Non Nested Paths to not include the trailing underscore - from: `/${useExperimentalNonNestedPaths ? 'posts' : 'posts_'}/$postId/edit`, + from: `/${useExperimentalNonNestedRoutes ? 'posts' : 'posts_'}/$postId/edit`, }) const paramsViaRouteHook = Route.useParams() diff --git a/e2e/react-router/basic-file-based/tests/non-nested-paths.spec.ts b/e2e/react-router/basic-file-based/tests/non-nested-paths.spec.ts index 1efc6d1e878..9682eb84885 100644 --- a/e2e/react-router/basic-file-based/tests/non-nested-paths.spec.ts +++ b/e2e/react-router/basic-file-based/tests/non-nested-paths.spec.ts @@ -1,5 +1,5 @@ import { expect, test } from '@playwright/test' -import { useExperimentalNonNestedPaths } from './utils/useExperimentalNonNestedPaths' +import { useExperimentalNonNestedRoutes } from './utils/useExperimentalNonNestedRoutes' const testCases: Array<{ name: string @@ -185,7 +185,7 @@ test.describe('Non-nested paths', () => { await expect(pathRouteHeading).not.toBeVisible() await expect(barHeading).toBeVisible() const bar2ParamValue = await barParams.innerText() - if (useExperimentalNonNestedPaths || testPathDesc !== 'named') { + if (useExperimentalNonNestedRoutes || testPathDesc !== 'named') { expect(JSON.parse(bar2ParamValue)).toEqual(paramValue2) } else { // this is a bug with named path params and non-nested paths diff --git a/e2e/react-router/basic-file-based/tests/params.spec.ts b/e2e/react-router/basic-file-based/tests/params.spec.ts index 98085768783..2288f2573a1 100644 --- a/e2e/react-router/basic-file-based/tests/params.spec.ts +++ b/e2e/react-router/basic-file-based/tests/params.spec.ts @@ -1,5 +1,5 @@ import { expect, test } from '@playwright/test' -import { useExperimentalNonNestedPaths } from './utils/useExperimentalNonNestedPaths' +import { useExperimentalNonNestedRoutes } from './utils/useExperimentalNonNestedRoutes' import type { Page } from '@playwright/test' test.beforeEach(async ({ page }) => { @@ -92,7 +92,6 @@ test.describe('params operations + non-nested routes', () => { 'href', '/params-ps/non-nested/foo2/bar2', ) - await foo2Bar2Link.click() await page.waitForURL('/params-ps/non-nested/foo2/bar2') const pagePathname2 = new URL(page.url()).pathname @@ -101,7 +100,7 @@ test.describe('params operations + non-nested routes', () => { const foo2ParamsValue = page.getByTestId('foo-params-value') const foo2ParamsText = await foo2ParamsValue.innerText() const foo2ParamsObj = JSON.parse(foo2ParamsText) - if (useExperimentalNonNestedPaths) { + if (useExperimentalNonNestedRoutes) { expect(foo2ParamsObj).toEqual({ foo: 'foo2' }) } else { // this is a bug that is resolved in the new experimental flag @@ -111,7 +110,7 @@ test.describe('params operations + non-nested routes', () => { const params2Value = page.getByTestId('foo-bar-params-value') const params2Text = await params2Value.innerText() const params2Obj = JSON.parse(params2Text) - if (useExperimentalNonNestedPaths) { + if (useExperimentalNonNestedRoutes) { expect(params2Obj).toEqual({ foo: 'foo2', bar: 'bar2' }) } else { // this is a bug that is resolved in the new experimental flag diff --git a/e2e/react-router/basic-file-based/tests/utils/useExperimentalNonNestedPaths.ts b/e2e/react-router/basic-file-based/tests/utils/useExperimentalNonNestedRoutes.ts similarity index 82% rename from e2e/react-router/basic-file-based/tests/utils/useExperimentalNonNestedPaths.ts rename to e2e/react-router/basic-file-based/tests/utils/useExperimentalNonNestedRoutes.ts index 94803d84f4d..1aa7b0d8deb 100644 --- a/e2e/react-router/basic-file-based/tests/utils/useExperimentalNonNestedPaths.ts +++ b/e2e/react-router/basic-file-based/tests/utils/useExperimentalNonNestedRoutes.ts @@ -1,4 +1,4 @@ -export const useExperimentalNonNestedPaths = +export const useExperimentalNonNestedRoutes = typeof process !== 'undefined' ? typeof process.env.MODE !== 'undefined' ? process.env.MODE === 'nonnested' diff --git a/e2e/react-router/basic-file-based/vite.config.js b/e2e/react-router/basic-file-based/vite.config.js index 50a470ed014..e37b67cfb2c 100644 --- a/e2e/react-router/basic-file-based/vite.config.js +++ b/e2e/react-router/basic-file-based/vite.config.js @@ -8,7 +8,7 @@ export default defineConfig({ tanstackRouter({ target: 'react', experimental: { - nonNestedPaths: process.env.MODE === 'nonnested', + nonNestedRoutes: process.env.MODE === 'nonnested', }, }), react(), diff --git a/packages/router-generator/src/config.ts b/packages/router-generator/src/config.ts index 06922d84f70..5c663a5593c 100644 --- a/packages/router-generator/src/config.ts +++ b/packages/router-generator/src/config.ts @@ -55,7 +55,7 @@ export const configSchema = baseConfigSchema.extend({ // TODO: This has been made stable and is now "autoCodeSplitting". Remove in next major version. enableCodeSplitting: z.boolean().optional(), // TODO: This resolves issues with non-nested paths in file-based routing. To be made default in next major version. - nonNestedPaths: z.boolean().optional(), + nonNestedRoutes: z.boolean().optional(), }) .optional(), plugins: z.array(z.custom()).optional(), diff --git a/packages/router-generator/src/filesystem/physical/getRouteNodes.ts b/packages/router-generator/src/filesystem/physical/getRouteNodes.ts index a3275c18dc8..96d902c70f1 100644 --- a/packages/router-generator/src/filesystem/physical/getRouteNodes.ts +++ b/packages/router-generator/src/filesystem/physical/getRouteNodes.ts @@ -135,7 +135,7 @@ export async function getRouteNodes( const { routePath: initialRoutePath, originalRoutePath, - isExperimentalNonNestedPath, + isExperimentalNonNestedRoute, } = determineInitialRoutePath(filePathNoExt, config) let routePath = initialRoutePath @@ -199,7 +199,7 @@ export async function getRouteNodes( routePath, variableName, _fsRouteType: routeType, - _isExperimentalNonNestedPath: isExperimentalNonNestedPath, + _isExperimentalNonNestedRoute: isExperimentalNonNestedRoute, originalRoutePath, }) } diff --git a/packages/router-generator/src/generator.ts b/packages/router-generator/src/generator.ts index 1d33176f6d6..e2b438c9c93 100644 --- a/packages/router-generator/src/generator.ts +++ b/packages/router-generator/src/generator.ts @@ -1200,8 +1200,8 @@ ${acc.routeTree.map((child) => `${child.variableName}Route: typeof ${getResolved // Do not remove this as we need to set the lastIndex to 0 as it // is necessary to reset the regex's index when using the global flag // otherwise it might not match the next time it's used - const useExperimentalNonNestedPaths = - config?.experimental?.nonNestedPaths ?? false + const useExperimentalNonNestedRoutes = + config?.experimental?.nonNestedRoutes ?? false resetRegex(this.routeGroupPatternRegex) @@ -1209,7 +1209,7 @@ ${acc.routeTree.map((child) => `${child.variableName}Route: typeof ${getResolved acc.routeNodes, node, node.routePath, - useExperimentalNonNestedPaths, + useExperimentalNonNestedRoutes, node.originalRoutePath, ) @@ -1242,7 +1242,7 @@ ${acc.routeTree.map((child) => `${child.variableName}Route: typeof ${getResolved // with new nonNestedPaths feature we can be sure any remaining trailing underscores are escaped and should remain // TODO with new major we can remove check and only remove leading underscores node.cleanedPath = removeGroups( - (useExperimentalNonNestedPaths + (useExperimentalNonNestedRoutes ? removeLeadingUnderscores( removeLayoutSegments(node.path ?? ''), config?.routeToken ?? '', diff --git a/packages/router-generator/src/types.ts b/packages/router-generator/src/types.ts index d61f2cbd370..1b9a74b2540 100644 --- a/packages/router-generator/src/types.ts +++ b/packages/router-generator/src/types.ts @@ -14,7 +14,7 @@ export type RouteNode = { children?: Array parent?: RouteNode createFileRouteProps?: Set - _isExperimentalNonNestedPath?: boolean + _isExperimentalNonNestedRoute?: boolean } export interface GetRouteNodesResult { diff --git a/packages/router-generator/src/utils.ts b/packages/router-generator/src/utils.ts index 1f6e1a7b903..8cd7df51689 100644 --- a/packages/router-generator/src/utils.ts +++ b/packages/router-generator/src/utils.ts @@ -81,7 +81,7 @@ export function determineInitialRoutePath( // check if the route path is a valid non-nested path, // TODO with new major rename to reflect not experimental anymore - const isExperimentalNonNestedPath = isValidNonNestedPath( + const isExperimentalNonNestedRoute = isValidNonNestedRoute( originalRoutePath, config, ) @@ -92,7 +92,7 @@ export function determineInitialRoutePath( // we need to do this now before we encounter any escaped trailing underscores // this way we can be sure any remaining trailing underscores should remain // TODO with new major we can remove check and always remove leading underscores - if (config?.experimental?.nonNestedPaths) { + if (config?.experimental?.nonNestedRoutes) { // we should leave trailing underscores if the route path is the root path if (originalRoutePath !== `/${rootPathId}`) { // remove trailing underscores on various path segments @@ -138,7 +138,7 @@ export function determineInitialRoutePath( return { routePath: final, - isExperimentalNonNestedPath, + isExperimentalNonNestedRoute, originalRoutePath, } } @@ -357,7 +357,7 @@ export function hasParentRoute( routes: Array, node: RouteNode, routePathToCheck: string | undefined, - useExperimentalNonNestedPaths?: boolean, + useExperimentalNonNestedRoutes?: boolean, originalRoutePathToCheck?: string, ): RouteNode | null { const getNonNestedSegments = (routePath: string) => { @@ -383,11 +383,11 @@ export function hasParentRoute( (d) => d.variableName, ]).filter((d) => d.routePath !== `/${rootPathId}`) - const filteredNodes = node._isExperimentalNonNestedPath + const filteredNodes = node._isExperimentalNonNestedRoute ? [] : [...sortedNodes] - if (node._isExperimentalNonNestedPath) { + if (node._isExperimentalNonNestedRoute) { const nonNestedSegments = getNonNestedSegments( originalRoutePathToCheck ?? '', ) @@ -397,7 +397,7 @@ export function hasParentRoute( if ( routePathToCheck.startsWith(`${route.routePath}/`) && - route._isExperimentalNonNestedPath && + route._isExperimentalNonNestedRoute && route.routePath !== routePathToCheck ) { return route @@ -437,7 +437,7 @@ export function hasParentRoute( routes, node, parentRoutePath, - useExperimentalNonNestedPaths, + useExperimentalNonNestedRoutes, originalRoutePathToCheck, ) } @@ -488,7 +488,7 @@ export const inferFullPath = ( // with new nonNestedPaths feature we can be sure any remaining trailing underscores are escaped and should remain // TODO with new major we can remove check and only remove leading underscores const fullPath = removeGroups( - (config?.experimental?.nonNestedPaths + (config?.experimental?.nonNestedRoutes ? removeLeadingUnderscores( removeLayoutSegments(routeNode.routePath), config.routeToken, @@ -800,11 +800,11 @@ export function getImportForRouteNode( * @param config The `router-generator` configuration object * @returns Boolean indicating if the route is a pathless layout route */ -export function isValidNonNestedPath( +export function isValidNonNestedRoute( normalizedRoutePath: string, config?: Pick, ): boolean { - if (!config?.experimental?.nonNestedPaths) { + if (!config?.experimental?.nonNestedRoutes) { return false } diff --git a/packages/router-generator/tests/deny-route-group-config.test.ts b/packages/router-generator/tests/deny-route-group-config.test.ts index c2573c635d7..bb8329634dd 100644 --- a/packages/router-generator/tests/deny-route-group-config.test.ts +++ b/packages/router-generator/tests/deny-route-group-config.test.ts @@ -24,7 +24,7 @@ function setupConfig( routesDirectory: dir, generatedRouteTree: dir + generatedRouteTree, experimental: { - nonNestedPaths: nonNested, + nonNestedRoutes: nonNested, }, ...rest, }) diff --git a/packages/router-generator/tests/generator.test.ts b/packages/router-generator/tests/generator.test.ts index 92f6cff0a6c..82811c7989f 100644 --- a/packages/router-generator/tests/generator.test.ts +++ b/packages/router-generator/tests/generator.test.ts @@ -56,7 +56,7 @@ function setupConfig( routesDirectory: dir + '/routes', generatedRouteTree: dir + generatedRouteTree, experimental: { - nonNestedPaths: nonNested, + nonNestedRoutes: nonNested, }, ...rest, }) diff --git a/packages/router-generator/tests/utils.test.ts b/packages/router-generator/tests/utils.test.ts index f8b47a285e0..9e7cce04cdc 100644 --- a/packages/router-generator/tests/utils.test.ts +++ b/packages/router-generator/tests/utils.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it, vi } from 'vitest' import { cleanPath, determineInitialRoutePath, - isValidNonNestedPath, + isValidNonNestedRoute, mergeImportDeclarations, multiSortBy, removeExt, @@ -25,7 +25,7 @@ describe.each([ ])('determineInitialRoutePath - $mode', ({ nonNested }) => { const config = { experimental: { - nonNestedPaths: nonNested, + nonNestedRoutes: nonNested, }, routeToken: 'route', indexToken: 'index', @@ -34,7 +34,7 @@ describe.each([ it('removes dots and adds slashes', () => { expect(determineInitialRoutePath('test.test', config)).toStrictEqual({ routePath: '/test/test', - isExperimentalNonNestedPath: false, + isExperimentalNonNestedRoute: false, originalRoutePath: '/test/test', }) }) @@ -42,7 +42,7 @@ describe.each([ it('keeps leading slash', () => { expect(determineInitialRoutePath('/test.test', config)).toStrictEqual({ routePath: '/test/test', - isExperimentalNonNestedPath: false, + isExperimentalNonNestedRoute: false, originalRoutePath: '/test/test', }) }) @@ -50,7 +50,7 @@ describe.each([ it('keeps trailing slash', () => { expect(determineInitialRoutePath('test.test/', config)).toStrictEqual({ routePath: '/test/test/', - isExperimentalNonNestedPath: false, + isExperimentalNonNestedRoute: false, originalRoutePath: '/test/test/', }) }) @@ -58,7 +58,7 @@ describe.each([ it('removes dots and adds slashes with leading and trailing slashes', () => { expect(determineInitialRoutePath('/test.test/', config)).toStrictEqual({ routePath: '/test/test/', - isExperimentalNonNestedPath: false, + isExperimentalNonNestedRoute: false, originalRoutePath: '/test/test/', }) }) @@ -66,7 +66,7 @@ describe.each([ it("returns '/' if path is empty", () => { expect(determineInitialRoutePath('', config)).toStrictEqual({ routePath: '/', - isExperimentalNonNestedPath: false, + isExperimentalNonNestedRoute: false, originalRoutePath: '/', }) }) @@ -74,7 +74,7 @@ describe.each([ it("returns '/' if path is '.'", () => { expect(determineInitialRoutePath('.', config)).toStrictEqual({ routePath: '/', - isExperimentalNonNestedPath: false, + isExperimentalNonNestedRoute: false, originalRoutePath: '/', }) }) @@ -82,7 +82,7 @@ describe.each([ it("returns '/' if path is './'", () => { expect(determineInitialRoutePath('./', config)).toStrictEqual({ routePath: '/', - isExperimentalNonNestedPath: false, + isExperimentalNonNestedRoute: false, originalRoutePath: '/', }) }) @@ -104,13 +104,13 @@ describe.each([ it('escapes characters correctly', () => { expect(determineInitialRoutePath('/a[.]', config)).toStrictEqual({ routePath: '/a.', - isExperimentalNonNestedPath: false, + isExperimentalNonNestedRoute: false, originalRoutePath: '/a[.]', }) expect(determineInitialRoutePath('/a[_]', config)).toStrictEqual({ routePath: '/a_', - isExperimentalNonNestedPath: false, + isExperimentalNonNestedRoute: false, originalRoutePath: '/a[_]', }) }) @@ -121,31 +121,31 @@ describe.each([ it('should handle trailing underscores correctly', () => { expect(determineInitialRoutePath('a_', config)).toStrictEqual({ routePath: `/a${nonNested ? '' : '_'}`, - isExperimentalNonNestedPath: nonNested, + isExperimentalNonNestedRoute: nonNested, originalRoutePath: '/a_', }) expect(determineInitialRoutePath('a_.route', config)).toStrictEqual({ routePath: `/a${nonNested ? '' : '_'}/route`, - isExperimentalNonNestedPath: nonNested, + isExperimentalNonNestedRoute: nonNested, originalRoutePath: '/a_/route', }) expect(determineInitialRoutePath('a_.b.c', config)).toStrictEqual({ routePath: `/a${nonNested ? '' : '_'}/b/c`, - isExperimentalNonNestedPath: nonNested, + isExperimentalNonNestedRoute: nonNested, originalRoutePath: '/a_/b/c', }) expect(determineInitialRoutePath('a.b_.c.d', config)).toStrictEqual({ routePath: `/a/b${nonNested ? '' : '_'}/c/d`, - isExperimentalNonNestedPath: nonNested, + isExperimentalNonNestedRoute: nonNested, originalRoutePath: '/a/b_/c/d', }) expect(determineInitialRoutePath('a_.route.b', config)).toStrictEqual({ routePath: `/a${nonNested ? '' : '_'}/route/b`, - isExperimentalNonNestedPath: false, + isExperimentalNonNestedRoute: false, originalRoutePath: '/a_/route/b', }) @@ -156,7 +156,7 @@ describe.each([ }), ).toStrictEqual({ routePath: `/a${nonNested ? '' : '_'}/_route${nonNested ? '' : '_'}/b${nonNested ? '' : '_'}/c/d_`, - isExperimentalNonNestedPath: nonNested, + isExperimentalNonNestedRoute: nonNested, originalRoutePath: '/a_/_route_/b_/c/d[_]', }) @@ -167,7 +167,7 @@ describe.each([ }), ).toStrictEqual({ routePath: `/a${nonNested ? '' : '_'}/_route_/b${nonNested ? '' : '_'}/c/d_`, - isExperimentalNonNestedPath: nonNested, + isExperimentalNonNestedRoute: nonNested, originalRoutePath: '/a_/_route_/b_/c/d[_]', }) }) @@ -362,35 +362,35 @@ describe('mergeImportDeclarations', () => { }) }) -describe('isValidNonNestedPath', () => { +describe('isValidNonNestedRoute', () => { const config = { experimental: { - nonNestedPaths: true, + nonNestedRoutes: true, }, routeToken: 'route', indexToken: 'index', } - it('should identify valid nested paths', () => { - expect(isValidNonNestedPath('/a_', config)).toBe(true) - expect(isValidNonNestedPath('/a/b_', config)).toBe(true) - expect(isValidNonNestedPath('/a_/route', config)).toBe(true) - expect(isValidNonNestedPath('/a/route/b_', config)).toBe(true) - expect(isValidNonNestedPath('/a_/b', config)).toBe(true) + it('should identify valid non-nested routes', () => { + expect(isValidNonNestedRoute('/a_', config)).toBe(true) + expect(isValidNonNestedRoute('/a/b_', config)).toBe(true) + expect(isValidNonNestedRoute('/a_/route', config)).toBe(true) + expect(isValidNonNestedRoute('/a/route/b_', config)).toBe(true) + expect(isValidNonNestedRoute('/a_/b', config)).toBe(true) }) - it('should identify invalid nested paths', () => { - expect(isValidNonNestedPath('/a', config)).toBe(false) - expect(isValidNonNestedPath('/a/b', config)).toBe(false) - expect(isValidNonNestedPath('/a/route/false', config)).toBe(false) - expect(isValidNonNestedPath('/a_/route/b', config)).toBe(false) + it('should identify invalid non-nested routes', () => { + expect(isValidNonNestedRoute('/a', config)).toBe(false) + expect(isValidNonNestedRoute('/a/b', config)).toBe(false) + expect(isValidNonNestedRoute('/a/route/false', config)).toBe(false) + expect(isValidNonNestedRoute('/a_/route/b', config)).toBe(false) }) it('should return false if not enabled', () => { expect( - isValidNonNestedPath('/a_', { + isValidNonNestedRoute('/a_', { ...config, - experimental: { nonNestedPaths: false }, + experimental: { nonNestedRoutes: false }, }), ).toBe(false) }) From a1a2ba617e8ab2d8c49ba3ddcbfcb9b473c71493 Mon Sep 17 00:00:00 2001 From: Nico Lynzaad Date: Tue, 7 Oct 2025 23:58:03 +0200 Subject: [PATCH 10/25] cleanup --- packages/router-generator/vite.config.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/router-generator/vite.config.ts b/packages/router-generator/vite.config.ts index 1474004cd45..5389f0f7391 100644 --- a/packages/router-generator/vite.config.ts +++ b/packages/router-generator/vite.config.ts @@ -7,10 +7,7 @@ const config = defineConfig({ name: packageJson.name, dir: './tests', watch: false, - typecheck: { - enabled: true, - exclude: ['./tests/generator/types-disabled/**/*.tsx'], - }, + typecheck: { enabled: true }, }, }) From ebb74d5e2bf0ad751c61c35399a18d344e15060f Mon Sep 17 00:00:00 2001 From: Nico Lynzaad Date: Tue, 7 Oct 2025 23:58:40 +0200 Subject: [PATCH 11/25] update solidjs basic-file-based tests --- .../basic-file-based/package.json | 8 +++++++- .../basic-file-based/playwright.config.ts | 19 ++++++++++++++++++- .../params-ps/non-nested/$foo_/$bar.tsx | 6 +++++- .../src/routes/posts_.$postId.edit.tsx | 9 ++++++--- .../tests/non-nested-paths.spec.ts | 9 ++++++++- .../basic-file-based/tests/params.spec.ts | 19 +++++++++++++++---- .../utils/useExperimentalNonNestedRoutes.ts | 6 ++++++ .../basic-file-based/vite.config.js | 10 +++++++++- 8 files changed, 74 insertions(+), 12 deletions(-) create mode 100644 e2e/solid-router/basic-file-based/tests/utils/useExperimentalNonNestedRoutes.ts diff --git a/e2e/solid-router/basic-file-based/package.json b/e2e/solid-router/basic-file-based/package.json index 16e33d2c6c3..3221d80c3fc 100644 --- a/e2e/solid-router/basic-file-based/package.json +++ b/e2e/solid-router/basic-file-based/package.json @@ -4,11 +4,17 @@ "type": "module", "scripts": { "dev": "vite --port 3000", + "dev:nonnested": "MODE=nonnested VITE_MODE=nonnested vite --port 3000", "dev:e2e": "vite", "build": "vite build && tsc --noEmit", + "build:nonnested": "MODE=nonnested VITE_MODE=nonnested vite build && tsc --noEmit", "serve": "vite preview", + "serve:nonnested": "MODE=nonnested VITE_MODE=nonnested vite preview", "start": "vite", - "test:e2e": "rm -rf port*.txt; playwright test --project=chromium" + "start:nonnested": "MODE=nonnested VITE_MODE=nonnested vite", + "test:e2e": "pnpm run test:e2e:nonnested && pnpm run test:e2e:default", + "test:e2e:default": "rm -rf port*.txt; playwright test --project=chromium", + "test:e2e:nonnested": "rm -rf port*.txt; MODE=nonnested playwright test --project=chromium" }, "dependencies": { "@tanstack/solid-router": "workspace:^", diff --git a/e2e/solid-router/basic-file-based/playwright.config.ts b/e2e/solid-router/basic-file-based/playwright.config.ts index 67ab8104e22..1f24d09fb85 100644 --- a/e2e/solid-router/basic-file-based/playwright.config.ts +++ b/e2e/solid-router/basic-file-based/playwright.config.ts @@ -4,10 +4,19 @@ import { getTestServerPort, } from '@tanstack/router-e2e-utils' import packageJson from './package.json' with { type: 'json' } +import { useExperimentalNonNestedRoutes } from './tests/utils/useExperimentalNonNestedRoutes' const PORT = await getTestServerPort(packageJson.name) const EXTERNAL_PORT = await getDummyServerPort(packageJson.name) const baseURL = `http://localhost:${PORT}` +const experimentalNonNestedPathsModeCommand = `pnpm build:nonnested && pnpm serve:nonnested --port ${PORT}` +const defaultCommand = `pnpm build && pnpm serve --port ${PORT}` +const command = useExperimentalNonNestedRoutes + ? experimentalNonNestedPathsModeCommand + : defaultCommand + +console.info('Running with mode: ', process.env.MODE || 'default') + /** * See https://playwright.dev/docs/test-configuration. */ @@ -26,10 +35,18 @@ export default defineConfig({ }, webServer: { - command: `VITE_NODE_ENV="test" VITE_EXTERNAL_PORT=${EXTERNAL_PORT} VITE_SERVER_PORT=${PORT} pnpm build && VITE_NODE_ENV="test" VITE_EXTERNAL_PORT=${EXTERNAL_PORT} VITE_SERVER_PORT=${PORT} pnpm serve --port ${PORT}`, + command, url: baseURL, reuseExistingServer: !process.env.CI, stdout: 'pipe', + env: { + MODE: process.env.MODE || '', + VITE_MODE: process.env.MODE || '', + VITE_NODE_ENV: 'test', + VITE_EXTERNAL_PORT: String(EXTERNAL_PORT), + VITE_SERVER_PORT: String(PORT), + PORT: String(PORT), + }, }, projects: [ diff --git a/e2e/solid-router/basic-file-based/src/routes/params-ps/non-nested/$foo_/$bar.tsx b/e2e/solid-router/basic-file-based/src/routes/params-ps/non-nested/$foo_/$bar.tsx index 837aa937c2d..221716f9836 100644 --- a/e2e/solid-router/basic-file-based/src/routes/params-ps/non-nested/$foo_/$bar.tsx +++ b/e2e/solid-router/basic-file-based/src/routes/params-ps/non-nested/$foo_/$bar.tsx @@ -1,11 +1,15 @@ import { createFileRoute, useParams } from '@tanstack/solid-router' +import { useExperimentalNonNestedRoutes } from '../../../../../tests/utils/useExperimentalNonNestedRoutes' export const Route = createFileRoute('/params-ps/non-nested/$foo_/$bar')({ component: RouteComponent, }) function RouteComponent() { - const fooParams = useParams({ from: '/params-ps/non-nested/$foo_' }) + const fooParams = useParams({ + // @ts-expect-error path is updated with new Experimental Non Nested Paths to not include the trailing underscore + from: `/params-ps/non-nested/${useExperimentalNonNestedRoutes ? '$foo' : '$foo_'}`, + }) const routeParams = Route.useParams() return ( diff --git a/e2e/solid-router/basic-file-based/src/routes/posts_.$postId.edit.tsx b/e2e/solid-router/basic-file-based/src/routes/posts_.$postId.edit.tsx index a1a0cc0b7a7..a5716ae455f 100644 --- a/e2e/solid-router/basic-file-based/src/routes/posts_.$postId.edit.tsx +++ b/e2e/solid-router/basic-file-based/src/routes/posts_.$postId.edit.tsx @@ -1,11 +1,14 @@ -import { createFileRoute } from '@tanstack/solid-router' -import { getRouteApi, useParams } from '@tanstack/solid-router' +import { createFileRoute, getRouteApi, useParams } from '@tanstack/solid-router' +import { useExperimentalNonNestedRoutes } from '../../tests/utils/useExperimentalNonNestedRoutes' export const Route = createFileRoute('/posts_/$postId/edit')({ component: PostEditPage, }) -const api = getRouteApi('/posts_/$postId/edit') +const api = getRouteApi( + // @ts-expect-error path is updated with new Experimental Non Nested Paths to not include the trailing underscore + `/${useExperimentalNonNestedRoutes ? 'posts' : 'posts_'}/$postId/edit`, +) function PostEditPage() { const paramsViaApi = api.useParams() diff --git a/e2e/solid-router/basic-file-based/tests/non-nested-paths.spec.ts b/e2e/solid-router/basic-file-based/tests/non-nested-paths.spec.ts index 2fdede5549a..9682eb84885 100644 --- a/e2e/solid-router/basic-file-based/tests/non-nested-paths.spec.ts +++ b/e2e/solid-router/basic-file-based/tests/non-nested-paths.spec.ts @@ -1,4 +1,5 @@ import { expect, test } from '@playwright/test' +import { useExperimentalNonNestedRoutes } from './utils/useExperimentalNonNestedRoutes' const testCases: Array<{ name: string @@ -184,7 +185,13 @@ test.describe('Non-nested paths', () => { await expect(pathRouteHeading).not.toBeVisible() await expect(barHeading).toBeVisible() const bar2ParamValue = await barParams.innerText() - expect(JSON.parse(bar2ParamValue)).toEqual(paramValue2) + if (useExperimentalNonNestedRoutes || testPathDesc !== 'named') { + expect(JSON.parse(bar2ParamValue)).toEqual(paramValue2) + } else { + // this is a bug with named path params and non-nested paths + // that is resolved in the new experimental flag + expect(JSON.parse(bar2ParamValue)).toEqual(paramValue) + } }) }) }, diff --git a/e2e/solid-router/basic-file-based/tests/params.spec.ts b/e2e/solid-router/basic-file-based/tests/params.spec.ts index e85cd0c6b01..c6ee258a85d 100644 --- a/e2e/solid-router/basic-file-based/tests/params.spec.ts +++ b/e2e/solid-router/basic-file-based/tests/params.spec.ts @@ -1,4 +1,5 @@ import { expect, test } from '@playwright/test' +import { useExperimentalNonNestedRoutes } from './utils/useExperimentalNonNestedRoutes' import type { Page } from '@playwright/test' test.beforeEach(async ({ page }) => { @@ -140,6 +141,8 @@ test.describe('params operations + non-nested routes', () => { const fooBarLink = page.getByTestId('l-to-non-nested-foo-bar') + const foo2Bar2Link = page.getByTestId('l-to-non-nested-foo2-bar2') + await expect(fooBarLink).toHaveAttribute( 'href', '/params-ps/non-nested/foo/bar', @@ -160,8 +163,6 @@ test.describe('params operations + non-nested routes', () => { const paramsObj = JSON.parse(paramsText) expect(paramsObj).toEqual({ foo: 'foo', bar: 'bar' }) - const foo2Bar2Link = page.getByTestId('l-to-non-nested-foo2-bar2') - await expect(foo2Bar2Link).toHaveAttribute( 'href', '/params-ps/non-nested/foo2/bar2', @@ -174,11 +175,21 @@ test.describe('params operations + non-nested routes', () => { const foo2ParamsValue = page.getByTestId('foo-params-value') const foo2ParamsText = await foo2ParamsValue.innerText() const foo2ParamsObj = JSON.parse(foo2ParamsText) - expect(foo2ParamsObj).toEqual({ foo: 'foo2' }) + if (useExperimentalNonNestedRoutes) { + expect(foo2ParamsObj).toEqual({ foo: 'foo2' }) + } else { + // this is a bug that is resolved in the new experimental flag + expect(foo2ParamsObj).toEqual({ foo: 'foo' }) + } const params2Value = page.getByTestId('foo-bar-params-value') const params2Text = await params2Value.innerText() const params2Obj = JSON.parse(params2Text) - expect(params2Obj).toEqual({ foo: 'foo2', bar: 'bar2' }) + if (useExperimentalNonNestedRoutes) { + expect(params2Obj).toEqual({ foo: 'foo2', bar: 'bar2' }) + } else { + // this is a bug that is resolved in the new experimental flag + expect(params2Obj).toEqual({ foo: 'foo', bar: 'bar2' }) + } }) }) diff --git a/e2e/solid-router/basic-file-based/tests/utils/useExperimentalNonNestedRoutes.ts b/e2e/solid-router/basic-file-based/tests/utils/useExperimentalNonNestedRoutes.ts new file mode 100644 index 00000000000..1aa7b0d8deb --- /dev/null +++ b/e2e/solid-router/basic-file-based/tests/utils/useExperimentalNonNestedRoutes.ts @@ -0,0 +1,6 @@ +export const useExperimentalNonNestedRoutes = + typeof process !== 'undefined' + ? typeof process.env.MODE !== 'undefined' + ? process.env.MODE === 'nonnested' + : process.env.VITE_MODE === 'nonnested' + : import.meta.env.VITE_MODE === 'nonnested' diff --git a/e2e/solid-router/basic-file-based/vite.config.js b/e2e/solid-router/basic-file-based/vite.config.js index 1fc0fce40af..fa9b80e95d5 100644 --- a/e2e/solid-router/basic-file-based/vite.config.js +++ b/e2e/solid-router/basic-file-based/vite.config.js @@ -4,5 +4,13 @@ import { tanstackRouter } from '@tanstack/router-plugin/vite' // https://vitejs.dev/config/ export default defineConfig({ - plugins: [tanstackRouter({ target: 'solid' }), solid()], + plugins: [ + tanstackRouter({ + target: 'solid', + experimental: { + nonNestedRoutes: process.env.MODE === 'nonnested', + }, + }), + solid(), + ], }) From 7d8d8d5cdb047d982ec682d2c91e8d18fdbd4fdc Mon Sep 17 00:00:00 2001 From: Nico Lynzaad Date: Wed, 8 Oct 2025 00:01:24 +0200 Subject: [PATCH 12/25] update documentation --- .../react/routing/file-naming-conventions.md | 1 + .../react/routing/routing-concepts.md | 32 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/docs/router/framework/react/routing/file-naming-conventions.md b/docs/router/framework/react/routing/file-naming-conventions.md index 8c7412b5b82..f72e45f361d 100644 --- a/docs/router/framework/react/routing/file-naming-conventions.md +++ b/docs/router/framework/react/routing/file-naming-conventions.md @@ -19,6 +19,7 @@ File-based routing requires that you follow a few simple file naming conventions > **💡 Remember:** The file-naming conventions for your project could be affected by what [options](../../../../api/file-based-routing.md) are configured. +> [!NOTE] To escape a trailing underscore, for example `/posts[_].tsx`, usage of the upgraded [Non-Nested Routes](../routing-concepts#non-nested-routes) is required. ## Dynamic Path Params Dynamic path params can be used in both flat and directory routes to create routes that can match a dynamic segment of the URL path. Dynamic path params are denoted by the `$` character in the filename: diff --git a/docs/router/framework/react/routing/routing-concepts.md b/docs/router/framework/react/routing/routing-concepts.md index 406c736771c..2203dbb8051 100644 --- a/docs/router/framework/react/routing/routing-concepts.md +++ b/docs/router/framework/react/routing/routing-concepts.md @@ -352,6 +352,38 @@ The following table shows which component will be rendered based on the URL: - The `posts.$postId.tsx` route is nested as normal under the `posts.tsx` route and will render ``. - The `posts_.$postId.edit.tsx` route **does not share** the same `posts` prefix as the other routes and therefore will be treated as if it is a top-level route and will render ``. +> [!NOTE] +> While using non-nested routes with file-based routing already works brilliantly, it might misbehave in certain conditions. +> Many of these limitations have already been addressed and will be released in the next major version of TanStack Router. +> +> To start enjoying these benefits early, you can enable the experimental `nonNestedRoutes` flag in the router plugin configuration: +> +> ```ts +> export default defineConfig({ +> plugins: [ +> tanstackRouter({ +> // some config, +> experimental: { +> nonNestedRoutes: true, +> }, +> }), +> ], +> }) +> ``` +> +> *It is important to note that this does bring a slight change in how non-nested routes are referenced in useParams, useNavigate, etc. For this reason this has been released as a feature flag. +> The trailing underscore is no longer expected in the path:* +> +> Previously: +> ```ts +> useParams({from: '/posts_/$postId/edit'}) +> ``` +> +> Now: +> ```ts +> useParams({from: '/posts/$postId/edit'}) +> ``` + ## Excluding Files and Folders from Routes Files and folders can be excluded from route generation with a `-` prefix attached to the file name. This gives you the ability to colocate logic in the route directories. From 1365c2151e9fc164292e01ae47849ee523b10f87 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 7 Oct 2025 22:52:18 +0000 Subject: [PATCH 13/25] ci: apply automated fixes --- .../react/routing/file-naming-conventions.md | 1 + .../react/routing/routing-concepts.md | 34 +++---- .../basic-file-based/package.json | 12 +-- .../basic-file-based/package.json | 14 +-- .../routeTree.nonnested.snapshot.ts | 96 +++++++++++-------- 5 files changed, 89 insertions(+), 68 deletions(-) diff --git a/docs/router/framework/react/routing/file-naming-conventions.md b/docs/router/framework/react/routing/file-naming-conventions.md index f72e45f361d..0aaa59ee12d 100644 --- a/docs/router/framework/react/routing/file-naming-conventions.md +++ b/docs/router/framework/react/routing/file-naming-conventions.md @@ -20,6 +20,7 @@ File-based routing requires that you follow a few simple file naming conventions > **💡 Remember:** The file-naming conventions for your project could be affected by what [options](../../../../api/file-based-routing.md) are configured. > [!NOTE] To escape a trailing underscore, for example `/posts[_].tsx`, usage of the upgraded [Non-Nested Routes](../routing-concepts#non-nested-routes) is required. + ## Dynamic Path Params Dynamic path params can be used in both flat and directory routes to create routes that can match a dynamic segment of the URL path. Dynamic path params are denoted by the `$` character in the filename: diff --git a/docs/router/framework/react/routing/routing-concepts.md b/docs/router/framework/react/routing/routing-concepts.md index 2203dbb8051..a4dff56438a 100644 --- a/docs/router/framework/react/routing/routing-concepts.md +++ b/docs/router/framework/react/routing/routing-concepts.md @@ -353,35 +353,37 @@ The following table shows which component will be rendered based on the URL: - The `posts_.$postId.edit.tsx` route **does not share** the same `posts` prefix as the other routes and therefore will be treated as if it is a top-level route and will render ``. > [!NOTE] -> While using non-nested routes with file-based routing already works brilliantly, it might misbehave in certain conditions. -> Many of these limitations have already been addressed and will be released in the next major version of TanStack Router. +> While using non-nested routes with file-based routing already works brilliantly, it might misbehave in certain conditions. +> Many of these limitations have already been addressed and will be released in the next major version of TanStack Router. > > To start enjoying these benefits early, you can enable the experimental `nonNestedRoutes` flag in the router plugin configuration: > > ```ts > export default defineConfig({ -> plugins: [ -> tanstackRouter({ -> // some config, -> experimental: { -> nonNestedRoutes: true, -> }, -> }), -> ], +> plugins: [ +> tanstackRouter({ +> // some config, +> experimental: { +> nonNestedRoutes: true, +> }, +> }), +> ], > }) > ``` > -> *It is important to note that this does bring a slight change in how non-nested routes are referenced in useParams, useNavigate, etc. For this reason this has been released as a feature flag. -> The trailing underscore is no longer expected in the path:* -> +> _It is important to note that this does bring a slight change in how non-nested routes are referenced in useParams, useNavigate, etc. For this reason this has been released as a feature flag. +> The trailing underscore is no longer expected in the path:_ +> > Previously: +> > ```ts -> useParams({from: '/posts_/$postId/edit'}) +> useParams({ from: '/posts_/$postId/edit' }) > ``` -> +> > Now: +> > ```ts -> useParams({from: '/posts/$postId/edit'}) +> useParams({ from: '/posts/$postId/edit' }) > ``` ## Excluding Files and Folders from Routes diff --git a/e2e/react-router/basic-file-based/package.json b/e2e/react-router/basic-file-based/package.json index f39ea4ed1c0..ebb92b5b0b3 100644 --- a/e2e/react-router/basic-file-based/package.json +++ b/e2e/react-router/basic-file-based/package.json @@ -4,17 +4,17 @@ "type": "module", "scripts": { "dev": "vite --port 3000", - "dev:nonnested": "MODE=nonnested VITE_MODE=nonnested vite --port 3000", + "dev:nonnested": "MODE=nonnested VITE_MODE=nonnested vite --port 3000", "dev:e2e": "vite", "build": "vite build && tsc --noEmit", - "build:nonnested": "MODE=nonnested VITE_MODE=nonnested vite build && tsc --noEmit", + "build:nonnested": "MODE=nonnested VITE_MODE=nonnested vite build && tsc --noEmit", "serve": "vite preview", - "serve:nonnested": "MODE=nonnested VITE_MODE=nonnested vite preview", + "serve:nonnested": "MODE=nonnested VITE_MODE=nonnested vite preview", "start": "vite", - "start:nonnested": "MODE=nonnested VITE_MODE=nonnested vite", + "start:nonnested": "MODE=nonnested VITE_MODE=nonnested vite", "test:e2e": "pnpm run test:e2e:nonnested && pnpm run test:e2e:default", - "test:e2e:default": "rm -rf port*.txt; playwright test --project=chromium", - "test:e2e:nonnested": "rm -rf port*.txt; MODE=nonnested playwright test --project=chromium" + "test:e2e:default": "rm -rf port*.txt; playwright test --project=chromium", + "test:e2e:nonnested": "rm -rf port*.txt; MODE=nonnested playwright test --project=chromium" }, "dependencies": { "@tanstack/react-router": "workspace:^", diff --git a/e2e/solid-router/basic-file-based/package.json b/e2e/solid-router/basic-file-based/package.json index 3221d80c3fc..b39fc3e6edc 100644 --- a/e2e/solid-router/basic-file-based/package.json +++ b/e2e/solid-router/basic-file-based/package.json @@ -4,17 +4,17 @@ "type": "module", "scripts": { "dev": "vite --port 3000", - "dev:nonnested": "MODE=nonnested VITE_MODE=nonnested vite --port 3000", + "dev:nonnested": "MODE=nonnested VITE_MODE=nonnested vite --port 3000", "dev:e2e": "vite", "build": "vite build && tsc --noEmit", - "build:nonnested": "MODE=nonnested VITE_MODE=nonnested vite build && tsc --noEmit", + "build:nonnested": "MODE=nonnested VITE_MODE=nonnested vite build && tsc --noEmit", "serve": "vite preview", - "serve:nonnested": "MODE=nonnested VITE_MODE=nonnested vite preview", + "serve:nonnested": "MODE=nonnested VITE_MODE=nonnested vite preview", "start": "vite", - "start:nonnested": "MODE=nonnested VITE_MODE=nonnested vite", - "test:e2e": "pnpm run test:e2e:nonnested && pnpm run test:e2e:default", - "test:e2e:default": "rm -rf port*.txt; playwright test --project=chromium", - "test:e2e:nonnested": "rm -rf port*.txt; MODE=nonnested playwright test --project=chromium" + "start:nonnested": "MODE=nonnested VITE_MODE=nonnested vite", + "test:e2e": "pnpm run test:e2e:nonnested && pnpm run test:e2e:default", + "test:e2e:default": "rm -rf port*.txt; playwright test --project=chromium", + "test:e2e:nonnested": "rm -rf port*.txt; MODE=nonnested playwright test --project=chromium" }, "dependencies": { "@tanstack/solid-router": "workspace:^", diff --git a/packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts b/packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts index e4f872ce3a9..38b28625519 100644 --- a/packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts +++ b/packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts @@ -14,64 +14,82 @@ import { Route as NestedIndexRouteImport } from './routes/nested/index' import { Route as NestedChildRouteImport } from './routes/nested/child' const IndexRoute = IndexRouteImport.update({ - id: '/',path: '/',getParentRoute: () => rootRouteImport - }as any) + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) const NestedIndexRoute = NestedIndexRouteImport.update({ - id: '/nested/',path: '/nested/',getParentRoute: () => rootRouteImport - }as any) + id: '/nested/', + path: '/nested/', + getParentRoute: () => rootRouteImport, +} as any) const NestedChildRoute = NestedChildRouteImport.update({ - id: '/nested/child',path: '/nested/child',getParentRoute: () => rootRouteImport - }as any) + id: '/nested/child', + path: '/nested/child', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { -'/': typeof IndexRoute,'/nested/child': typeof NestedChildRoute,'/nested': typeof NestedIndexRoute + '/': typeof IndexRoute + '/nested/child': typeof NestedChildRoute + '/nested': typeof NestedIndexRoute } export interface FileRoutesByTo { -'/': typeof IndexRoute,'/nested/child': typeof NestedChildRoute,'/nested': typeof NestedIndexRoute + '/': typeof IndexRoute + '/nested/child': typeof NestedChildRoute + '/nested': typeof NestedIndexRoute } export interface FileRoutesById { -'__root__': typeof rootRouteImport, -'/': typeof IndexRoute,'/nested/child': typeof NestedChildRoute,'/nested/': typeof NestedIndexRoute + __root__: typeof rootRouteImport + '/': typeof IndexRoute + '/nested/child': typeof NestedChildRoute + '/nested/': typeof NestedIndexRoute } export interface FileRouteTypes { -fileRoutesByFullPath: FileRoutesByFullPath -fullPaths: '/'|'/nested/child'|'/nested' -fileRoutesByTo: FileRoutesByTo -to: '/'|'/nested/child'|'/nested' -id: '__root__'|'/'|'/nested/child'|'/nested/' -fileRoutesById: FileRoutesById + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' | '/nested/child' | '/nested' + fileRoutesByTo: FileRoutesByTo + to: '/' | '/nested/child' | '/nested' + id: '__root__' | '/' | '/nested/child' | '/nested/' + fileRoutesById: FileRoutesById } export interface RootRouteChildren { -IndexRoute: typeof IndexRoute,NestedChildRoute: typeof NestedChildRoute,NestedIndexRoute: typeof NestedIndexRoute + IndexRoute: typeof IndexRoute + NestedChildRoute: typeof NestedChildRoute + NestedIndexRoute: typeof NestedIndexRoute } declare module '@tanstack/react-router' { interface FileRoutesByPath { '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport - parentRoute: typeof rootRouteImport - } -'/nested/': { - id: '/nested/' - path: '/nested' - fullPath: '/nested' - preLoaderRoute: typeof NestedIndexRouteImport - parentRoute: typeof rootRouteImport - } -'/nested/child': { - id: '/nested/child' - path: '/nested/child' - fullPath: '/nested/child' - preLoaderRoute: typeof NestedChildRouteImport - parentRoute: typeof rootRouteImport - } + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + '/nested/': { + id: '/nested/' + path: '/nested' + fullPath: '/nested' + preLoaderRoute: typeof NestedIndexRouteImport + parentRoute: typeof rootRouteImport + } + '/nested/child': { + id: '/nested/child' + path: '/nested/child' + fullPath: '/nested/child' + preLoaderRoute: typeof NestedChildRouteImport + parentRoute: typeof rootRouteImport + } } } const rootRouteChildren: RootRouteChildren = { - IndexRoute: IndexRoute,NestedChildRoute: NestedChildRoute,NestedIndexRoute: NestedIndexRoute + IndexRoute: IndexRoute, + NestedChildRoute: NestedChildRoute, + NestedIndexRoute: NestedIndexRoute, } -export const routeTree = rootRouteImport._addFileChildren(rootRouteChildren)._addFileTypes() \ No newline at end of file +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() From 126c320d319fa3b334bab260ff5592308decfaea Mon Sep 17 00:00:00 2001 From: Nico Lynzaad Date: Wed, 8 Oct 2025 01:09:18 +0200 Subject: [PATCH 14/25] resolve test issues --- .../src/routes/posts_.$postId.edit.tsx | 5 +- .../routeTree.nonnested.snapshot.ts | 96 ++++++++----------- 2 files changed, 43 insertions(+), 58 deletions(-) diff --git a/e2e/solid-router/basic-file-based/src/routes/posts_.$postId.edit.tsx b/e2e/solid-router/basic-file-based/src/routes/posts_.$postId.edit.tsx index a5716ae455f..576d1245a5c 100644 --- a/e2e/solid-router/basic-file-based/src/routes/posts_.$postId.edit.tsx +++ b/e2e/solid-router/basic-file-based/src/routes/posts_.$postId.edit.tsx @@ -12,7 +12,10 @@ const api = getRouteApi( function PostEditPage() { const paramsViaApi = api.useParams() - const paramsViaHook = useParams({ from: '/posts_/$postId/edit' }) + const paramsViaHook = useParams({ + // @ts-expect-error path is updated with new Experimental Non Nested Paths to not include the trailing underscore + from: `/${useExperimentalNonNestedRoutes ? 'posts' : 'posts_'}/$postId/edit`, + }) const paramsViaRouteHook = Route.useParams() return ( diff --git a/packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts b/packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts index 38b28625519..e4f872ce3a9 100644 --- a/packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts +++ b/packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts @@ -14,82 +14,64 @@ import { Route as NestedIndexRouteImport } from './routes/nested/index' import { Route as NestedChildRouteImport } from './routes/nested/child' const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => rootRouteImport, -} as any) + id: '/',path: '/',getParentRoute: () => rootRouteImport + }as any) const NestedIndexRoute = NestedIndexRouteImport.update({ - id: '/nested/', - path: '/nested/', - getParentRoute: () => rootRouteImport, -} as any) + id: '/nested/',path: '/nested/',getParentRoute: () => rootRouteImport + }as any) const NestedChildRoute = NestedChildRouteImport.update({ - id: '/nested/child', - path: '/nested/child', - getParentRoute: () => rootRouteImport, -} as any) + id: '/nested/child',path: '/nested/child',getParentRoute: () => rootRouteImport + }as any) export interface FileRoutesByFullPath { - '/': typeof IndexRoute - '/nested/child': typeof NestedChildRoute - '/nested': typeof NestedIndexRoute +'/': typeof IndexRoute,'/nested/child': typeof NestedChildRoute,'/nested': typeof NestedIndexRoute } export interface FileRoutesByTo { - '/': typeof IndexRoute - '/nested/child': typeof NestedChildRoute - '/nested': typeof NestedIndexRoute +'/': typeof IndexRoute,'/nested/child': typeof NestedChildRoute,'/nested': typeof NestedIndexRoute } export interface FileRoutesById { - __root__: typeof rootRouteImport - '/': typeof IndexRoute - '/nested/child': typeof NestedChildRoute - '/nested/': typeof NestedIndexRoute +'__root__': typeof rootRouteImport, +'/': typeof IndexRoute,'/nested/child': typeof NestedChildRoute,'/nested/': typeof NestedIndexRoute } export interface FileRouteTypes { - fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: '/' | '/nested/child' | '/nested' - fileRoutesByTo: FileRoutesByTo - to: '/' | '/nested/child' | '/nested' - id: '__root__' | '/' | '/nested/child' | '/nested/' - fileRoutesById: FileRoutesById +fileRoutesByFullPath: FileRoutesByFullPath +fullPaths: '/'|'/nested/child'|'/nested' +fileRoutesByTo: FileRoutesByTo +to: '/'|'/nested/child'|'/nested' +id: '__root__'|'/'|'/nested/child'|'/nested/' +fileRoutesById: FileRoutesById } export interface RootRouteChildren { - IndexRoute: typeof IndexRoute - NestedChildRoute: typeof NestedChildRoute - NestedIndexRoute: typeof NestedIndexRoute +IndexRoute: typeof IndexRoute,NestedChildRoute: typeof NestedChildRoute,NestedIndexRoute: typeof NestedIndexRoute } declare module '@tanstack/react-router' { interface FileRoutesByPath { '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport - parentRoute: typeof rootRouteImport - } - '/nested/': { - id: '/nested/' - path: '/nested' - fullPath: '/nested' - preLoaderRoute: typeof NestedIndexRouteImport - parentRoute: typeof rootRouteImport - } - '/nested/child': { - id: '/nested/child' - path: '/nested/child' - fullPath: '/nested/child' - preLoaderRoute: typeof NestedChildRouteImport - parentRoute: typeof rootRouteImport - } + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } +'/nested/': { + id: '/nested/' + path: '/nested' + fullPath: '/nested' + preLoaderRoute: typeof NestedIndexRouteImport + parentRoute: typeof rootRouteImport + } +'/nested/child': { + id: '/nested/child' + path: '/nested/child' + fullPath: '/nested/child' + preLoaderRoute: typeof NestedChildRouteImport + parentRoute: typeof rootRouteImport + } } } const rootRouteChildren: RootRouteChildren = { - IndexRoute: IndexRoute, - NestedChildRoute: NestedChildRoute, - NestedIndexRoute: NestedIndexRoute, + IndexRoute: IndexRoute,NestedChildRoute: NestedChildRoute,NestedIndexRoute: NestedIndexRoute } -export const routeTree = rootRouteImport - ._addFileChildren(rootRouteChildren) - ._addFileTypes() +export const routeTree = rootRouteImport._addFileChildren(rootRouteChildren)._addFileTypes() \ No newline at end of file From cb9bee574481fd70f68a71c57170ab96e60e467f Mon Sep 17 00:00:00 2001 From: Nico Lynzaad Date: Wed, 8 Oct 2025 01:36:40 +0200 Subject: [PATCH 15/25] code rabbit suggestions --- .../src/filesystem/physical/getRouteNodes.ts | 2 +- packages/router-generator/src/generator.ts | 17 ++++------- packages/router-generator/src/utils.ts | 30 +++++++------------ .../router-generator/tests/generator.test.ts | 20 +++---------- 4 files changed, 21 insertions(+), 48 deletions(-) diff --git a/packages/router-generator/src/filesystem/physical/getRouteNodes.ts b/packages/router-generator/src/filesystem/physical/getRouteNodes.ts index 96d902c70f1..949c93302e1 100644 --- a/packages/router-generator/src/filesystem/physical/getRouteNodes.ts +++ b/packages/router-generator/src/filesystem/physical/getRouteNodes.ts @@ -233,7 +233,7 @@ export async function getRouteNodes( */ export function getRouteMeta( routePath: string, - config: Pick, + config: Pick, ): { // `__root` is can be more easily determined by filtering down to routePath === /${rootPathId} // `pathless` is needs to determined after `lazy` has been cleaned up from the routePath diff --git a/packages/router-generator/src/generator.ts b/packages/router-generator/src/generator.ts index e2b438c9c93..162f67ccdcb 100644 --- a/packages/router-generator/src/generator.ts +++ b/packages/router-generator/src/generator.ts @@ -677,14 +677,14 @@ export class Generator { if (!config.disableTypes) { fileRoutesByFullPath = [ `export interface FileRoutesByFullPath { -${[...createRouteNodesByFullPath(acc.routeNodes, this.config).entries()] +${[...createRouteNodesByFullPath(acc.routeNodes, config).entries()] .filter(([fullPath]) => fullPath) .map(([fullPath, routeNode]) => { return `'${fullPath}': typeof ${getResolvedRouteNodeVariableName(routeNode)}` })} }`, `export interface FileRoutesByTo { -${[...createRouteNodesByTo(acc.routeNodes, this.config).entries()] +${[...createRouteNodesByTo(acc.routeNodes, config).entries()] .filter(([to]) => to) .map(([to, routeNode]) => { return `'${to}': typeof ${getResolvedRouteNodeVariableName(routeNode)}` @@ -700,12 +700,7 @@ ${[...createRouteNodesById(acc.routeNodes).entries()].map(([id, routeNode]) => { fileRoutesByFullPath: FileRoutesByFullPath fullPaths: ${ acc.routeNodes.length > 0 - ? [ - ...createRouteNodesByFullPath( - acc.routeNodes, - this.config, - ).keys(), - ] + ? [...createRouteNodesByFullPath(acc.routeNodes, config).keys()] .filter((fullPath) => fullPath) .map((fullPath) => `'${fullPath}'`) .join('|') @@ -714,7 +709,7 @@ fullPaths: ${ fileRoutesByTo: FileRoutesByTo to: ${ acc.routeNodes.length > 0 - ? [...createRouteNodesByTo(acc.routeNodes, this.config).keys()] + ? [...createRouteNodesByTo(acc.routeNodes, config).keys()] .filter((to) => to) .map((to) => `'${to}'`) .join('|') @@ -732,7 +727,7 @@ ${acc.routeTree.map((child) => `${child.variableName}Route: typeof ${getResolved module: this.targetTemplate.fullPkg, interfaceName: 'FileRoutesByPath', routeNodes: sortedRouteNodes, - config: this.config, + config, }) } @@ -1209,7 +1204,6 @@ ${acc.routeTree.map((child) => `${child.variableName}Route: typeof ${getResolved acc.routeNodes, node, node.routePath, - useExperimentalNonNestedRoutes, node.originalRoutePath, ) @@ -1220,6 +1214,7 @@ ${acc.routeTree.map((child) => `${child.variableName}Route: typeof ${getResolved parentRoute.children, node, node.routePath, + node.originalRoutePath, ) if (possibleParentRoute) { parentRoute = possibleParentRoute diff --git a/packages/router-generator/src/utils.ts b/packages/router-generator/src/utils.ts index 8cd7df51689..5b9d9f54a98 100644 --- a/packages/router-generator/src/utils.ts +++ b/packages/router-generator/src/utils.ts @@ -193,33 +193,33 @@ export function removeUnderscores(s?: string) { return s?.replaceAll(/(^_|_$)/gi, '').replaceAll(/(\/_|_\/)/gi, '/') } -export function removeLeadingUnderscores(s: string, routeToken: string) { - const parts = s.split('/') +function escapeRegExp(s: string): string { + return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') +} - if (parts.length === 0) return s +export function removeLeadingUnderscores(s: string, routeToken: string) { + if (!s) return s const routeTokenToExclude = routeToken[0] === '_' ? routeToken.slice(1) : routeToken const leadingUnderscoreRegex = routeToken[0] === '_' - ? new RegExp(`(?<=^|\\/)_(?!${routeTokenToExclude})`, 'g') + ? new RegExp(`(?<=^|\\/)_(?!${escapeRegExp(routeTokenToExclude)})`, 'g') : new RegExp(`(?<=^|\\/)_`, 'g') return s.replaceAll(leadingUnderscoreRegex, '') } export function removeTrailingUnderscores(s: string, routeToken: string) { - const parts = s.split('/') - - if (parts.length === 0) return s + if (!s) return s const routeTokenToExclude = routeToken.slice(-1) === '_' ? routeToken.slice(0, -1) : routeToken const trailingUnderscoreRegex = routeToken[0] === '_' - ? new RegExp(`(?, node: RouteNode, routePathToCheck: string | undefined, - useExperimentalNonNestedRoutes?: boolean, originalRoutePathToCheck?: string, ): RouteNode | null { const getNonNestedSegments = (routePath: string) => { @@ -433,13 +432,7 @@ export function hasParentRoute( segments.pop() // Remove the last segment const parentRoutePath = segments.join('/') - return hasParentRoute( - routes, - node, - parentRoutePath, - useExperimentalNonNestedRoutes, - originalRoutePathToCheck, - ) + return hasParentRoute(routes, node, parentRoutePath, originalRoutePathToCheck) } /** @@ -489,10 +482,7 @@ export const inferFullPath = ( // TODO with new major we can remove check and only remove leading underscores const fullPath = removeGroups( (config?.experimental?.nonNestedRoutes - ? removeLeadingUnderscores( - removeLayoutSegments(routeNode.routePath), - config.routeToken, - ) + ? removeLayoutSegments(routeNode.routePath) : removeUnderscores(removeLayoutSegments(routeNode.routePath))) ?? '', ) diff --git a/packages/router-generator/tests/generator.test.ts b/packages/router-generator/tests/generator.test.ts index 82811c7989f..59f9f446ebb 100644 --- a/packages/router-generator/tests/generator.test.ts +++ b/packages/router-generator/tests/generator.test.ts @@ -246,22 +246,10 @@ function shouldThrow(folderName: string) { describe('generator works', async () => { const folderNames = await readDir() - const testCases = folderNames.reduce( - (accum: Array<{ folderName: string; nonNested: boolean }>, folderName) => { - accum.push({ - folderName, - nonNested: true, - }) - - accum.push({ - folderName, - nonNested: false, - }) - - return accum - }, - [], - ) + const testCases = folderNames.flatMap((folderName) => [ + { folderName, nonNested: true }, + { folderName, nonNested: false }, + ]) it.each(testCases)( 'should wire-up the routes for a "%s" tree', From 638c0db65d7a8fb8549a9e55bf5ebdea724cf4b7 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 7 Oct 2025 23:37:44 +0000 Subject: [PATCH 16/25] ci: apply automated fixes --- .../routeTree.nonnested.snapshot.ts | 96 +++++++++++-------- 1 file changed, 57 insertions(+), 39 deletions(-) diff --git a/packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts b/packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts index e4f872ce3a9..38b28625519 100644 --- a/packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts +++ b/packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts @@ -14,64 +14,82 @@ import { Route as NestedIndexRouteImport } from './routes/nested/index' import { Route as NestedChildRouteImport } from './routes/nested/child' const IndexRoute = IndexRouteImport.update({ - id: '/',path: '/',getParentRoute: () => rootRouteImport - }as any) + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) const NestedIndexRoute = NestedIndexRouteImport.update({ - id: '/nested/',path: '/nested/',getParentRoute: () => rootRouteImport - }as any) + id: '/nested/', + path: '/nested/', + getParentRoute: () => rootRouteImport, +} as any) const NestedChildRoute = NestedChildRouteImport.update({ - id: '/nested/child',path: '/nested/child',getParentRoute: () => rootRouteImport - }as any) + id: '/nested/child', + path: '/nested/child', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { -'/': typeof IndexRoute,'/nested/child': typeof NestedChildRoute,'/nested': typeof NestedIndexRoute + '/': typeof IndexRoute + '/nested/child': typeof NestedChildRoute + '/nested': typeof NestedIndexRoute } export interface FileRoutesByTo { -'/': typeof IndexRoute,'/nested/child': typeof NestedChildRoute,'/nested': typeof NestedIndexRoute + '/': typeof IndexRoute + '/nested/child': typeof NestedChildRoute + '/nested': typeof NestedIndexRoute } export interface FileRoutesById { -'__root__': typeof rootRouteImport, -'/': typeof IndexRoute,'/nested/child': typeof NestedChildRoute,'/nested/': typeof NestedIndexRoute + __root__: typeof rootRouteImport + '/': typeof IndexRoute + '/nested/child': typeof NestedChildRoute + '/nested/': typeof NestedIndexRoute } export interface FileRouteTypes { -fileRoutesByFullPath: FileRoutesByFullPath -fullPaths: '/'|'/nested/child'|'/nested' -fileRoutesByTo: FileRoutesByTo -to: '/'|'/nested/child'|'/nested' -id: '__root__'|'/'|'/nested/child'|'/nested/' -fileRoutesById: FileRoutesById + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' | '/nested/child' | '/nested' + fileRoutesByTo: FileRoutesByTo + to: '/' | '/nested/child' | '/nested' + id: '__root__' | '/' | '/nested/child' | '/nested/' + fileRoutesById: FileRoutesById } export interface RootRouteChildren { -IndexRoute: typeof IndexRoute,NestedChildRoute: typeof NestedChildRoute,NestedIndexRoute: typeof NestedIndexRoute + IndexRoute: typeof IndexRoute + NestedChildRoute: typeof NestedChildRoute + NestedIndexRoute: typeof NestedIndexRoute } declare module '@tanstack/react-router' { interface FileRoutesByPath { '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport - parentRoute: typeof rootRouteImport - } -'/nested/': { - id: '/nested/' - path: '/nested' - fullPath: '/nested' - preLoaderRoute: typeof NestedIndexRouteImport - parentRoute: typeof rootRouteImport - } -'/nested/child': { - id: '/nested/child' - path: '/nested/child' - fullPath: '/nested/child' - preLoaderRoute: typeof NestedChildRouteImport - parentRoute: typeof rootRouteImport - } + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + '/nested/': { + id: '/nested/' + path: '/nested' + fullPath: '/nested' + preLoaderRoute: typeof NestedIndexRouteImport + parentRoute: typeof rootRouteImport + } + '/nested/child': { + id: '/nested/child' + path: '/nested/child' + fullPath: '/nested/child' + preLoaderRoute: typeof NestedChildRouteImport + parentRoute: typeof rootRouteImport + } } } const rootRouteChildren: RootRouteChildren = { - IndexRoute: IndexRoute,NestedChildRoute: NestedChildRoute,NestedIndexRoute: NestedIndexRoute + IndexRoute: IndexRoute, + NestedChildRoute: NestedChildRoute, + NestedIndexRoute: NestedIndexRoute, } -export const routeTree = rootRouteImport._addFileChildren(rootRouteChildren)._addFileTypes() \ No newline at end of file +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() From 7f6e4b5ca656211946d3d64d83ecc7aad5b2d5e9 Mon Sep 17 00:00:00 2001 From: Nico Lynzaad Date: Wed, 8 Oct 2025 01:47:39 +0200 Subject: [PATCH 17/25] fix excess period in routeTree path --- packages/router-generator/tests/generator.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/router-generator/tests/generator.test.ts b/packages/router-generator/tests/generator.test.ts index 59f9f446ebb..ee9e317d14f 100644 --- a/packages/router-generator/tests/generator.test.ts +++ b/packages/router-generator/tests/generator.test.ts @@ -115,7 +115,7 @@ function rewriteConfigByFolderName( config.disableTypes = true config.generatedRouteTree = makeFolderDir(folderName) + - `/routeTree.${nonNested ? 'nonnested.' : ''}.gen.js` + `/routeTree${nonNested ? 'nonnested.' : ''}.gen.js` break case 'custom-scaffolding': config.customScaffolding = { From d561757aebb79646eb2ceb81ac2b3c7c9ff931c0 Mon Sep 17 00:00:00 2001 From: Nico Lynzaad Date: Wed, 8 Oct 2025 01:47:55 +0200 Subject: [PATCH 18/25] restore snapshot again --- .../routeTree.nonnested.snapshot.ts | 96 ++++++++----------- 1 file changed, 39 insertions(+), 57 deletions(-) diff --git a/packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts b/packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts index 38b28625519..e4f872ce3a9 100644 --- a/packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts +++ b/packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts @@ -14,82 +14,64 @@ import { Route as NestedIndexRouteImport } from './routes/nested/index' import { Route as NestedChildRouteImport } from './routes/nested/child' const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => rootRouteImport, -} as any) + id: '/',path: '/',getParentRoute: () => rootRouteImport + }as any) const NestedIndexRoute = NestedIndexRouteImport.update({ - id: '/nested/', - path: '/nested/', - getParentRoute: () => rootRouteImport, -} as any) + id: '/nested/',path: '/nested/',getParentRoute: () => rootRouteImport + }as any) const NestedChildRoute = NestedChildRouteImport.update({ - id: '/nested/child', - path: '/nested/child', - getParentRoute: () => rootRouteImport, -} as any) + id: '/nested/child',path: '/nested/child',getParentRoute: () => rootRouteImport + }as any) export interface FileRoutesByFullPath { - '/': typeof IndexRoute - '/nested/child': typeof NestedChildRoute - '/nested': typeof NestedIndexRoute +'/': typeof IndexRoute,'/nested/child': typeof NestedChildRoute,'/nested': typeof NestedIndexRoute } export interface FileRoutesByTo { - '/': typeof IndexRoute - '/nested/child': typeof NestedChildRoute - '/nested': typeof NestedIndexRoute +'/': typeof IndexRoute,'/nested/child': typeof NestedChildRoute,'/nested': typeof NestedIndexRoute } export interface FileRoutesById { - __root__: typeof rootRouteImport - '/': typeof IndexRoute - '/nested/child': typeof NestedChildRoute - '/nested/': typeof NestedIndexRoute +'__root__': typeof rootRouteImport, +'/': typeof IndexRoute,'/nested/child': typeof NestedChildRoute,'/nested/': typeof NestedIndexRoute } export interface FileRouteTypes { - fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: '/' | '/nested/child' | '/nested' - fileRoutesByTo: FileRoutesByTo - to: '/' | '/nested/child' | '/nested' - id: '__root__' | '/' | '/nested/child' | '/nested/' - fileRoutesById: FileRoutesById +fileRoutesByFullPath: FileRoutesByFullPath +fullPaths: '/'|'/nested/child'|'/nested' +fileRoutesByTo: FileRoutesByTo +to: '/'|'/nested/child'|'/nested' +id: '__root__'|'/'|'/nested/child'|'/nested/' +fileRoutesById: FileRoutesById } export interface RootRouteChildren { - IndexRoute: typeof IndexRoute - NestedChildRoute: typeof NestedChildRoute - NestedIndexRoute: typeof NestedIndexRoute +IndexRoute: typeof IndexRoute,NestedChildRoute: typeof NestedChildRoute,NestedIndexRoute: typeof NestedIndexRoute } declare module '@tanstack/react-router' { interface FileRoutesByPath { '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport - parentRoute: typeof rootRouteImport - } - '/nested/': { - id: '/nested/' - path: '/nested' - fullPath: '/nested' - preLoaderRoute: typeof NestedIndexRouteImport - parentRoute: typeof rootRouteImport - } - '/nested/child': { - id: '/nested/child' - path: '/nested/child' - fullPath: '/nested/child' - preLoaderRoute: typeof NestedChildRouteImport - parentRoute: typeof rootRouteImport - } + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } +'/nested/': { + id: '/nested/' + path: '/nested' + fullPath: '/nested' + preLoaderRoute: typeof NestedIndexRouteImport + parentRoute: typeof rootRouteImport + } +'/nested/child': { + id: '/nested/child' + path: '/nested/child' + fullPath: '/nested/child' + preLoaderRoute: typeof NestedChildRouteImport + parentRoute: typeof rootRouteImport + } } } const rootRouteChildren: RootRouteChildren = { - IndexRoute: IndexRoute, - NestedChildRoute: NestedChildRoute, - NestedIndexRoute: NestedIndexRoute, + IndexRoute: IndexRoute,NestedChildRoute: NestedChildRoute,NestedIndexRoute: NestedIndexRoute } -export const routeTree = rootRouteImport - ._addFileChildren(rootRouteChildren) - ._addFileTypes() +export const routeTree = rootRouteImport._addFileChildren(rootRouteChildren)._addFileTypes() \ No newline at end of file From 4a51ede05422676907d0f774cf38d8c22a0c9038 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 7 Oct 2025 23:49:01 +0000 Subject: [PATCH 19/25] ci: apply automated fixes --- .../routeTree.nonnested.snapshot.ts | 96 +++++++++++-------- 1 file changed, 57 insertions(+), 39 deletions(-) diff --git a/packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts b/packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts index e4f872ce3a9..38b28625519 100644 --- a/packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts +++ b/packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts @@ -14,64 +14,82 @@ import { Route as NestedIndexRouteImport } from './routes/nested/index' import { Route as NestedChildRouteImport } from './routes/nested/child' const IndexRoute = IndexRouteImport.update({ - id: '/',path: '/',getParentRoute: () => rootRouteImport - }as any) + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) const NestedIndexRoute = NestedIndexRouteImport.update({ - id: '/nested/',path: '/nested/',getParentRoute: () => rootRouteImport - }as any) + id: '/nested/', + path: '/nested/', + getParentRoute: () => rootRouteImport, +} as any) const NestedChildRoute = NestedChildRouteImport.update({ - id: '/nested/child',path: '/nested/child',getParentRoute: () => rootRouteImport - }as any) + id: '/nested/child', + path: '/nested/child', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { -'/': typeof IndexRoute,'/nested/child': typeof NestedChildRoute,'/nested': typeof NestedIndexRoute + '/': typeof IndexRoute + '/nested/child': typeof NestedChildRoute + '/nested': typeof NestedIndexRoute } export interface FileRoutesByTo { -'/': typeof IndexRoute,'/nested/child': typeof NestedChildRoute,'/nested': typeof NestedIndexRoute + '/': typeof IndexRoute + '/nested/child': typeof NestedChildRoute + '/nested': typeof NestedIndexRoute } export interface FileRoutesById { -'__root__': typeof rootRouteImport, -'/': typeof IndexRoute,'/nested/child': typeof NestedChildRoute,'/nested/': typeof NestedIndexRoute + __root__: typeof rootRouteImport + '/': typeof IndexRoute + '/nested/child': typeof NestedChildRoute + '/nested/': typeof NestedIndexRoute } export interface FileRouteTypes { -fileRoutesByFullPath: FileRoutesByFullPath -fullPaths: '/'|'/nested/child'|'/nested' -fileRoutesByTo: FileRoutesByTo -to: '/'|'/nested/child'|'/nested' -id: '__root__'|'/'|'/nested/child'|'/nested/' -fileRoutesById: FileRoutesById + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' | '/nested/child' | '/nested' + fileRoutesByTo: FileRoutesByTo + to: '/' | '/nested/child' | '/nested' + id: '__root__' | '/' | '/nested/child' | '/nested/' + fileRoutesById: FileRoutesById } export interface RootRouteChildren { -IndexRoute: typeof IndexRoute,NestedChildRoute: typeof NestedChildRoute,NestedIndexRoute: typeof NestedIndexRoute + IndexRoute: typeof IndexRoute + NestedChildRoute: typeof NestedChildRoute + NestedIndexRoute: typeof NestedIndexRoute } declare module '@tanstack/react-router' { interface FileRoutesByPath { '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport - parentRoute: typeof rootRouteImport - } -'/nested/': { - id: '/nested/' - path: '/nested' - fullPath: '/nested' - preLoaderRoute: typeof NestedIndexRouteImport - parentRoute: typeof rootRouteImport - } -'/nested/child': { - id: '/nested/child' - path: '/nested/child' - fullPath: '/nested/child' - preLoaderRoute: typeof NestedChildRouteImport - parentRoute: typeof rootRouteImport - } + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + '/nested/': { + id: '/nested/' + path: '/nested' + fullPath: '/nested' + preLoaderRoute: typeof NestedIndexRouteImport + parentRoute: typeof rootRouteImport + } + '/nested/child': { + id: '/nested/child' + path: '/nested/child' + fullPath: '/nested/child' + preLoaderRoute: typeof NestedChildRouteImport + parentRoute: typeof rootRouteImport + } } } const rootRouteChildren: RootRouteChildren = { - IndexRoute: IndexRoute,NestedChildRoute: NestedChildRoute,NestedIndexRoute: NestedIndexRoute + IndexRoute: IndexRoute, + NestedChildRoute: NestedChildRoute, + NestedIndexRoute: NestedIndexRoute, } -export const routeTree = rootRouteImport._addFileChildren(rootRouteChildren)._addFileTypes() \ No newline at end of file +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() From 258ddf29f38655c2cf85d8826e30753357878ff5 Mon Sep 17 00:00:00 2001 From: Nico Lynzaad Date: Wed, 8 Oct 2025 01:51:11 +0200 Subject: [PATCH 20/25] resolve nitpick --- packages/router-generator/src/utils.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/router-generator/src/utils.ts b/packages/router-generator/src/utils.ts index 5b9d9f54a98..1419d236acd 100644 --- a/packages/router-generator/src/utils.ts +++ b/packages/router-generator/src/utils.ts @@ -203,9 +203,11 @@ export function removeLeadingUnderscores(s: string, routeToken: string) { const routeTokenToExclude = routeToken[0] === '_' ? routeToken.slice(1) : routeToken + const escapedRouteToken = escapeRegExp(routeTokenToExclude) + const leadingUnderscoreRegex = routeToken[0] === '_' - ? new RegExp(`(?<=^|\\/)_(?!${escapeRegExp(routeTokenToExclude)})`, 'g') + ? new RegExp(`(?<=^|\\/)_(?!${escapedRouteToken})`, 'g') : new RegExp(`(?<=^|\\/)_`, 'g') return s.replaceAll(leadingUnderscoreRegex, '') @@ -217,9 +219,11 @@ export function removeTrailingUnderscores(s: string, routeToken: string) { const routeTokenToExclude = routeToken.slice(-1) === '_' ? routeToken.slice(0, -1) : routeToken + const escapedRouteToken = escapeRegExp(routeTokenToExclude) + const trailingUnderscoreRegex = routeToken[0] === '_' - ? new RegExp(`(? Date: Wed, 8 Oct 2025 02:10:45 +0200 Subject: [PATCH 21/25] resolve removeTrailingUnderscore issue --- packages/router-generator/src/utils.ts | 28 +++++++++++-------- packages/router-generator/tests/utils.test.ts | 8 ++++++ 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/packages/router-generator/src/utils.ts b/packages/router-generator/src/utils.ts index 1419d236acd..015afde82bb 100644 --- a/packages/router-generator/src/utils.ts +++ b/packages/router-generator/src/utils.ts @@ -200,15 +200,17 @@ function escapeRegExp(s: string): string { export function removeLeadingUnderscores(s: string, routeToken: string) { if (!s) return s - const routeTokenToExclude = - routeToken[0] === '_' ? routeToken.slice(1) : routeToken + const hasLeadingUnderscore = routeToken[0] === '_' + + const routeTokenToExclude = hasLeadingUnderscore + ? routeToken.slice(1) + : routeToken const escapedRouteToken = escapeRegExp(routeTokenToExclude) - const leadingUnderscoreRegex = - routeToken[0] === '_' - ? new RegExp(`(?<=^|\\/)_(?!${escapedRouteToken})`, 'g') - : new RegExp(`(?<=^|\\/)_`, 'g') + const leadingUnderscoreRegex = hasLeadingUnderscore + ? new RegExp(`(?<=^|\\/)_(?!${escapedRouteToken})`, 'g') + : new RegExp(`(?<=^|\\/)_`, 'g') return s.replaceAll(leadingUnderscoreRegex, '') } @@ -216,15 +218,17 @@ export function removeLeadingUnderscores(s: string, routeToken: string) { export function removeTrailingUnderscores(s: string, routeToken: string) { if (!s) return s - const routeTokenToExclude = - routeToken.slice(-1) === '_' ? routeToken.slice(0, -1) : routeToken + const hasTrailingUnderscore = routeToken.slice(-1) === '_' + + const routeTokenToExclude = hasTrailingUnderscore + ? routeToken.slice(0, -1) + : routeToken const escapedRouteToken = escapeRegExp(routeTokenToExclude) - const trailingUnderscoreRegex = - routeToken[0] === '_' - ? new RegExp(`(? { expect( removeLeadingUnderscores('/_test_/abc/_route_/d_/_e', '_route_'), ).toBe('/test_/abc/_route_/d_/e') + + expect( + removeLeadingUnderscores('/_test_/abc/_route_/d_/_e', 'route_'), + ).toBe('/test_/abc/route_/d_/e') }) }) @@ -273,6 +277,10 @@ describe('removeTrailingUnderscores', () => { expect( removeTrailingUnderscores('/_test_/abc/_route_/d_/_e', '_route_'), ).toBe('/_test/abc/_route_/d/_e') + + expect( + removeTrailingUnderscores('/_test_/abc/_route_/d_/_e', '_route'), + ).toBe('/_test/abc/_route/d/_e') }) }) From 16db1a56395c7f108202117251ed8f8f739578df Mon Sep 17 00:00:00 2001 From: Nico Lynzaad Date: Wed, 8 Oct 2025 02:12:54 +0200 Subject: [PATCH 22/25] fix routeTree path --- packages/router-generator/tests/generator.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/router-generator/tests/generator.test.ts b/packages/router-generator/tests/generator.test.ts index ee9e317d14f..4caeff1ee4f 100644 --- a/packages/router-generator/tests/generator.test.ts +++ b/packages/router-generator/tests/generator.test.ts @@ -115,7 +115,7 @@ function rewriteConfigByFolderName( config.disableTypes = true config.generatedRouteTree = makeFolderDir(folderName) + - `/routeTree${nonNested ? 'nonnested.' : ''}.gen.js` + `/routeTree.${nonNested ? 'nonnested.' : ''}gen.js` break case 'custom-scaffolding': config.customScaffolding = { From bac68cdc16e7df41accd5242940e1c398114c809 Mon Sep 17 00:00:00 2001 From: Nico Lynzaad Date: Wed, 8 Oct 2025 02:23:21 +0200 Subject: [PATCH 23/25] final nitpicks --- packages/router-generator/src/utils.ts | 10 +++++----- packages/router-generator/tests/utils.test.ts | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/router-generator/src/utils.ts b/packages/router-generator/src/utils.ts index 015afde82bb..455941ec463 100644 --- a/packages/router-generator/src/utils.ts +++ b/packages/router-generator/src/utils.ts @@ -53,6 +53,7 @@ export function removeTrailingSlash(s: string) { } const BRACKET_CONTENT_RE = /\[(.*?)\]/g +const SPLIT_REGEX = /(? { }) describe('removeTrailingUnderscores', () => { - it('removes leading underscore when nonnested path', () => { + it('removes trailing underscore when nonnested path', () => { expect(removeTrailingUnderscores('test_', 'route')).toBe('test') expect(removeTrailingUnderscores('/_test_/abc_/route/_d', 'route')).toBe( From d69249704c9122cafd026a3025fc043f52f2beee Mon Sep 17 00:00:00 2001 From: Nico Lynzaad Date: Wed, 8 Oct 2025 02:24:24 +0200 Subject: [PATCH 24/25] better test description --- packages/router-generator/tests/utils.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/router-generator/tests/utils.test.ts b/packages/router-generator/tests/utils.test.ts index 897138742dc..268986cb7a9 100644 --- a/packages/router-generator/tests/utils.test.ts +++ b/packages/router-generator/tests/utils.test.ts @@ -241,7 +241,7 @@ describe('removeUnderscores', () => { }) describe('removeLeadingUnderscores', () => { - it('removes leading underscore when nonnested path', () => { + it('removes leading underscore when not routeToken', () => { expect(removeLeadingUnderscores('_test', 'route')).toBe('test') expect(removeLeadingUnderscores('/_test/abc/route/_d', 'route')).toBe( @@ -263,7 +263,7 @@ describe('removeLeadingUnderscores', () => { }) describe('removeTrailingUnderscores', () => { - it('removes trailing underscore when nonnested path', () => { + it('removes trailing underscore when not routeToken', () => { expect(removeTrailingUnderscores('test_', 'route')).toBe('test') expect(removeTrailingUnderscores('/_test_/abc_/route/_d', 'route')).toBe( From 43220cf02b6a0ca8114c5a6180d2b70218f5dd37 Mon Sep 17 00:00:00 2001 From: Nico Lynzaad Date: Wed, 8 Oct 2025 10:02:10 +0200 Subject: [PATCH 25/25] exclude snapshots from prettier --- packages/router-generator/.prettierignore | 1 + .../routeTree.nonnested.snapshot.ts | 96 ++++++++----------- 2 files changed, 40 insertions(+), 57 deletions(-) create mode 100644 packages/router-generator/.prettierignore diff --git a/packages/router-generator/.prettierignore b/packages/router-generator/.prettierignore new file mode 100644 index 00000000000..5ab1ab37032 --- /dev/null +++ b/packages/router-generator/.prettierignore @@ -0,0 +1 @@ +**/routeTree.*.snapshot.ts diff --git a/packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts b/packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts index 38b28625519..e4f872ce3a9 100644 --- a/packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts +++ b/packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts @@ -14,82 +14,64 @@ import { Route as NestedIndexRouteImport } from './routes/nested/index' import { Route as NestedChildRouteImport } from './routes/nested/child' const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => rootRouteImport, -} as any) + id: '/',path: '/',getParentRoute: () => rootRouteImport + }as any) const NestedIndexRoute = NestedIndexRouteImport.update({ - id: '/nested/', - path: '/nested/', - getParentRoute: () => rootRouteImport, -} as any) + id: '/nested/',path: '/nested/',getParentRoute: () => rootRouteImport + }as any) const NestedChildRoute = NestedChildRouteImport.update({ - id: '/nested/child', - path: '/nested/child', - getParentRoute: () => rootRouteImport, -} as any) + id: '/nested/child',path: '/nested/child',getParentRoute: () => rootRouteImport + }as any) export interface FileRoutesByFullPath { - '/': typeof IndexRoute - '/nested/child': typeof NestedChildRoute - '/nested': typeof NestedIndexRoute +'/': typeof IndexRoute,'/nested/child': typeof NestedChildRoute,'/nested': typeof NestedIndexRoute } export interface FileRoutesByTo { - '/': typeof IndexRoute - '/nested/child': typeof NestedChildRoute - '/nested': typeof NestedIndexRoute +'/': typeof IndexRoute,'/nested/child': typeof NestedChildRoute,'/nested': typeof NestedIndexRoute } export interface FileRoutesById { - __root__: typeof rootRouteImport - '/': typeof IndexRoute - '/nested/child': typeof NestedChildRoute - '/nested/': typeof NestedIndexRoute +'__root__': typeof rootRouteImport, +'/': typeof IndexRoute,'/nested/child': typeof NestedChildRoute,'/nested/': typeof NestedIndexRoute } export interface FileRouteTypes { - fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: '/' | '/nested/child' | '/nested' - fileRoutesByTo: FileRoutesByTo - to: '/' | '/nested/child' | '/nested' - id: '__root__' | '/' | '/nested/child' | '/nested/' - fileRoutesById: FileRoutesById +fileRoutesByFullPath: FileRoutesByFullPath +fullPaths: '/'|'/nested/child'|'/nested' +fileRoutesByTo: FileRoutesByTo +to: '/'|'/nested/child'|'/nested' +id: '__root__'|'/'|'/nested/child'|'/nested/' +fileRoutesById: FileRoutesById } export interface RootRouteChildren { - IndexRoute: typeof IndexRoute - NestedChildRoute: typeof NestedChildRoute - NestedIndexRoute: typeof NestedIndexRoute +IndexRoute: typeof IndexRoute,NestedChildRoute: typeof NestedChildRoute,NestedIndexRoute: typeof NestedIndexRoute } declare module '@tanstack/react-router' { interface FileRoutesByPath { '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport - parentRoute: typeof rootRouteImport - } - '/nested/': { - id: '/nested/' - path: '/nested' - fullPath: '/nested' - preLoaderRoute: typeof NestedIndexRouteImport - parentRoute: typeof rootRouteImport - } - '/nested/child': { - id: '/nested/child' - path: '/nested/child' - fullPath: '/nested/child' - preLoaderRoute: typeof NestedChildRouteImport - parentRoute: typeof rootRouteImport - } + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } +'/nested/': { + id: '/nested/' + path: '/nested' + fullPath: '/nested' + preLoaderRoute: typeof NestedIndexRouteImport + parentRoute: typeof rootRouteImport + } +'/nested/child': { + id: '/nested/child' + path: '/nested/child' + fullPath: '/nested/child' + preLoaderRoute: typeof NestedChildRouteImport + parentRoute: typeof rootRouteImport + } } } const rootRouteChildren: RootRouteChildren = { - IndexRoute: IndexRoute, - NestedChildRoute: NestedChildRoute, - NestedIndexRoute: NestedIndexRoute, + IndexRoute: IndexRoute,NestedChildRoute: NestedChildRoute,NestedIndexRoute: NestedIndexRoute } -export const routeTree = rootRouteImport - ._addFileChildren(rootRouteChildren) - ._addFileTypes() +export const routeTree = rootRouteImport._addFileChildren(rootRouteChildren)._addFileTypes() \ No newline at end of file