diff --git a/.changeset/bright-bats-fold.md b/.changeset/bright-bats-fold.md new file mode 100644 index 0000000000..514947525c --- /dev/null +++ b/.changeset/bright-bats-fold.md @@ -0,0 +1,5 @@ +--- +'@tanstack/router-generator': patch +--- + +Preserve escaped underscore segments when generating index routes across physical and virtual route trees, including pathless layouts, `physical()` prefixes, and `__virtual.ts` subtrees. diff --git a/packages/router-generator/src/filesystem/physical/getRouteNodes.ts b/packages/router-generator/src/filesystem/physical/getRouteNodes.ts index a4f72a8df7..09e7fc6c25 100644 --- a/packages/router-generator/src/filesystem/physical/getRouteNodes.ts +++ b/packages/router-generator/src/filesystem/physical/getRouteNodes.ts @@ -128,23 +128,26 @@ export async function getRouteNodes( const filePath = replaceBackslash( path.join(normalizedDir, node.filePath), ) - const prefixPath = cleanPath(`/${normalizedDir}`) - const routePath = cleanPath(`/${normalizedDir}${node.routePath}`) + const { routePath: prefixPath, originalRoutePath: originalPrefixPath } = + normalizedDir + ? determineInitialRoutePath(normalizedDir) + : { routePath: '', originalRoutePath: '' } + const routePath = cleanPath(`${prefixPath}${node.routePath}`) node.variableName = routePathToVariable( - cleanPath(`/${normalizedDir}/${removeExt(node.filePath)}`), + cleanPath(`${prefixPath}/${removeExt(node.filePath)}`), ) node._routePathSegmentMetadata = joinRoutePathSegmentMetadata( routePath, prefixPath, - undefined, + createRoutePathSegmentMetadata(prefixPath, originalPrefixPath), node._routePathSegmentMetadata, ) node.routePath = routePath // Keep originalRoutePath aligned with routePath for escape detection if (node.originalRoutePath) { node.originalRoutePath = cleanPath( - `/${normalizedDir}${node.originalRoutePath}`, + `${originalPrefixPath}${node.originalRoutePath}`, ) } node.filePath = filePath @@ -313,10 +316,6 @@ export async function getRouteNodes( routePath = '/' } - if (lastOriginalSegment === updatedLastRouteSegment) { - originalRoutePath = '/' - } - // For layout routes, don't use '/' fallback - an empty path means // "layout for the parent path" which is important for physical() mounts // where route.tsx at root should have empty path, not '/' diff --git a/packages/router-generator/src/filesystem/virtual/getRouteNodes.ts b/packages/router-generator/src/filesystem/virtual/getRouteNodes.ts index be07a648d7..509b1be321 100644 --- a/packages/router-generator/src/filesystem/virtual/getRouteNodes.ts +++ b/packages/router-generator/src/filesystem/virtual/getRouteNodes.ts @@ -152,6 +152,12 @@ export async function getRouteNodesRecursive( const children = await Promise.all( nodes.map(async (node) => { if (node.type === 'physical') { + const { + routePath: routePathPrefix, + originalRoutePath: originalRoutePathPrefix, + } = node.pathPrefix + ? determineInitialRoutePath(removeLeadingSlash(node.pathPrefix)) + : { routePath: '', originalRoutePath: '' } const { routeNodes, physicalDirectories } = await getRouteNodesPhysical( { ...tsrConfig, @@ -166,13 +172,16 @@ export async function getRouteNodesRecursive( ) routeNodes.forEach((subtreeNode) => { const pathPrefix = cleanPath( - `${parent?.routePath ?? ''}${node.pathPrefix}`, + `${parent?.routePath ?? ''}${routePathPrefix}`, + ) + const originalPathPrefix = cleanPath( + `${parent?.originalRoutePath ?? parent?.routePath ?? ''}${originalRoutePathPrefix}`, ) const literalPathPrefixSegments = createLiteralRoutePathSegmentMetadata(pathPrefix, parent, true) const routePath = cleanPath(`${pathPrefix}${subtreeNode.routePath}`) subtreeNode.variableName = routePathToVariable( - `${node.pathPrefix}/${removeExt(subtreeNode.filePath)}`, + `${routePathPrefix}/${removeExt(subtreeNode.filePath)}`, ) subtreeNode._routePathSegmentMetadata = joinRoutePathSegmentMetadata( routePath, @@ -184,7 +193,7 @@ export async function getRouteNodesRecursive( // Keep originalRoutePath aligned with routePath for escape detection if (subtreeNode.originalRoutePath) { subtreeNode.originalRoutePath = cleanPath( - `${parent?.originalRoutePath ?? parent?.routePath ?? ''}${node.pathPrefix}${subtreeNode.originalRoutePath}`, + `${originalPathPrefix}${subtreeNode.originalRoutePath}`, ) } subtreeNode.filePath = `${node.directory}/${subtreeNode.filePath}` @@ -208,11 +217,16 @@ export async function getRouteNodesRecursive( case 'index': { const { filePath, variableName, fullPath } = getFile(node.file) const routePath = `${parentRoutePath}/` + const originalRoutePath = `${parentOriginalRoutePath}/` return { filePath, fullPath, variableName, routePath, + originalRoutePath, + _routePathSegmentMetadata: parent?._routePathSegmentMetadata + ? [...parent._routePathSegmentMetadata] + : undefined, _fsRouteType: 'static', _virtualParentRoutePath: virtualParentRoutePath, } satisfies RouteNode diff --git a/packages/router-generator/tests/generator/physical-pathless-layout-escaped-underscore/routeTree.snapshot.ts b/packages/router-generator/tests/generator/physical-pathless-layout-escaped-underscore/routeTree.snapshot.ts index 6877c1ac1d..53a35bf149 100644 --- a/packages/router-generator/tests/generator/physical-pathless-layout-escaped-underscore/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/physical-pathless-layout-escaped-underscore/routeTree.snapshot.ts @@ -9,18 +9,32 @@ // 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 Char91__rootIndexChar93IndexRouteImport } from './routes/[__root-index]/index' import { Route as LayoutRouteImport } from './routes/_layout' import { Route as FooRouteImport } from './routes/[_]foo' import { Route as Char91__literalChar93RouteImport } from './routes/[__literal]' import { Route as _layoutRouteImport } from './routes/__layout' +import { Route as RootIndexIndexRouteImport } from './routes/[_]root-index/index' import { Route as LayoutNestedRouteImport } from './routes/_layout/_nested' import { Route as LayoutBarRouteImport } from './routes/_layout/[_]bar' import { Route as Layout_nested2RouteImport } from './routes/_layout/__nested2' import { Route as LayoutChar91__doubleChar93RouteImport } from './routes/_layout/[__double]' import { Route as _layoutQuxRouteImport } from './routes/__layout/[_]qux' +import { Route as LayoutNestedTrailingIndexRouteImport } from './routes/_layout/nested-trailing[_]/index' +import { Route as LayoutNestedIndexIndexRouteImport } from './routes/_layout/[_]nested-index/index' +import { Route as LayoutChar91__doubleIndexChar93IndexRouteImport } from './routes/_layout/[__double-index]/index' import { Route as LayoutNestedBazRouteImport } from './routes/_layout/_nested/[_]baz' +import { Route as LayoutNestedIndexIdRouteImport } from './routes/_layout/[_]nested-index/$id' import { Route as Layout_nested2DeepRouteImport } from './routes/_layout/__nested2/[_]deep' +import { Route as LayoutChar91__doubleIndexChar93IdRouteImport } from './routes/_layout/[__double-index]/$id' +import { Route as LayoutNestedDeepIndexIndexRouteImport } from './routes/_layout/_nested/[_]deep-index/index' +const Char91__rootIndexChar93IndexRoute = + Char91__rootIndexChar93IndexRouteImport.update({ + id: '/__root-index/', + path: '/__root-index/', + getParentRoute: () => rootRouteImport, + } as any) const LayoutRoute = LayoutRouteImport.update({ id: '/_layout', getParentRoute: () => rootRouteImport, @@ -39,6 +53,11 @@ const _layoutRoute = _layoutRouteImport.update({ id: '/__layout', getParentRoute: () => rootRouteImport, } as any) +const RootIndexIndexRoute = RootIndexIndexRouteImport.update({ + id: '/_root-index/', + path: '/_root-index/', + getParentRoute: () => rootRouteImport, +} as any) const LayoutNestedRoute = LayoutNestedRouteImport.update({ id: '/_nested', getParentRoute: () => LayoutRoute, @@ -63,16 +82,50 @@ const _layoutQuxRoute = _layoutQuxRouteImport.update({ path: '/_qux', getParentRoute: () => _layoutRoute, } as any) +const LayoutNestedTrailingIndexRoute = + LayoutNestedTrailingIndexRouteImport.update({ + id: '/nested-trailing_/', + path: '/nested-trailing_/', + getParentRoute: () => LayoutRoute, + } as any) +const LayoutNestedIndexIndexRoute = LayoutNestedIndexIndexRouteImport.update({ + id: '/_nested-index/', + path: '/_nested-index/', + getParentRoute: () => LayoutRoute, +} as any) +const LayoutChar91__doubleIndexChar93IndexRoute = + LayoutChar91__doubleIndexChar93IndexRouteImport.update({ + id: '/__double-index/', + path: '/__double-index/', + getParentRoute: () => LayoutRoute, + } as any) const LayoutNestedBazRoute = LayoutNestedBazRouteImport.update({ id: '/_baz', path: '/_baz', getParentRoute: () => LayoutNestedRoute, } as any) +const LayoutNestedIndexIdRoute = LayoutNestedIndexIdRouteImport.update({ + id: '/_nested-index/$id', + path: '/_nested-index/$id', + getParentRoute: () => LayoutRoute, +} as any) const Layout_nested2DeepRoute = Layout_nested2DeepRouteImport.update({ id: '/_deep', path: '/_deep', getParentRoute: () => Layout_nested2Route, } as any) +const LayoutChar91__doubleIndexChar93IdRoute = + LayoutChar91__doubleIndexChar93IdRouteImport.update({ + id: '/__double-index/$id', + path: '/__double-index/$id', + getParentRoute: () => LayoutRoute, + } as any) +const LayoutNestedDeepIndexIndexRoute = + LayoutNestedDeepIndexIndexRouteImport.update({ + id: '/_deep-index/', + path: '/_deep-index/', + getParentRoute: () => LayoutNestedRoute, + } as any) export interface FileRoutesByFullPath { '/': typeof LayoutNestedRouteWithChildren @@ -81,8 +134,16 @@ export interface FileRoutesByFullPath { '/_qux': typeof _layoutQuxRoute '/__double': typeof LayoutChar91__doubleChar93Route '/_bar': typeof LayoutBarRoute + '/__root-index/': typeof Char91__rootIndexChar93IndexRoute + '/_root-index/': typeof RootIndexIndexRoute + '/__double-index/$id': typeof LayoutChar91__doubleIndexChar93IdRoute '/_deep': typeof Layout_nested2DeepRoute + '/_nested-index/$id': typeof LayoutNestedIndexIdRoute '/_baz': typeof LayoutNestedBazRoute + '/__double-index/': typeof LayoutChar91__doubleIndexChar93IndexRoute + '/_nested-index/': typeof LayoutNestedIndexIndexRoute + '/nested-trailing_/': typeof LayoutNestedTrailingIndexRoute + '/_deep-index/': typeof LayoutNestedDeepIndexIndexRoute } export interface FileRoutesByTo { '/': typeof LayoutNestedRouteWithChildren @@ -91,8 +152,16 @@ export interface FileRoutesByTo { '/_qux': typeof _layoutQuxRoute '/__double': typeof LayoutChar91__doubleChar93Route '/_bar': typeof LayoutBarRoute + '/__root-index': typeof Char91__rootIndexChar93IndexRoute + '/_root-index': typeof RootIndexIndexRoute + '/__double-index/$id': typeof LayoutChar91__doubleIndexChar93IdRoute '/_deep': typeof Layout_nested2DeepRoute + '/_nested-index/$id': typeof LayoutNestedIndexIdRoute '/_baz': typeof LayoutNestedBazRoute + '/__double-index': typeof LayoutChar91__doubleIndexChar93IndexRoute + '/_nested-index': typeof LayoutNestedIndexIndexRoute + '/nested-trailing_': typeof LayoutNestedTrailingIndexRoute + '/_deep-index': typeof LayoutNestedDeepIndexIndexRoute } export interface FileRoutesById { __root__: typeof rootRouteImport @@ -105,8 +174,16 @@ export interface FileRoutesById { '/_layout/__nested2': typeof Layout_nested2RouteWithChildren '/_layout/_bar': typeof LayoutBarRoute '/_layout/_nested': typeof LayoutNestedRouteWithChildren + '/__root-index/': typeof Char91__rootIndexChar93IndexRoute + '/_root-index/': typeof RootIndexIndexRoute + '/_layout/__double-index/$id': typeof LayoutChar91__doubleIndexChar93IdRoute '/_layout/__nested2/_deep': typeof Layout_nested2DeepRoute + '/_layout/_nested-index/$id': typeof LayoutNestedIndexIdRoute '/_layout/_nested/_baz': typeof LayoutNestedBazRoute + '/_layout/__double-index/': typeof LayoutChar91__doubleIndexChar93IndexRoute + '/_layout/_nested-index/': typeof LayoutNestedIndexIndexRoute + '/_layout/nested-trailing_/': typeof LayoutNestedTrailingIndexRoute + '/_layout/_nested/_deep-index/': typeof LayoutNestedDeepIndexIndexRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath @@ -117,8 +194,16 @@ export interface FileRouteTypes { | '/_qux' | '/__double' | '/_bar' + | '/__root-index/' + | '/_root-index/' + | '/__double-index/$id' | '/_deep' + | '/_nested-index/$id' | '/_baz' + | '/__double-index/' + | '/_nested-index/' + | '/nested-trailing_/' + | '/_deep-index/' fileRoutesByTo: FileRoutesByTo to: | '/' @@ -127,8 +212,16 @@ export interface FileRouteTypes { | '/_qux' | '/__double' | '/_bar' + | '/__root-index' + | '/_root-index' + | '/__double-index/$id' | '/_deep' + | '/_nested-index/$id' | '/_baz' + | '/__double-index' + | '/_nested-index' + | '/nested-trailing_' + | '/_deep-index' id: | '__root__' | '/__layout' @@ -140,8 +233,16 @@ export interface FileRouteTypes { | '/_layout/__nested2' | '/_layout/_bar' | '/_layout/_nested' + | '/__root-index/' + | '/_root-index/' + | '/_layout/__double-index/$id' | '/_layout/__nested2/_deep' + | '/_layout/_nested-index/$id' | '/_layout/_nested/_baz' + | '/_layout/__double-index/' + | '/_layout/_nested-index/' + | '/_layout/nested-trailing_/' + | '/_layout/_nested/_deep-index/' fileRoutesById: FileRoutesById } export interface RootRouteChildren { @@ -149,10 +250,19 @@ export interface RootRouteChildren { Char91__literalChar93Route: typeof Char91__literalChar93Route FooRoute: typeof FooRoute LayoutRoute: typeof LayoutRouteWithChildren + Char91__rootIndexChar93IndexRoute: typeof Char91__rootIndexChar93IndexRoute + RootIndexIndexRoute: typeof RootIndexIndexRoute } declare module '@tanstack/react-router' { interface FileRoutesByPath { + '/__root-index/': { + id: '/__root-index/' + path: '/__root-index' + fullPath: '/__root-index/' + preLoaderRoute: typeof Char91__rootIndexChar93IndexRouteImport + parentRoute: typeof rootRouteImport + } '/_layout': { id: '/_layout' path: '' @@ -181,6 +291,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof _layoutRouteImport parentRoute: typeof rootRouteImport } + '/_root-index/': { + id: '/_root-index/' + path: '/_root-index' + fullPath: '/_root-index/' + preLoaderRoute: typeof RootIndexIndexRouteImport + parentRoute: typeof rootRouteImport + } '/_layout/_nested': { id: '/_layout/_nested' path: '' @@ -216,6 +333,27 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof _layoutQuxRouteImport parentRoute: typeof _layoutRoute } + '/_layout/nested-trailing_/': { + id: '/_layout/nested-trailing_/' + path: '/nested-trailing_' + fullPath: '/nested-trailing_/' + preLoaderRoute: typeof LayoutNestedTrailingIndexRouteImport + parentRoute: typeof LayoutRoute + } + '/_layout/_nested-index/': { + id: '/_layout/_nested-index/' + path: '/_nested-index' + fullPath: '/_nested-index/' + preLoaderRoute: typeof LayoutNestedIndexIndexRouteImport + parentRoute: typeof LayoutRoute + } + '/_layout/__double-index/': { + id: '/_layout/__double-index/' + path: '/__double-index' + fullPath: '/__double-index/' + preLoaderRoute: typeof LayoutChar91__doubleIndexChar93IndexRouteImport + parentRoute: typeof LayoutRoute + } '/_layout/_nested/_baz': { id: '/_layout/_nested/_baz' path: '/_baz' @@ -223,6 +361,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutNestedBazRouteImport parentRoute: typeof LayoutNestedRoute } + '/_layout/_nested-index/$id': { + id: '/_layout/_nested-index/$id' + path: '/_nested-index/$id' + fullPath: '/_nested-index/$id' + preLoaderRoute: typeof LayoutNestedIndexIdRouteImport + parentRoute: typeof LayoutRoute + } '/_layout/__nested2/_deep': { id: '/_layout/__nested2/_deep' path: '/_deep' @@ -230,6 +375,20 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof Layout_nested2DeepRouteImport parentRoute: typeof Layout_nested2Route } + '/_layout/__double-index/$id': { + id: '/_layout/__double-index/$id' + path: '/__double-index/$id' + fullPath: '/__double-index/$id' + preLoaderRoute: typeof LayoutChar91__doubleIndexChar93IdRouteImport + parentRoute: typeof LayoutRoute + } + '/_layout/_nested/_deep-index/': { + id: '/_layout/_nested/_deep-index/' + path: '/_deep-index' + fullPath: '/_deep-index/' + preLoaderRoute: typeof LayoutNestedDeepIndexIndexRouteImport + parentRoute: typeof LayoutNestedRoute + } } } @@ -258,10 +417,12 @@ const Layout_nested2RouteWithChildren = Layout_nested2Route._addFileChildren( interface LayoutNestedRouteChildren { LayoutNestedBazRoute: typeof LayoutNestedBazRoute + LayoutNestedDeepIndexIndexRoute: typeof LayoutNestedDeepIndexIndexRoute } const LayoutNestedRouteChildren: LayoutNestedRouteChildren = { LayoutNestedBazRoute: LayoutNestedBazRoute, + LayoutNestedDeepIndexIndexRoute: LayoutNestedDeepIndexIndexRoute, } const LayoutNestedRouteWithChildren = LayoutNestedRoute._addFileChildren( @@ -273,6 +434,11 @@ interface LayoutRouteChildren { Layout_nested2Route: typeof Layout_nested2RouteWithChildren LayoutBarRoute: typeof LayoutBarRoute LayoutNestedRoute: typeof LayoutNestedRouteWithChildren + LayoutChar91__doubleIndexChar93IdRoute: typeof LayoutChar91__doubleIndexChar93IdRoute + LayoutNestedIndexIdRoute: typeof LayoutNestedIndexIdRoute + LayoutChar91__doubleIndexChar93IndexRoute: typeof LayoutChar91__doubleIndexChar93IndexRoute + LayoutNestedIndexIndexRoute: typeof LayoutNestedIndexIndexRoute + LayoutNestedTrailingIndexRoute: typeof LayoutNestedTrailingIndexRoute } const LayoutRouteChildren: LayoutRouteChildren = { @@ -280,6 +446,13 @@ const LayoutRouteChildren: LayoutRouteChildren = { Layout_nested2Route: Layout_nested2RouteWithChildren, LayoutBarRoute: LayoutBarRoute, LayoutNestedRoute: LayoutNestedRouteWithChildren, + LayoutChar91__doubleIndexChar93IdRoute: + LayoutChar91__doubleIndexChar93IdRoute, + LayoutNestedIndexIdRoute: LayoutNestedIndexIdRoute, + LayoutChar91__doubleIndexChar93IndexRoute: + LayoutChar91__doubleIndexChar93IndexRoute, + LayoutNestedIndexIndexRoute: LayoutNestedIndexIndexRoute, + LayoutNestedTrailingIndexRoute: LayoutNestedTrailingIndexRoute, } const LayoutRouteWithChildren = @@ -290,6 +463,8 @@ const rootRouteChildren: RootRouteChildren = { Char91__literalChar93Route: Char91__literalChar93Route, FooRoute: FooRoute, LayoutRoute: LayoutRouteWithChildren, + Char91__rootIndexChar93IndexRoute: Char91__rootIndexChar93IndexRoute, + RootIndexIndexRoute: RootIndexIndexRoute, } export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) diff --git a/packages/router-generator/tests/generator/physical-pathless-layout-escaped-underscore/routes/[_]root-index/index.tsx b/packages/router-generator/tests/generator/physical-pathless-layout-escaped-underscore/routes/[_]root-index/index.tsx new file mode 100644 index 0000000000..39387c7250 --- /dev/null +++ b/packages/router-generator/tests/generator/physical-pathless-layout-escaped-underscore/routes/[_]root-index/index.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_root-index/')({ + component: () => 'Root Index', +}) diff --git a/packages/router-generator/tests/generator/physical-pathless-layout-escaped-underscore/routes/[__root-index]/index.tsx b/packages/router-generator/tests/generator/physical-pathless-layout-escaped-underscore/routes/[__root-index]/index.tsx new file mode 100644 index 0000000000..9945c7d300 --- /dev/null +++ b/packages/router-generator/tests/generator/physical-pathless-layout-escaped-underscore/routes/[__root-index]/index.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/__root-index/')({ + component: () => 'Double Root Index', +}) diff --git a/packages/router-generator/tests/generator/physical-pathless-layout-escaped-underscore/routes/_layout/[_]nested-index/$id.tsx b/packages/router-generator/tests/generator/physical-pathless-layout-escaped-underscore/routes/_layout/[_]nested-index/$id.tsx new file mode 100644 index 0000000000..e09396020b --- /dev/null +++ b/packages/router-generator/tests/generator/physical-pathless-layout-escaped-underscore/routes/_layout/[_]nested-index/$id.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_layout/_nested-index/$id')({ + component: () => 'Nested Id', +}) diff --git a/packages/router-generator/tests/generator/physical-pathless-layout-escaped-underscore/routes/_layout/[_]nested-index/index.tsx b/packages/router-generator/tests/generator/physical-pathless-layout-escaped-underscore/routes/_layout/[_]nested-index/index.tsx new file mode 100644 index 0000000000..a5f8f95497 --- /dev/null +++ b/packages/router-generator/tests/generator/physical-pathless-layout-escaped-underscore/routes/_layout/[_]nested-index/index.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_layout/_nested-index/')({ + component: () => 'Nested Index', +}) diff --git a/packages/router-generator/tests/generator/physical-pathless-layout-escaped-underscore/routes/_layout/[__double-index]/$id.tsx b/packages/router-generator/tests/generator/physical-pathless-layout-escaped-underscore/routes/_layout/[__double-index]/$id.tsx new file mode 100644 index 0000000000..2a31adf60c --- /dev/null +++ b/packages/router-generator/tests/generator/physical-pathless-layout-escaped-underscore/routes/_layout/[__double-index]/$id.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_layout/__double-index/$id')({ + component: () => 'Double Id', +}) diff --git a/packages/router-generator/tests/generator/physical-pathless-layout-escaped-underscore/routes/_layout/[__double-index]/index.tsx b/packages/router-generator/tests/generator/physical-pathless-layout-escaped-underscore/routes/_layout/[__double-index]/index.tsx new file mode 100644 index 0000000000..231e787df0 --- /dev/null +++ b/packages/router-generator/tests/generator/physical-pathless-layout-escaped-underscore/routes/_layout/[__double-index]/index.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_layout/__double-index/')({ + component: () => 'Double Index', +}) diff --git a/packages/router-generator/tests/generator/physical-pathless-layout-escaped-underscore/routes/_layout/_nested/[_]deep-index/index.tsx b/packages/router-generator/tests/generator/physical-pathless-layout-escaped-underscore/routes/_layout/_nested/[_]deep-index/index.tsx new file mode 100644 index 0000000000..4113450ea1 --- /dev/null +++ b/packages/router-generator/tests/generator/physical-pathless-layout-escaped-underscore/routes/_layout/_nested/[_]deep-index/index.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_layout/_nested/_deep-index/')({ + component: () => 'Deep Index', +}) diff --git a/packages/router-generator/tests/generator/physical-pathless-layout-escaped-underscore/routes/_layout/nested-trailing[_]/index.tsx b/packages/router-generator/tests/generator/physical-pathless-layout-escaped-underscore/routes/_layout/nested-trailing[_]/index.tsx new file mode 100644 index 0000000000..786604a503 --- /dev/null +++ b/packages/router-generator/tests/generator/physical-pathless-layout-escaped-underscore/routes/_layout/nested-trailing[_]/index.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_layout/nested-trailing_/')({ + component: () => 'Nested Trailing Index', +}) diff --git a/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routeTree.snapshot.ts b/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routeTree.snapshot.ts index c708ac47c0..70d06b2004 100644 --- a/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routeTree.snapshot.ts @@ -13,11 +13,22 @@ import { Route as layoutRouteImport } from './routes/layout' import { Route as fooRouteImport } from './routes/foo' import { Route as escapedRouteImport } from './routes/escaped' import { Route as doubleRouteImport } from './routes/double' +import { Route as rootIdRouteImport } from './routes/root-id' import { Route as nestedLayoutRouteImport } from './routes/nested-layout' +import { Route as innerLayoutRouteImport } from './routes/inner-layout' import { Route as escapedBarRouteImport } from './routes/escaped-bar' import { Route as barRouteImport } from './routes/bar' import { Route as doubleBarRouteImport } from './routes/double-bar' +import { Route as rootIndexRouteImport } from './routes/root-index' import { Route as bazRouteImport } from './routes/baz' +import { Route as nestedIdRouteImport } from './routes/nested-id' +import { Route as escapedIdRouteImport } from './routes/escaped-id' +import { Route as doubleIdRouteImport } from './routes/double-id' +import { Route as trailIndexRouteImport } from './routes/trail-index' +import { Route as nestedIndexRouteImport } from './routes/nested-index' +import { Route as escapedIndexRouteImport } from './routes/escaped-index' +import { Route as doubleIndexRouteImport } from './routes/double-index' +import { Route as deepIndexRouteImport } from './routes/deep-index' const layoutRoute = layoutRouteImport.update({ id: '/_layout', @@ -38,10 +49,19 @@ const doubleRoute = doubleRouteImport.update({ path: '/__double', getParentRoute: () => rootRouteImport, } as any) +const rootIdRoute = rootIdRouteImport.update({ + id: '/_root-index/$id', + path: '/_root-index/$id', + getParentRoute: () => rootRouteImport, +} as any) const nestedLayoutRoute = nestedLayoutRouteImport.update({ id: '/_nested', getParentRoute: () => layoutRoute, } as any) +const innerLayoutRoute = innerLayoutRouteImport.update({ + id: '/_inner', + getParentRoute: () => layoutRoute, +} as any) const escapedBarRoute = escapedBarRouteImport.update({ id: '/_escaped-bar', path: '/_escaped-bar', @@ -57,31 +77,96 @@ const doubleBarRoute = doubleBarRouteImport.update({ path: '/__double-bar', getParentRoute: () => layoutRoute, } as any) +const rootIndexRoute = rootIndexRouteImport.update({ + id: '/_root-index/', + path: '/_root-index/', + getParentRoute: () => rootRouteImport, +} as any) const bazRoute = bazRouteImport.update({ id: '/_baz', path: '/_baz', getParentRoute: () => nestedLayoutRoute, } as any) +const nestedIdRoute = nestedIdRouteImport.update({ + id: '/_nested-index/$id', + path: '/_nested-index/$id', + getParentRoute: () => layoutRoute, +} as any) +const escapedIdRoute = escapedIdRouteImport.update({ + id: '/_escaped-index/$id', + path: '/_escaped-index/$id', + getParentRoute: () => layoutRoute, +} as any) +const doubleIdRoute = doubleIdRouteImport.update({ + id: '/__double-index/$id', + path: '/__double-index/$id', + getParentRoute: () => layoutRoute, +} as any) +const trailIndexRoute = trailIndexRouteImport.update({ + id: '/trail_/', + path: '/trail_/', + getParentRoute: () => layoutRoute, +} as any) +const nestedIndexRoute = nestedIndexRouteImport.update({ + id: '/_nested-index/', + path: '/_nested-index/', + getParentRoute: () => layoutRoute, +} as any) +const escapedIndexRoute = escapedIndexRouteImport.update({ + id: '/_escaped-index/', + path: '/_escaped-index/', + getParentRoute: () => layoutRoute, +} as any) +const doubleIndexRoute = doubleIndexRouteImport.update({ + id: '/__double-index/', + path: '/__double-index/', + getParentRoute: () => layoutRoute, +} as any) +const deepIndexRoute = deepIndexRouteImport.update({ + id: '/_deep-index/', + path: '/_deep-index/', + getParentRoute: () => innerLayoutRoute, +} as any) export interface FileRoutesByFullPath { '/__double': typeof doubleRoute '/_escaped': typeof escapedRoute '/_foo': typeof fooRoute '/': typeof nestedLayoutRouteWithChildren + '/_root-index/': typeof rootIndexRoute '/__double-bar': typeof doubleBarRoute '/_bar': typeof barRoute '/_escaped-bar': typeof escapedBarRoute + '/_root-index/$id': typeof rootIdRoute + '/__double-index/': typeof doubleIndexRoute + '/_escaped-index/': typeof escapedIndexRoute + '/_nested-index/': typeof nestedIndexRoute + '/trail_/': typeof trailIndexRoute + '/__double-index/$id': typeof doubleIdRoute + '/_escaped-index/$id': typeof escapedIdRoute + '/_nested-index/$id': typeof nestedIdRoute '/_baz': typeof bazRoute + '/_deep-index/': typeof deepIndexRoute } export interface FileRoutesByTo { '/__double': typeof doubleRoute '/_escaped': typeof escapedRoute '/_foo': typeof fooRoute '/': typeof nestedLayoutRouteWithChildren + '/_root-index': typeof rootIndexRoute '/__double-bar': typeof doubleBarRoute '/_bar': typeof barRoute '/_escaped-bar': typeof escapedBarRoute + '/_root-index/$id': typeof rootIdRoute + '/__double-index': typeof doubleIndexRoute + '/_escaped-index': typeof escapedIndexRoute + '/_nested-index': typeof nestedIndexRoute + '/trail_': typeof trailIndexRoute + '/__double-index/$id': typeof doubleIdRoute + '/_escaped-index/$id': typeof escapedIdRoute + '/_nested-index/$id': typeof nestedIdRoute '/_baz': typeof bazRoute + '/_deep-index': typeof deepIndexRoute } export interface FileRoutesById { __root__: typeof rootRouteImport @@ -89,11 +174,22 @@ export interface FileRoutesById { '/_escaped': typeof escapedRoute '/_foo': typeof fooRoute '/_layout': typeof layoutRouteWithChildren + '/_root-index/': typeof rootIndexRoute '/_layout/__double-bar': typeof doubleBarRoute '/_layout/_bar': typeof barRoute '/_layout/_escaped-bar': typeof escapedBarRoute + '/_layout/_inner': typeof innerLayoutRouteWithChildren '/_layout/_nested': typeof nestedLayoutRouteWithChildren + '/_root-index/$id': typeof rootIdRoute + '/_layout/__double-index/': typeof doubleIndexRoute + '/_layout/_escaped-index/': typeof escapedIndexRoute + '/_layout/_nested-index/': typeof nestedIndexRoute + '/_layout/trail_/': typeof trailIndexRoute + '/_layout/__double-index/$id': typeof doubleIdRoute + '/_layout/_escaped-index/$id': typeof escapedIdRoute + '/_layout/_nested-index/$id': typeof nestedIdRoute '/_layout/_nested/_baz': typeof bazRoute + '/_layout/_inner/_deep-index/': typeof deepIndexRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath @@ -102,31 +198,62 @@ export interface FileRouteTypes { | '/_escaped' | '/_foo' | '/' + | '/_root-index/' | '/__double-bar' | '/_bar' | '/_escaped-bar' + | '/_root-index/$id' + | '/__double-index/' + | '/_escaped-index/' + | '/_nested-index/' + | '/trail_/' + | '/__double-index/$id' + | '/_escaped-index/$id' + | '/_nested-index/$id' | '/_baz' + | '/_deep-index/' fileRoutesByTo: FileRoutesByTo to: | '/__double' | '/_escaped' | '/_foo' | '/' + | '/_root-index' | '/__double-bar' | '/_bar' | '/_escaped-bar' + | '/_root-index/$id' + | '/__double-index' + | '/_escaped-index' + | '/_nested-index' + | '/trail_' + | '/__double-index/$id' + | '/_escaped-index/$id' + | '/_nested-index/$id' | '/_baz' + | '/_deep-index' id: | '__root__' | '/__double' | '/_escaped' | '/_foo' | '/_layout' + | '/_root-index/' | '/_layout/__double-bar' | '/_layout/_bar' | '/_layout/_escaped-bar' + | '/_layout/_inner' | '/_layout/_nested' + | '/_root-index/$id' + | '/_layout/__double-index/' + | '/_layout/_escaped-index/' + | '/_layout/_nested-index/' + | '/_layout/trail_/' + | '/_layout/__double-index/$id' + | '/_layout/_escaped-index/$id' + | '/_layout/_nested-index/$id' | '/_layout/_nested/_baz' + | '/_layout/_inner/_deep-index/' fileRoutesById: FileRoutesById } export interface RootRouteChildren { @@ -134,6 +261,8 @@ export interface RootRouteChildren { escapedRoute: typeof escapedRoute fooRoute: typeof fooRoute layoutRoute: typeof layoutRouteWithChildren + rootIndexRoute: typeof rootIndexRoute + rootIdRoute: typeof rootIdRoute } declare module '@tanstack/react-router' { @@ -166,6 +295,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof doubleRouteImport parentRoute: typeof rootRouteImport } + '/_root-index/$id': { + id: '/_root-index/$id' + path: '/_root-index/$id' + fullPath: '/_root-index/$id' + preLoaderRoute: typeof rootIdRouteImport + parentRoute: typeof rootRouteImport + } '/_layout/_nested': { id: '/_layout/_nested' path: '' @@ -173,6 +309,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof nestedLayoutRouteImport parentRoute: typeof layoutRoute } + '/_layout/_inner': { + id: '/_layout/_inner' + path: '' + fullPath: '/' + preLoaderRoute: typeof innerLayoutRouteImport + parentRoute: typeof layoutRoute + } '/_layout/_escaped-bar': { id: '/_layout/_escaped-bar' path: '/_escaped-bar' @@ -194,6 +337,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof doubleBarRouteImport parentRoute: typeof layoutRoute } + '/_root-index/': { + id: '/_root-index/' + path: '/_root-index' + fullPath: '/_root-index/' + preLoaderRoute: typeof rootIndexRouteImport + parentRoute: typeof rootRouteImport + } '/_layout/_nested/_baz': { id: '/_layout/_nested/_baz' path: '/_baz' @@ -201,9 +351,77 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof bazRouteImport parentRoute: typeof nestedLayoutRoute } + '/_layout/_nested-index/$id': { + id: '/_layout/_nested-index/$id' + path: '/_nested-index/$id' + fullPath: '/_nested-index/$id' + preLoaderRoute: typeof nestedIdRouteImport + parentRoute: typeof layoutRoute + } + '/_layout/_escaped-index/$id': { + id: '/_layout/_escaped-index/$id' + path: '/_escaped-index/$id' + fullPath: '/_escaped-index/$id' + preLoaderRoute: typeof escapedIdRouteImport + parentRoute: typeof layoutRoute + } + '/_layout/__double-index/$id': { + id: '/_layout/__double-index/$id' + path: '/__double-index/$id' + fullPath: '/__double-index/$id' + preLoaderRoute: typeof doubleIdRouteImport + parentRoute: typeof layoutRoute + } + '/_layout/trail_/': { + id: '/_layout/trail_/' + path: '/trail_' + fullPath: '/trail_/' + preLoaderRoute: typeof trailIndexRouteImport + parentRoute: typeof layoutRoute + } + '/_layout/_nested-index/': { + id: '/_layout/_nested-index/' + path: '/_nested-index' + fullPath: '/_nested-index/' + preLoaderRoute: typeof nestedIndexRouteImport + parentRoute: typeof layoutRoute + } + '/_layout/_escaped-index/': { + id: '/_layout/_escaped-index/' + path: '/_escaped-index' + fullPath: '/_escaped-index/' + preLoaderRoute: typeof escapedIndexRouteImport + parentRoute: typeof layoutRoute + } + '/_layout/__double-index/': { + id: '/_layout/__double-index/' + path: '/__double-index' + fullPath: '/__double-index/' + preLoaderRoute: typeof doubleIndexRouteImport + parentRoute: typeof layoutRoute + } + '/_layout/_inner/_deep-index/': { + id: '/_layout/_inner/_deep-index/' + path: '/_deep-index' + fullPath: '/_deep-index/' + preLoaderRoute: typeof deepIndexRouteImport + parentRoute: typeof innerLayoutRoute + } } } +interface innerLayoutRouteChildren { + deepIndexRoute: typeof deepIndexRoute +} + +const innerLayoutRouteChildren: innerLayoutRouteChildren = { + deepIndexRoute: deepIndexRoute, +} + +const innerLayoutRouteWithChildren = innerLayoutRoute._addFileChildren( + innerLayoutRouteChildren, +) + interface nestedLayoutRouteChildren { bazRoute: typeof bazRoute } @@ -220,14 +438,30 @@ interface layoutRouteChildren { doubleBarRoute: typeof doubleBarRoute barRoute: typeof barRoute escapedBarRoute: typeof escapedBarRoute + innerLayoutRoute: typeof innerLayoutRouteWithChildren nestedLayoutRoute: typeof nestedLayoutRouteWithChildren + doubleIndexRoute: typeof doubleIndexRoute + escapedIndexRoute: typeof escapedIndexRoute + nestedIndexRoute: typeof nestedIndexRoute + trailIndexRoute: typeof trailIndexRoute + doubleIdRoute: typeof doubleIdRoute + escapedIdRoute: typeof escapedIdRoute + nestedIdRoute: typeof nestedIdRoute } const layoutRouteChildren: layoutRouteChildren = { doubleBarRoute: doubleBarRoute, barRoute: barRoute, escapedBarRoute: escapedBarRoute, + innerLayoutRoute: innerLayoutRouteWithChildren, nestedLayoutRoute: nestedLayoutRouteWithChildren, + doubleIndexRoute: doubleIndexRoute, + escapedIndexRoute: escapedIndexRoute, + nestedIndexRoute: nestedIndexRoute, + trailIndexRoute: trailIndexRoute, + doubleIdRoute: doubleIdRoute, + escapedIdRoute: escapedIdRoute, + nestedIdRoute: nestedIdRoute, } const layoutRouteWithChildren = @@ -238,6 +472,8 @@ const rootRouteChildren: RootRouteChildren = { escapedRoute: escapedRoute, fooRoute: fooRoute, layoutRoute: layoutRouteWithChildren, + rootIndexRoute: rootIndexRoute, + rootIdRoute: rootIdRoute, } export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) diff --git a/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routes.ts b/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routes.ts index 27aac72e77..34ebbc111e 100644 --- a/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routes.ts +++ b/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routes.ts @@ -1,13 +1,30 @@ -import { layout, rootRoute, route } from '@tanstack/virtual-file-routes' +import { index, layout, rootRoute, route } from '@tanstack/virtual-file-routes' export const routes = rootRoute('__root.tsx', [ route('/_foo', 'foo.tsx'), route('/[_]escaped', 'escaped.tsx'), route('/__double', 'double.tsx'), + route('/_root-index', [index('root-index.tsx'), route('$id', 'root-id.tsx')]), layout('_layout', 'layout.tsx', [ route('/_bar', 'bar.tsx'), route('/[_]escaped-bar', 'escaped-bar.tsx'), route('/__double-bar', 'double-bar.tsx'), + route('/_nested-index', [ + index('nested-index.tsx'), + route('$id', 'nested-id.tsx'), + ]), + route('/[_]escaped-index', [ + index('escaped-index.tsx'), + route('$id', 'escaped-id.tsx'), + ]), + route('/__double-index', [ + index('double-index.tsx'), + route('$id', 'double-id.tsx'), + ]), + route('/trail_', [index('trail-index.tsx')]), layout('_nested', 'nested-layout.tsx', [route('/_baz', 'baz.tsx')]), + layout('_inner', 'inner-layout.tsx', [ + route('/_deep-index', [index('deep-index.tsx')]), + ]), ]), ]) diff --git a/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routes/deep-index.tsx b/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routes/deep-index.tsx new file mode 100644 index 0000000000..1952b00184 --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routes/deep-index.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_layout/_inner/_deep-index/')({ + component: () => 'Deep Index', +}) diff --git a/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routes/double-id.tsx b/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routes/double-id.tsx new file mode 100644 index 0000000000..2a31adf60c --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routes/double-id.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_layout/__double-index/$id')({ + component: () => 'Double Id', +}) diff --git a/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routes/double-index.tsx b/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routes/double-index.tsx new file mode 100644 index 0000000000..231e787df0 --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routes/double-index.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_layout/__double-index/')({ + component: () => 'Double Index', +}) diff --git a/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routes/escaped-id.tsx b/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routes/escaped-id.tsx new file mode 100644 index 0000000000..a9fad62448 --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routes/escaped-id.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_layout/_escaped-index/$id')({ + component: () => 'Escaped Id', +}) diff --git a/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routes/escaped-index.tsx b/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routes/escaped-index.tsx new file mode 100644 index 0000000000..9b81fcbadd --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routes/escaped-index.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_layout/_escaped-index/')({ + component: () => 'Escaped Index', +}) diff --git a/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routes/inner-layout.tsx b/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routes/inner-layout.tsx new file mode 100644 index 0000000000..5266c32a7c --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routes/inner-layout.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_layout/_inner')({ + component: () => 'Inner Layout', +}) diff --git a/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routes/nested-id.tsx b/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routes/nested-id.tsx new file mode 100644 index 0000000000..e09396020b --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routes/nested-id.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_layout/_nested-index/$id')({ + component: () => 'Nested Id', +}) diff --git a/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routes/nested-index.tsx b/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routes/nested-index.tsx new file mode 100644 index 0000000000..a5f8f95497 --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routes/nested-index.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_layout/_nested-index/')({ + component: () => 'Nested Index', +}) diff --git a/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routes/root-id.tsx b/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routes/root-id.tsx new file mode 100644 index 0000000000..ec329adfd3 --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routes/root-id.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_root-index/$id')({ + component: () => 'Root Id', +}) diff --git a/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routes/root-index.tsx b/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routes/root-index.tsx new file mode 100644 index 0000000000..39387c7250 --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routes/root-index.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_root-index/')({ + component: () => 'Root Index', +}) diff --git a/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routes/trail-index.tsx b/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routes/trail-index.tsx new file mode 100644 index 0000000000..7794e8f0b3 --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-pathless-layout-escaped-underscore/routes/trail-index.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_layout/trail_/')({ + component: () => 'Trail Index', +}) diff --git a/packages/router-generator/tests/generator/virtual-physical-pathless-layout-literal-index/routeTree.snapshot.ts b/packages/router-generator/tests/generator/virtual-physical-pathless-layout-literal-index/routeTree.snapshot.ts new file mode 100644 index 0000000000..b0fd76d1ef --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-physical-pathless-layout-literal-index/routeTree.snapshot.ts @@ -0,0 +1,104 @@ +/* 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 Char91_Char93nestedIndexRouteImport } from './routes/physical-routes/[_]nested/index' +import { Route as Char91_Char93nestedIdRouteImport } from './routes/physical-routes/[_]nested/$id' + +const layoutRoute = layoutRouteImport.update({ + id: '/_layout', + getParentRoute: () => rootRouteImport, +} as any) +const Char91_Char93nestedIndexRoute = + Char91_Char93nestedIndexRouteImport.update({ + id: '/_nested/', + path: '/_nested/', + getParentRoute: () => layoutRoute, + } as any) +const Char91_Char93nestedIdRoute = Char91_Char93nestedIdRouteImport.update({ + id: '/_nested/$id', + path: '/_nested/$id', + getParentRoute: () => layoutRoute, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof layoutRouteWithChildren + '/_nested/$id': typeof Char91_Char93nestedIdRoute + '/_nested/': typeof Char91_Char93nestedIndexRoute +} +export interface FileRoutesByTo { + '/': typeof layoutRouteWithChildren + '/_nested/$id': typeof Char91_Char93nestedIdRoute + '/_nested': typeof Char91_Char93nestedIndexRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/_layout': typeof layoutRouteWithChildren + '/_layout/_nested/$id': typeof Char91_Char93nestedIdRoute + '/_layout/_nested/': typeof Char91_Char93nestedIndexRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' | '/_nested/$id' | '/_nested/' + fileRoutesByTo: FileRoutesByTo + to: '/' | '/_nested/$id' | '/_nested' + id: '__root__' | '/_layout' | '/_layout/_nested/$id' | '/_layout/_nested/' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + layoutRoute: typeof layoutRouteWithChildren +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/_layout': { + id: '/_layout' + path: '' + fullPath: '/' + preLoaderRoute: typeof layoutRouteImport + parentRoute: typeof rootRouteImport + } + '/_layout/_nested/': { + id: '/_layout/_nested/' + path: '/_nested' + fullPath: '/_nested/' + preLoaderRoute: typeof Char91_Char93nestedIndexRouteImport + parentRoute: typeof layoutRoute + } + '/_layout/_nested/$id': { + id: '/_layout/_nested/$id' + path: '/_nested/$id' + fullPath: '/_nested/$id' + preLoaderRoute: typeof Char91_Char93nestedIdRouteImport + parentRoute: typeof layoutRoute + } + } +} + +interface layoutRouteChildren { + Char91_Char93nestedIdRoute: typeof Char91_Char93nestedIdRoute + Char91_Char93nestedIndexRoute: typeof Char91_Char93nestedIndexRoute +} + +const layoutRouteChildren: layoutRouteChildren = { + Char91_Char93nestedIdRoute: Char91_Char93nestedIdRoute, + Char91_Char93nestedIndexRoute: Char91_Char93nestedIndexRoute, +} + +const layoutRouteWithChildren = + layoutRoute._addFileChildren(layoutRouteChildren) + +const rootRouteChildren: RootRouteChildren = { + layoutRoute: layoutRouteWithChildren, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/packages/router-generator/tests/generator/virtual-physical-pathless-layout-literal-index/routes.ts b/packages/router-generator/tests/generator/virtual-physical-pathless-layout-literal-index/routes.ts new file mode 100644 index 0000000000..8cd9a3a353 --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-physical-pathless-layout-literal-index/routes.ts @@ -0,0 +1,5 @@ +import { layout, physical, rootRoute } from '@tanstack/virtual-file-routes' + +export const routes = rootRoute('__root.tsx', [ + layout('_layout', 'layout.tsx', [physical('', 'physical-routes')]), +]) diff --git a/packages/router-generator/tests/generator/virtual-physical-pathless-layout-literal-index/routes/__root.tsx b/packages/router-generator/tests/generator/virtual-physical-pathless-layout-literal-index/routes/__root.tsx new file mode 100644 index 0000000000..9c657c7d5b --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-physical-pathless-layout-literal-index/routes/__root.tsx @@ -0,0 +1,3 @@ +import { createRootRoute } from '@tanstack/react-router' + +export const Route = createRootRoute() diff --git a/packages/router-generator/tests/generator/virtual-physical-pathless-layout-literal-index/routes/layout.tsx b/packages/router-generator/tests/generator/virtual-physical-pathless-layout-literal-index/routes/layout.tsx new file mode 100644 index 0000000000..9c1673dafd --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-physical-pathless-layout-literal-index/routes/layout.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_layout')({ + component: () => 'Layout', +}) diff --git a/packages/router-generator/tests/generator/virtual-physical-pathless-layout-literal-index/routes/physical-routes/[_]nested/$id.tsx b/packages/router-generator/tests/generator/virtual-physical-pathless-layout-literal-index/routes/physical-routes/[_]nested/$id.tsx new file mode 100644 index 0000000000..4b06b8799e --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-physical-pathless-layout-literal-index/routes/physical-routes/[_]nested/$id.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_layout/_nested/$id')({ + component: () => 'Nested Id', +}) diff --git a/packages/router-generator/tests/generator/virtual-physical-pathless-layout-literal-index/routes/physical-routes/[_]nested/index.tsx b/packages/router-generator/tests/generator/virtual-physical-pathless-layout-literal-index/routes/physical-routes/[_]nested/index.tsx new file mode 100644 index 0000000000..b8eca0e427 --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-physical-pathless-layout-literal-index/routes/physical-routes/[_]nested/index.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_layout/_nested/')({ + component: () => 'Nested Index', +}) diff --git a/packages/router-generator/tests/generator/virtual-physical-pathless-layout-literal-index/tsr.config.json b/packages/router-generator/tests/generator/virtual-physical-pathless-layout-literal-index/tsr.config.json new file mode 100644 index 0000000000..4d587108e3 --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-physical-pathless-layout-literal-index/tsr.config.json @@ -0,0 +1,5 @@ +{ + "routesDirectory": "./routes", + "generatedRouteTree": "./routeTree.gen.ts", + "virtualRouteConfig": "./routes.ts" +} diff --git a/packages/router-generator/tests/generator/virtual-physical-prefix-escaped-underscore/routeTree.snapshot.ts b/packages/router-generator/tests/generator/virtual-physical-prefix-escaped-underscore/routeTree.snapshot.ts new file mode 100644 index 0000000000..63b1df0f40 --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-physical-prefix-escaped-underscore/routeTree.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 ApiIndexRouteImport } from './routes/physical-routes/index' +import { Route as ApiIdRouteImport } from './routes/physical-routes/$id' + +const ApiIndexRoute = ApiIndexRouteImport.update({ + id: '/_api/', + path: '/_api/', + getParentRoute: () => rootRouteImport, +} as any) +const ApiIdRoute = ApiIdRouteImport.update({ + id: '/_api/$id', + path: '/_api/$id', + getParentRoute: () => rootRouteImport, +} as any) + +export interface FileRoutesByFullPath { + '/_api/$id': typeof ApiIdRoute + '/_api/': typeof ApiIndexRoute +} +export interface FileRoutesByTo { + '/_api/$id': typeof ApiIdRoute + '/_api': typeof ApiIndexRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/_api/$id': typeof ApiIdRoute + '/_api/': typeof ApiIndexRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/_api/$id' | '/_api/' + fileRoutesByTo: FileRoutesByTo + to: '/_api/$id' | '/_api' + id: '__root__' | '/_api/$id' | '/_api/' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + ApiIdRoute: typeof ApiIdRoute + ApiIndexRoute: typeof ApiIndexRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/_api/': { + id: '/_api/' + path: '/_api' + fullPath: '/_api/' + preLoaderRoute: typeof ApiIndexRouteImport + parentRoute: typeof rootRouteImport + } + '/_api/$id': { + id: '/_api/$id' + path: '/_api/$id' + fullPath: '/_api/$id' + preLoaderRoute: typeof ApiIdRouteImport + parentRoute: typeof rootRouteImport + } + } +} + +const rootRouteChildren: RootRouteChildren = { + ApiIdRoute: ApiIdRoute, + ApiIndexRoute: ApiIndexRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/packages/router-generator/tests/generator/virtual-physical-prefix-escaped-underscore/routes.ts b/packages/router-generator/tests/generator/virtual-physical-prefix-escaped-underscore/routes.ts new file mode 100644 index 0000000000..8e3883cec6 --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-physical-prefix-escaped-underscore/routes.ts @@ -0,0 +1,5 @@ +import { physical, rootRoute } from '@tanstack/virtual-file-routes' + +export const routes = rootRoute('__root.tsx', [ + physical('/[_]api', 'physical-routes'), +]) diff --git a/packages/router-generator/tests/generator/virtual-physical-prefix-escaped-underscore/routes/__root.tsx b/packages/router-generator/tests/generator/virtual-physical-prefix-escaped-underscore/routes/__root.tsx new file mode 100644 index 0000000000..9c657c7d5b --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-physical-prefix-escaped-underscore/routes/__root.tsx @@ -0,0 +1,3 @@ +import { createRootRoute } from '@tanstack/react-router' + +export const Route = createRootRoute() diff --git a/packages/router-generator/tests/generator/virtual-physical-prefix-escaped-underscore/routes/physical-routes/$id.tsx b/packages/router-generator/tests/generator/virtual-physical-prefix-escaped-underscore/routes/physical-routes/$id.tsx new file mode 100644 index 0000000000..e45ad32d5d --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-physical-prefix-escaped-underscore/routes/physical-routes/$id.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_api/$id')({ + component: () => 'API Id', +}) diff --git a/packages/router-generator/tests/generator/virtual-physical-prefix-escaped-underscore/routes/physical-routes/index.tsx b/packages/router-generator/tests/generator/virtual-physical-prefix-escaped-underscore/routes/physical-routes/index.tsx new file mode 100644 index 0000000000..ac44aee10d --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-physical-prefix-escaped-underscore/routes/physical-routes/index.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_api/')({ + component: () => 'API Index', +}) diff --git a/packages/router-generator/tests/generator/virtual-physical-prefix-escaped-underscore/tsr.config.json b/packages/router-generator/tests/generator/virtual-physical-prefix-escaped-underscore/tsr.config.json new file mode 100644 index 0000000000..4d587108e3 --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-physical-prefix-escaped-underscore/tsr.config.json @@ -0,0 +1,5 @@ +{ + "routesDirectory": "./routes", + "generatedRouteTree": "./routeTree.gen.ts", + "virtualRouteConfig": "./routes.ts" +} diff --git a/packages/router-generator/tests/generator/virtual-subtree-in-escaped-physical-directory/routeTree.snapshot.ts b/packages/router-generator/tests/generator/virtual-subtree-in-escaped-physical-directory/routeTree.snapshot.ts new file mode 100644 index 0000000000..bb8b39ad4a --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-subtree-in-escaped-physical-directory/routeTree.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 AreaIndexRouteImport } from './routes/[_]area/index' +import { Route as AreaChildRouteImport } from './routes/[_]area/child' + +const AreaIndexRoute = AreaIndexRouteImport.update({ + id: '/_area/', + path: '/_area/', + getParentRoute: () => rootRouteImport, +} as any) +const AreaChildRoute = AreaChildRouteImport.update({ + id: '/_area/child', + path: '/_area/child', + getParentRoute: () => rootRouteImport, +} as any) + +export interface FileRoutesByFullPath { + '/_area/child': typeof AreaChildRoute + '/_area/': typeof AreaIndexRoute +} +export interface FileRoutesByTo { + '/_area/child': typeof AreaChildRoute + '/_area': typeof AreaIndexRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/_area/child': typeof AreaChildRoute + '/_area/': typeof AreaIndexRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/_area/child' | '/_area/' + fileRoutesByTo: FileRoutesByTo + to: '/_area/child' | '/_area' + id: '__root__' | '/_area/child' | '/_area/' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + AreaChildRoute: typeof AreaChildRoute + AreaIndexRoute: typeof AreaIndexRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/_area/': { + id: '/_area/' + path: '/_area' + fullPath: '/_area/' + preLoaderRoute: typeof AreaIndexRouteImport + parentRoute: typeof rootRouteImport + } + '/_area/child': { + id: '/_area/child' + path: '/_area/child' + fullPath: '/_area/child' + preLoaderRoute: typeof AreaChildRouteImport + parentRoute: typeof rootRouteImport + } + } +} + +const rootRouteChildren: RootRouteChildren = { + AreaChildRoute: AreaChildRoute, + AreaIndexRoute: AreaIndexRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/packages/router-generator/tests/generator/virtual-subtree-in-escaped-physical-directory/routes/[_]area/__virtual.ts b/packages/router-generator/tests/generator/virtual-subtree-in-escaped-physical-directory/routes/[_]area/__virtual.ts new file mode 100644 index 0000000000..fd1baf16be --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-subtree-in-escaped-physical-directory/routes/[_]area/__virtual.ts @@ -0,0 +1,10 @@ +import { + defineVirtualSubtreeConfig, + index, + route, +} from '@tanstack/virtual-file-routes' + +export default defineVirtualSubtreeConfig([ + index('index.tsx'), + route('/child', 'child.tsx'), +]) diff --git a/packages/router-generator/tests/generator/virtual-subtree-in-escaped-physical-directory/routes/[_]area/child.tsx b/packages/router-generator/tests/generator/virtual-subtree-in-escaped-physical-directory/routes/[_]area/child.tsx new file mode 100644 index 0000000000..f3af7057cb --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-subtree-in-escaped-physical-directory/routes/[_]area/child.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_area/child')({ + component: () => 'Area Child', +}) diff --git a/packages/router-generator/tests/generator/virtual-subtree-in-escaped-physical-directory/routes/[_]area/index.tsx b/packages/router-generator/tests/generator/virtual-subtree-in-escaped-physical-directory/routes/[_]area/index.tsx new file mode 100644 index 0000000000..90b81675e6 --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-subtree-in-escaped-physical-directory/routes/[_]area/index.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_area/')({ + component: () => 'Area Index', +}) diff --git a/packages/router-generator/tests/generator/virtual-subtree-in-escaped-physical-directory/routes/__root.tsx b/packages/router-generator/tests/generator/virtual-subtree-in-escaped-physical-directory/routes/__root.tsx new file mode 100644 index 0000000000..9c657c7d5b --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-subtree-in-escaped-physical-directory/routes/__root.tsx @@ -0,0 +1,3 @@ +import { createRootRoute } from '@tanstack/react-router' + +export const Route = createRootRoute() diff --git a/packages/router-generator/tests/utils.test.ts b/packages/router-generator/tests/utils.test.ts index 6e1a58a204..0cdce3cfae 100644 --- a/packages/router-generator/tests/utils.test.ts +++ b/packages/router-generator/tests/utils.test.ts @@ -2,6 +2,8 @@ import { describe, expect, it, vi } from 'vitest' import { RoutePrefixMap, cleanPath, + createLiteralRoutePathSegmentMetadata, + createRoutePathSegmentMetadata, createRouteNodesByFullPath, createRouteNodesByTo, determineInitialRoutePath, @@ -58,6 +60,88 @@ describe('inferFullPath', () => { expect(inferFullPath(node)).toBe('/__foo') }) + + it('preserves explicit literal leading underscores on index routes after removing pathless ancestors', () => { + const parent = { + routePath: '/_layout', + } as unknown as RouteNode + const node = { + routePath: '/_layout/_nested/', + originalRoutePath: '/_layout/_nested/', + _routePathSegmentMetadata: createLiteralRoutePathSegmentMetadata( + '/_layout/_nested', + parent, + true, + ), + } as unknown as RouteNode + + expect(inferFullPath(node)).toBe('/_nested/') + }) + + it('removes pathless index route segments when they are not marked literal', () => { + const node = { + routePath: '/_layout/_nested/', + originalRoutePath: '/_layout/_nested/', + } as unknown as RouteNode + + expect(inferFullPath(node)).toBe('/') + }) + + it('preserves escaped trailing underscores on index routes', () => { + const node = { + routePath: '/_layout/foo_/', + originalRoutePath: '/_layout/foo[_]/', + _routePathSegmentMetadata: createRoutePathSegmentMetadata( + '/_layout/foo_/', + '/_layout/foo[_]/', + ), + } as unknown as RouteNode + + expect(inferFullPath(node)).toBe('/foo_/') + }) + + it('removes unescaped trailing underscores on index routes', () => { + const node = { + routePath: '/_layout/foo_/', + originalRoutePath: '/_layout/foo_/', + } as unknown as RouteNode + + expect(inferFullPath(node)).toBe('/foo/') + }) +}) + +describe('createRoutePathSegmentMetadata', () => { + it('preserves escaped trailing underscores on index routes', () => { + const metadata = createRoutePathSegmentMetadata('/foo_/', '/foo[_]/') + + expect(metadata).toHaveLength(3) + expect(metadata?.[1]).toEqual({ literalTrailingUnderscore: true }) + }) + + it('preserves mixed escaped leading and trailing underscores', () => { + const metadata = createRoutePathSegmentMetadata('/_foo_/', '/[_]foo[_]/') + + expect(metadata).toHaveLength(3) + expect(metadata?.[1]).toEqual({ + literalLeadingUnderscore: true, + literalTrailingUnderscore: true, + }) + }) + + it('tracks multiple literal underscore segments independently', () => { + const metadata = createRoutePathSegmentMetadata( + '/_foo/bar_/_baz_/', + '/[_]foo/bar[_]/[_baz_]/', + ) + + expect(metadata).toHaveLength(5) + expect(metadata?.[1]).toEqual({ literalLeadingUnderscore: true }) + expect(metadata?.[2]).toEqual({ literalTrailingUnderscore: true }) + expect(metadata?.[3]).toEqual({ + literalLeadingUnderscore: true, + literalTrailingUnderscore: true, + }) + }) }) describe('createRouteNodesByTo', () => { @@ -663,6 +747,24 @@ describe('removeLayoutSegmentsAndUnderscoresWithEscape', () => { ), ).toBe('/foo/bar') }) + + it('preserves escaped trailing underscores after removing pathless ancestors', () => { + expect( + removeLayoutSegmentsAndUnderscoresWithEscape( + '/_layout/foo_/', + '/_layout/foo[_]/', + ), + ).toBe('/foo_/') + }) + + it('preserves mixed escaped leading and trailing underscores after removing pathless ancestors', () => { + expect( + removeLayoutSegmentsAndUnderscoresWithEscape( + '/_layout/_foo_/', + '/_layout/[_]foo[_]/', + ), + ).toBe('/_foo_/') + }) }) describe('routePathToVariable', () => {