From 58b9ddfc9a164959bc99ac1a5571058678e45de3 Mon Sep 17 00:00:00 2001 From: Gurupungav Narayanan Codespace Date: Wed, 29 Apr 2026 12:42:38 +0000 Subject: [PATCH 1/3] feat(router-generator): add isolatedDeclarations config option When `isolatedDeclarations: true` is set in tsr.config.json, the generator emits explicit `: any` type annotations on route constants: const PostsRoute: any = PostsRouteImport.update({...} as any) Without this annotation, TypeScript and oxc raise TS9010 ("Variable must have an explicit type annotation with --isolatedDeclarations") because the return type of `.update()` must be inferred across files. The option defaults to `false` to preserve existing behaviour. It is a no-op when `disableTypes: true` (JS output). Adds an `isolated-declarations` test fixture (copied from `single-level`) with a snapshot that asserts the `: any` annotations are emitted. --- packages/router-generator/src/config.ts | 1 + packages/router-generator/src/generator.ts | 2 +- .../router-generator/tests/generator.test.ts | 3 + .../routeTree.snapshot.ts | 77 +++++++++++++++++++ .../isolated-declarations/routes/__root.tsx | 2 + .../isolated-declarations/routes/index.tsx | 3 + .../isolated-declarations/routes/posts.tsx | 3 + 7 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 packages/router-generator/tests/generator/isolated-declarations/routeTree.snapshot.ts create mode 100644 packages/router-generator/tests/generator/isolated-declarations/routes/__root.tsx create mode 100644 packages/router-generator/tests/generator/isolated-declarations/routes/index.tsx create mode 100644 packages/router-generator/tests/generator/isolated-declarations/routes/posts.tsx diff --git a/packages/router-generator/src/config.ts b/packages/router-generator/src/config.ts index bedb265155f..66be7023067 100644 --- a/packages/router-generator/src/config.ts +++ b/packages/router-generator/src/config.ts @@ -49,6 +49,7 @@ export type BaseConfig = z.infer export const configSchema = baseConfigSchema.extend({ generatedRouteTree: z.string().optional().default('./src/routeTree.gen.ts'), disableTypes: z.boolean().optional().default(false), + isolatedDeclarations: z.boolean().optional().default(false), addExtensions: z .union([z.boolean(), z.string()]) .optional() diff --git a/packages/router-generator/src/generator.ts b/packages/router-generator/src/generator.ts index f951beb05ba..01c2bcba27b 100644 --- a/packages/router-generator/src/generator.ts +++ b/packages/router-generator/src/generator.ts @@ -672,7 +672,7 @@ export class Generator { return [ [ - `const ${node.variableName}Route = ${node.variableName}RouteImport.update({ + `const ${node.variableName}Route${config.isolatedDeclarations && !config.disableTypes ? ': any' : ''} = ${node.variableName}RouteImport.update({ ${[ `id: '${node.path}'`, !node.isNonPath || diff --git a/packages/router-generator/tests/generator.test.ts b/packages/router-generator/tests/generator.test.ts index 79f7d505b29..ce618d27e7a 100644 --- a/packages/router-generator/tests/generator.test.ts +++ b/packages/router-generator/tests/generator.test.ts @@ -121,6 +121,9 @@ function rewriteConfigByFolderName(folderName: string, config: Config) { config.generatedRouteTree = makeFolderDir(folderName) + `/routeTree.gen.js` break + case 'isolated-declarations': + config.isolatedDeclarations = true + break case 'custom-scaffolding': config.customScaffolding = { routeTemplate: [ diff --git a/packages/router-generator/tests/generator/isolated-declarations/routeTree.snapshot.ts b/packages/router-generator/tests/generator/isolated-declarations/routeTree.snapshot.ts new file mode 100644 index 00000000000..ee41ddb17d5 --- /dev/null +++ b/packages/router-generator/tests/generator/isolated-declarations/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 PostsRouteImport } from './routes/posts' +import { Route as IndexRouteImport } from './routes/index' + +const PostsRoute: any = PostsRouteImport.update({ + id: '/posts', + path: '/posts', + getParentRoute: () => rootRouteImport, +} as any) +const IndexRoute: any = 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/isolated-declarations/routes/__root.tsx b/packages/router-generator/tests/generator/isolated-declarations/routes/__root.tsx new file mode 100644 index 00000000000..a8c4d4c8938 --- /dev/null +++ b/packages/router-generator/tests/generator/isolated-declarations/routes/__root.tsx @@ -0,0 +1,2 @@ +// @ts-nocheck +export const Route = createFileRoute() diff --git a/packages/router-generator/tests/generator/isolated-declarations/routes/index.tsx b/packages/router-generator/tests/generator/isolated-declarations/routes/index.tsx new file mode 100644 index 00000000000..f338b4f8e93 --- /dev/null +++ b/packages/router-generator/tests/generator/isolated-declarations/routes/index.tsx @@ -0,0 +1,3 @@ +import { createFileRoute } from '@tanstack/react-router' +// @ts-nocheck +export const Route = createFileRoute('/')() diff --git a/packages/router-generator/tests/generator/isolated-declarations/routes/posts.tsx b/packages/router-generator/tests/generator/isolated-declarations/routes/posts.tsx new file mode 100644 index 00000000000..fb01464ab38 --- /dev/null +++ b/packages/router-generator/tests/generator/isolated-declarations/routes/posts.tsx @@ -0,0 +1,3 @@ +import { createFileRoute } from '@tanstack/react-router' +// @ts-nocheck +export const Route = createFileRoute('/posts')() From b222df263d810f308d843d6b9f2a4b6a4ba6b766 Mon Sep 17 00:00:00 2001 From: Gurupungav Narayanan Codespace Date: Wed, 29 Apr 2026 13:44:08 +0000 Subject: [PATCH 2/3] fix(router-generator): fix isolatedDeclarations for WithChildren constants and header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three correctness gaps in the original PR: 1. RouteWithChildren constants (generated by buildRouteTreeConfig for parent routes with children) were missing ': any' annotations. Add isolatedDeclarations param to buildRouteTreeConfig and propagate it through recursive calls. 2. rootRouteWithChildren (generated when the root route has component piece files) was also missing ': any'. Apply the same conditional. 3. The default routeTreeFileHeader includes '// @ts-nocheck', which opts the generated file out of type-checking entirely — defeating the purpose of isolatedDeclarations mode. Automatically strip '// @ts-nocheck' from the header when isolatedDeclarations: true. Add 'isolated-declarations-nested' fixture to cover the WithChildren code path (PostsRouteWithChildren: any on line 92 of snapshot). Co-Authored-By: Claude Sonnet 4.6 --- packages/router-generator/src/generator.ts | 15 ++- packages/router-generator/src/utils.ts | 4 +- .../router-generator/tests/generator.test.ts | 1 + .../routeTree.snapshot.ts | 101 ++++++++++++++++++ .../routes/__root.tsx | 2 + .../routes/index.tsx | 3 + .../routes/posts.tsx | 3 + .../routes/posts/index.tsx | 3 + .../routeTree.snapshot.ts | 2 - 9 files changed, 129 insertions(+), 5 deletions(-) create mode 100644 packages/router-generator/tests/generator/isolated-declarations-nested/routeTree.snapshot.ts create mode 100644 packages/router-generator/tests/generator/isolated-declarations-nested/routes/__root.tsx create mode 100644 packages/router-generator/tests/generator/isolated-declarations-nested/routes/index.tsx create mode 100644 packages/router-generator/tests/generator/isolated-declarations-nested/routes/posts.tsx create mode 100644 packages/router-generator/tests/generator/isolated-declarations-nested/routes/posts/index.tsx diff --git a/packages/router-generator/src/generator.ts b/packages/router-generator/src/generator.ts index 01c2bcba27b..88725999887 100644 --- a/packages/router-generator/src/generator.ts +++ b/packages/router-generator/src/generator.ts @@ -659,6 +659,8 @@ export class Generator { const routeTreeConfig = buildRouteTreeConfig( acc.routeTree, config.disableTypes, + 1, + config.isolatedDeclarations, ) const createUpdateRoutes = sortedRouteNodes.map((node) => { @@ -789,7 +791,7 @@ export class Generator { rootNotFoundComponentNode || rootPendingComponentNode ) { - rootRouteUpdate = `const rootRouteWithChildren = rootRouteImport${ + rootRouteUpdate = `const rootRouteWithChildren${config.isolatedDeclarations && !config.disableTypes ? ': any' : ''} = rootRouteImport${ rootComponentNode || rootErrorComponentNode || rootNotFoundComponentNode || @@ -945,8 +947,17 @@ ${acc.routeTree.map((child) => `${child.variableName}Route: typeof ${getResolved footer = config.routeTreeFileFooter() } } + // When isolatedDeclarations is enabled, remove "// @ts-nocheck" from the + // header — the feature only works if TypeScript actually type-checks the + // file, so keeping the opt-out comment would silently defeat its purpose. + const header = config.isolatedDeclarations + ? config.routeTreeFileHeader.filter( + (line) => line.trim() !== '// @ts-nocheck', + ) + : config.routeTreeFileHeader + const routeTreeContent = [ - ...config.routeTreeFileHeader, + ...header, `// 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.`, diff --git a/packages/router-generator/src/utils.ts b/packages/router-generator/src/utils.ts index db0981e7d16..210113d20ad 100644 --- a/packages/router-generator/src/utils.ts +++ b/packages/router-generator/src/utils.ts @@ -850,6 +850,7 @@ export function buildRouteTreeConfig( nodes: Array, disableTypes: boolean, depth = 1, + isolatedDeclarations = false, ): Array { const children = nodes.map((node) => { if (node._fsRouteType === '__root') { @@ -867,6 +868,7 @@ export function buildRouteTreeConfig( node.children, disableTypes, depth + 1, + isolatedDeclarations, ) const childrenDeclaration = disableTypes @@ -889,7 +891,7 @@ export function buildRouteTreeConfig( .join(',')} }` - const routeWithChildren = `const ${route}RouteWithChildren = ${route}Route._addFileChildren(${route}RouteChildren)` + const routeWithChildren = `const ${route}RouteWithChildren${isolatedDeclarations && !disableTypes ? ': any' : ''} = ${route}Route._addFileChildren(${route}RouteChildren)` return [ childConfigs.join('\n'), diff --git a/packages/router-generator/tests/generator.test.ts b/packages/router-generator/tests/generator.test.ts index ce618d27e7a..9a4a02bb3cd 100644 --- a/packages/router-generator/tests/generator.test.ts +++ b/packages/router-generator/tests/generator.test.ts @@ -122,6 +122,7 @@ function rewriteConfigByFolderName(folderName: string, config: Config) { makeFolderDir(folderName) + `/routeTree.gen.js` break case 'isolated-declarations': + case 'isolated-declarations-nested': config.isolatedDeclarations = true break case 'custom-scaffolding': diff --git a/packages/router-generator/tests/generator/isolated-declarations-nested/routeTree.snapshot.ts b/packages/router-generator/tests/generator/isolated-declarations-nested/routeTree.snapshot.ts new file mode 100644 index 00000000000..5daa7fa26c8 --- /dev/null +++ b/packages/router-generator/tests/generator/isolated-declarations-nested/routeTree.snapshot.ts @@ -0,0 +1,101 @@ +/* eslint-disable */ + +// 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 PostsIndexRouteImport } from './routes/posts/index' + +const PostsRoute: any = PostsRouteImport.update({ + id: '/posts', + path: '/posts', + getParentRoute: () => rootRouteImport, +} as any) +const IndexRoute: any = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) +const PostsIndexRoute: any = PostsIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => PostsRoute, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute + '/posts': typeof PostsRouteWithChildren + '/posts/': typeof PostsIndexRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute + '/posts': typeof PostsIndexRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute + '/posts': typeof PostsRouteWithChildren + '/posts/': typeof PostsIndexRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' | '/posts' | '/posts/' + fileRoutesByTo: FileRoutesByTo + to: '/' | '/posts' + id: '__root__' | '/' | '/posts' | '/posts/' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute + PostsRoute: typeof PostsRouteWithChildren +} + +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 + } + '/posts/': { + id: '/posts/' + path: '/' + fullPath: '/posts/' + preLoaderRoute: typeof PostsIndexRouteImport + parentRoute: typeof PostsRoute + } + } +} + +interface PostsRouteChildren { + PostsIndexRoute: typeof PostsIndexRoute +} + +const PostsRouteChildren: PostsRouteChildren = { + PostsIndexRoute: PostsIndexRoute, +} + +const PostsRouteWithChildren: any = + PostsRoute._addFileChildren(PostsRouteChildren) + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, + PostsRoute: PostsRouteWithChildren, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/packages/router-generator/tests/generator/isolated-declarations-nested/routes/__root.tsx b/packages/router-generator/tests/generator/isolated-declarations-nested/routes/__root.tsx new file mode 100644 index 00000000000..a8c4d4c8938 --- /dev/null +++ b/packages/router-generator/tests/generator/isolated-declarations-nested/routes/__root.tsx @@ -0,0 +1,2 @@ +// @ts-nocheck +export const Route = createFileRoute() diff --git a/packages/router-generator/tests/generator/isolated-declarations-nested/routes/index.tsx b/packages/router-generator/tests/generator/isolated-declarations-nested/routes/index.tsx new file mode 100644 index 00000000000..f338b4f8e93 --- /dev/null +++ b/packages/router-generator/tests/generator/isolated-declarations-nested/routes/index.tsx @@ -0,0 +1,3 @@ +import { createFileRoute } from '@tanstack/react-router' +// @ts-nocheck +export const Route = createFileRoute('/')() diff --git a/packages/router-generator/tests/generator/isolated-declarations-nested/routes/posts.tsx b/packages/router-generator/tests/generator/isolated-declarations-nested/routes/posts.tsx new file mode 100644 index 00000000000..fb01464ab38 --- /dev/null +++ b/packages/router-generator/tests/generator/isolated-declarations-nested/routes/posts.tsx @@ -0,0 +1,3 @@ +import { createFileRoute } from '@tanstack/react-router' +// @ts-nocheck +export const Route = createFileRoute('/posts')() diff --git a/packages/router-generator/tests/generator/isolated-declarations-nested/routes/posts/index.tsx b/packages/router-generator/tests/generator/isolated-declarations-nested/routes/posts/index.tsx new file mode 100644 index 00000000000..bc66da6c180 --- /dev/null +++ b/packages/router-generator/tests/generator/isolated-declarations-nested/routes/posts/index.tsx @@ -0,0 +1,3 @@ +import { createFileRoute } from '@tanstack/react-router' +// @ts-nocheck +export const Route = createFileRoute('/posts/')() diff --git a/packages/router-generator/tests/generator/isolated-declarations/routeTree.snapshot.ts b/packages/router-generator/tests/generator/isolated-declarations/routeTree.snapshot.ts index ee41ddb17d5..93de60980e4 100644 --- a/packages/router-generator/tests/generator/isolated-declarations/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/isolated-declarations/routeTree.snapshot.ts @@ -1,7 +1,5 @@ /* eslint-disable */ -// @ts-nocheck - // noinspection JSUnusedGlobalSymbols // This file was automatically generated by TanStack Router. From 305c1aca900bef3b62fdb90b50a2d55cc78a0d6d Mon Sep 17 00:00:00 2001 From: Gurupungav Narayanan Codespace Date: Wed, 29 Apr 2026 14:25:34 +0000 Subject: [PATCH 3/3] revert: don't auto-strip // @ts-nocheck when isolatedDeclarations is set MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit // @ts-nocheck is intentionally included in TanStack's default routeTreeFileHeader. Users who need isolatedDeclarations type-checking should configure a custom routeTreeFileHeader that omits it — that is the right locus of control, not the generator silently overriding it. Co-Authored-By: Claude Sonnet 4.6 --- packages/router-generator/src/generator.ts | 11 +---------- .../routeTree.snapshot.ts | 2 ++ .../isolated-declarations/routeTree.snapshot.ts | 2 ++ 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/packages/router-generator/src/generator.ts b/packages/router-generator/src/generator.ts index 88725999887..d116748dbca 100644 --- a/packages/router-generator/src/generator.ts +++ b/packages/router-generator/src/generator.ts @@ -947,17 +947,8 @@ ${acc.routeTree.map((child) => `${child.variableName}Route: typeof ${getResolved footer = config.routeTreeFileFooter() } } - // When isolatedDeclarations is enabled, remove "// @ts-nocheck" from the - // header — the feature only works if TypeScript actually type-checks the - // file, so keeping the opt-out comment would silently defeat its purpose. - const header = config.isolatedDeclarations - ? config.routeTreeFileHeader.filter( - (line) => line.trim() !== '// @ts-nocheck', - ) - : config.routeTreeFileHeader - const routeTreeContent = [ - ...header, + ...config.routeTreeFileHeader, `// 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.`, diff --git a/packages/router-generator/tests/generator/isolated-declarations-nested/routeTree.snapshot.ts b/packages/router-generator/tests/generator/isolated-declarations-nested/routeTree.snapshot.ts index 5daa7fa26c8..552e30be8bc 100644 --- a/packages/router-generator/tests/generator/isolated-declarations-nested/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/isolated-declarations-nested/routeTree.snapshot.ts @@ -1,5 +1,7 @@ /* eslint-disable */ +// @ts-nocheck + // noinspection JSUnusedGlobalSymbols // This file was automatically generated by TanStack Router. diff --git a/packages/router-generator/tests/generator/isolated-declarations/routeTree.snapshot.ts b/packages/router-generator/tests/generator/isolated-declarations/routeTree.snapshot.ts index 93de60980e4..ee41ddb17d5 100644 --- a/packages/router-generator/tests/generator/isolated-declarations/routeTree.snapshot.ts +++ b/packages/router-generator/tests/generator/isolated-declarations/routeTree.snapshot.ts @@ -1,5 +1,7 @@ /* eslint-disable */ +// @ts-nocheck + // noinspection JSUnusedGlobalSymbols // This file was automatically generated by TanStack Router.