@tanstack/router-generator
Advanced tools
@@ -52,3 +52,2 @@ const require_runtime = require("./_virtual/_rolldown/runtime.cjs"); | ||
| disableTypes: zod.z.boolean().optional().default(false), | ||
| verboseFileRoutes: zod.z.boolean().optional(), | ||
| addExtensions: zod.z.union([zod.z.boolean(), zod.z.string()]).optional().default(false).transform((v) => typeof v === "string" ? v.startsWith(".") ? v : `.${v}` : v), | ||
@@ -55,0 +54,0 @@ enableRouteTreeFormatting: zod.z.boolean().optional().default(true), |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"config.cjs","names":[],"sources":["../../src/config.ts"],"sourcesContent":["import path from 'node:path'\nimport { existsSync, readFileSync } from 'node:fs'\nimport { z } from 'zod'\nimport { virtualRootRouteSchema } from './filesystem/virtual/config'\nimport type { GeneratorPlugin } from './plugin/types'\n\nconst tokenJsonRegexSchema = z.object({\n regex: z.string(),\n flags: z.string().optional(),\n})\n\nconst tokenMatcherSchema = z.union([\n z.string(),\n z.instanceof(RegExp),\n tokenJsonRegexSchema,\n])\n\nexport type TokenMatcherJson = string | z.infer<typeof tokenJsonRegexSchema>\n\nexport type TokenMatcher = z.infer<typeof tokenMatcherSchema>\n\nexport const baseConfigSchema = z.object({\n target: z.enum(['react', 'solid', 'vue']).optional().default('react'),\n virtualRouteConfig: virtualRootRouteSchema.or(z.string()).optional(),\n routeFilePrefix: z.string().optional(),\n routeFileIgnorePrefix: z.string().optional().default('-'),\n routeFileIgnorePattern: z.string().optional(),\n routesDirectory: z.string().optional().default('./src/routes'),\n quoteStyle: z.enum(['single', 'double']).optional().default('single'),\n semicolons: z.boolean().optional().default(false),\n disableLogging: z.boolean().optional().default(false),\n routeTreeFileHeader: z\n .array(z.string())\n .optional()\n .default([\n '/* eslint-disable */',\n '// @ts-nocheck',\n '// noinspection JSUnusedGlobalSymbols',\n ]),\n indexToken: tokenMatcherSchema.optional().default('index'),\n routeToken: tokenMatcherSchema.optional().default('route'),\n pathParamsAllowedCharacters: z\n .array(z.enum([';', ':', '@', '&', '=', '+', '$', ',']))\n .optional(),\n})\n\nexport type BaseConfig = z.infer<typeof baseConfigSchema>\n\nexport const configSchema = baseConfigSchema.extend({\n generatedRouteTree: z.string().optional().default('./src/routeTree.gen.ts'),\n disableTypes: z.boolean().optional().default(false),\n verboseFileRoutes: z.boolean().optional(),\n addExtensions: z\n .union([z.boolean(), z.string()])\n .optional()\n .default(false)\n .transform((v) =>\n typeof v === 'string' ? (v.startsWith('.') ? v : `.${v}`) : v,\n ),\n enableRouteTreeFormatting: z.boolean().optional().default(true),\n routeTreeFileFooter: z\n .union([\n z.array(z.string()).optional().default([]),\n z.function().returns(z.array(z.string())),\n ])\n .optional(),\n autoCodeSplitting: z.boolean().optional(),\n customScaffolding: z\n .object({\n routeTemplate: z.string().optional(),\n lazyRouteTemplate: z.string().optional(),\n })\n .optional(),\n experimental: z\n .object({\n // TODO: This has been made stable and is now \"autoCodeSplitting\". Remove in next major version.\n enableCodeSplitting: z.boolean().optional(),\n })\n .optional(),\n plugins: z.array(z.custom<GeneratorPlugin>()).optional(),\n tmpDir: z.string().optional().default(''),\n importRoutesUsingAbsolutePaths: z.boolean().optional().default(false),\n})\n\nexport type Config = z.infer<typeof configSchema>\n\ntype ResolveParams = {\n configDirectory: string\n}\n\nexport function resolveConfigPath({ configDirectory }: ResolveParams) {\n return path.resolve(configDirectory, 'tsr.config.json')\n}\n\nexport function getConfig(\n inlineConfig: Partial<Config> = {},\n configDirectory?: string,\n): Config {\n if (configDirectory === undefined) {\n configDirectory = process.cwd()\n }\n const configFilePathJson = resolveConfigPath({ configDirectory })\n const exists = existsSync(configFilePathJson)\n\n let config: Config\n\n if (exists) {\n // Parse file config (allows JSON regex-object form)\n const fileConfigRaw = JSON.parse(readFileSync(configFilePathJson, 'utf-8'))\n\n // Merge raw configs (inline overrides file), then parse once to apply defaults\n // This ensures file config values aren't overwritten by inline defaults\n const merged = {\n ...fileConfigRaw,\n ...inlineConfig,\n }\n config = configSchema.parse(merged)\n } else {\n config = configSchema.parse(inlineConfig)\n }\n\n // If typescript is disabled, make sure the generated route tree is a .js file\n if (config.disableTypes) {\n config.generatedRouteTree = config.generatedRouteTree.replace(\n /\\.(ts|tsx)$/,\n '.js',\n )\n }\n\n // if a configDirectory is used, paths should be relative to that directory\n if (configDirectory) {\n // if absolute configDirectory is provided, use it as the root\n if (path.isAbsolute(configDirectory)) {\n config.routesDirectory = path.resolve(\n configDirectory,\n config.routesDirectory,\n )\n config.generatedRouteTree = path.resolve(\n configDirectory,\n config.generatedRouteTree,\n )\n } else {\n config.routesDirectory = path.resolve(\n process.cwd(),\n configDirectory,\n config.routesDirectory,\n )\n config.generatedRouteTree = path.resolve(\n process.cwd(),\n configDirectory,\n config.generatedRouteTree,\n )\n }\n }\n\n const resolveTmpDir = (dir: string | Array<string>) => {\n if (Array.isArray(dir)) {\n dir = path.join(...dir)\n }\n if (!path.isAbsolute(dir)) {\n dir = path.resolve(process.cwd(), dir)\n }\n return dir\n }\n\n if (config.tmpDir) {\n config.tmpDir = resolveTmpDir(config.tmpDir)\n } else if (process.env.TSR_TMP_DIR) {\n config.tmpDir = resolveTmpDir(process.env.TSR_TMP_DIR)\n } else {\n config.tmpDir = resolveTmpDir(['.tanstack', 'tmp'])\n }\n\n validateConfig(config)\n return config\n}\n\nfunction validateConfig(config: Config) {\n if (typeof config.experimental?.enableCodeSplitting !== 'undefined') {\n const message = `\n------\n⚠️ ⚠️ ⚠️\nERROR: The \"experimental.enableCodeSplitting\" flag has been made stable and is now \"autoCodeSplitting\". Please update your configuration file to use \"autoCodeSplitting\" instead of \"experimental.enableCodeSplitting\".\n------\n`\n console.error(message)\n throw new Error(message)\n }\n\n // Check that indexToken and routeToken are not identical\n // Works for strings, RegExp, and JSON regex objects\n if (areTokensEqual(config.indexToken, config.routeToken)) {\n throw new Error(\n `The \"indexToken\" and \"routeToken\" options must be different.`,\n )\n }\n\n if (\n config.routeFileIgnorePrefix &&\n config.routeFileIgnorePrefix.trim() === '_'\n ) {\n throw new Error(\n `The \"routeFileIgnorePrefix\" cannot be an underscore (\"_\"). This is a reserved character used to denote a pathless route. Please use a different prefix.`,\n )\n }\n\n return config\n}\n\n/**\n * Compares two token matchers for equality.\n * Handles strings, RegExp instances, and JSON regex objects.\n */\nfunction areTokensEqual(a: TokenMatcher, b: TokenMatcher): boolean {\n // Both strings\n if (typeof a === 'string' && typeof b === 'string') {\n return a === b\n }\n\n // Both RegExp instances\n if (a instanceof RegExp && b instanceof RegExp) {\n return a.source === b.source && a.flags === b.flags\n }\n\n // Both JSON regex objects\n if (\n typeof a === 'object' &&\n 'regex' in a &&\n typeof b === 'object' &&\n 'regex' in b\n ) {\n return a.regex === b.regex && (a.flags ?? '') === (b.flags ?? '')\n }\n\n // Mixed types - not equal\n return false\n}\n"],"mappings":"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"} | ||
| {"version":3,"file":"config.cjs","names":[],"sources":["../../src/config.ts"],"sourcesContent":["import path from 'node:path'\nimport { existsSync, readFileSync } from 'node:fs'\nimport { z } from 'zod'\nimport { virtualRootRouteSchema } from './filesystem/virtual/config'\nimport type { GeneratorPlugin } from './plugin/types'\n\nconst tokenJsonRegexSchema = z.object({\n regex: z.string(),\n flags: z.string().optional(),\n})\n\nconst tokenMatcherSchema = z.union([\n z.string(),\n z.instanceof(RegExp),\n tokenJsonRegexSchema,\n])\n\nexport type TokenMatcherJson = string | z.infer<typeof tokenJsonRegexSchema>\n\nexport type TokenMatcher = z.infer<typeof tokenMatcherSchema>\n\nexport const baseConfigSchema = z.object({\n target: z.enum(['react', 'solid', 'vue']).optional().default('react'),\n virtualRouteConfig: virtualRootRouteSchema.or(z.string()).optional(),\n routeFilePrefix: z.string().optional(),\n routeFileIgnorePrefix: z.string().optional().default('-'),\n routeFileIgnorePattern: z.string().optional(),\n routesDirectory: z.string().optional().default('./src/routes'),\n quoteStyle: z.enum(['single', 'double']).optional().default('single'),\n semicolons: z.boolean().optional().default(false),\n disableLogging: z.boolean().optional().default(false),\n routeTreeFileHeader: z\n .array(z.string())\n .optional()\n .default([\n '/* eslint-disable */',\n '// @ts-nocheck',\n '// noinspection JSUnusedGlobalSymbols',\n ]),\n indexToken: tokenMatcherSchema.optional().default('index'),\n routeToken: tokenMatcherSchema.optional().default('route'),\n pathParamsAllowedCharacters: z\n .array(z.enum([';', ':', '@', '&', '=', '+', '$', ',']))\n .optional(),\n})\n\nexport type BaseConfig = z.infer<typeof baseConfigSchema>\n\nexport const configSchema = baseConfigSchema.extend({\n generatedRouteTree: z.string().optional().default('./src/routeTree.gen.ts'),\n disableTypes: z.boolean().optional().default(false),\n addExtensions: z\n .union([z.boolean(), z.string()])\n .optional()\n .default(false)\n .transform((v) =>\n typeof v === 'string' ? (v.startsWith('.') ? v : `.${v}`) : v,\n ),\n enableRouteTreeFormatting: z.boolean().optional().default(true),\n routeTreeFileFooter: z\n .union([\n z.array(z.string()).optional().default([]),\n z.function().returns(z.array(z.string())),\n ])\n .optional(),\n autoCodeSplitting: z.boolean().optional(),\n customScaffolding: z\n .object({\n routeTemplate: z.string().optional(),\n lazyRouteTemplate: z.string().optional(),\n })\n .optional(),\n experimental: z\n .object({\n // TODO: This has been made stable and is now \"autoCodeSplitting\". Remove in next major version.\n enableCodeSplitting: z.boolean().optional(),\n })\n .optional(),\n plugins: z.array(z.custom<GeneratorPlugin>()).optional(),\n tmpDir: z.string().optional().default(''),\n importRoutesUsingAbsolutePaths: z.boolean().optional().default(false),\n})\n\nexport type Config = z.infer<typeof configSchema>\n\ntype ResolveParams = {\n configDirectory: string\n}\n\nexport function resolveConfigPath({ configDirectory }: ResolveParams) {\n return path.resolve(configDirectory, 'tsr.config.json')\n}\n\nexport function getConfig(\n inlineConfig: Partial<Config> = {},\n configDirectory?: string,\n): Config {\n if (configDirectory === undefined) {\n configDirectory = process.cwd()\n }\n const configFilePathJson = resolveConfigPath({ configDirectory })\n const exists = existsSync(configFilePathJson)\n\n let config: Config\n\n if (exists) {\n // Parse file config (allows JSON regex-object form)\n const fileConfigRaw = JSON.parse(readFileSync(configFilePathJson, 'utf-8'))\n\n // Merge raw configs (inline overrides file), then parse once to apply defaults\n // This ensures file config values aren't overwritten by inline defaults\n const merged = {\n ...fileConfigRaw,\n ...inlineConfig,\n }\n config = configSchema.parse(merged)\n } else {\n config = configSchema.parse(inlineConfig)\n }\n\n // If typescript is disabled, make sure the generated route tree is a .js file\n if (config.disableTypes) {\n config.generatedRouteTree = config.generatedRouteTree.replace(\n /\\.(ts|tsx)$/,\n '.js',\n )\n }\n\n // if a configDirectory is used, paths should be relative to that directory\n if (configDirectory) {\n // if absolute configDirectory is provided, use it as the root\n if (path.isAbsolute(configDirectory)) {\n config.routesDirectory = path.resolve(\n configDirectory,\n config.routesDirectory,\n )\n config.generatedRouteTree = path.resolve(\n configDirectory,\n config.generatedRouteTree,\n )\n } else {\n config.routesDirectory = path.resolve(\n process.cwd(),\n configDirectory,\n config.routesDirectory,\n )\n config.generatedRouteTree = path.resolve(\n process.cwd(),\n configDirectory,\n config.generatedRouteTree,\n )\n }\n }\n\n const resolveTmpDir = (dir: string | Array<string>) => {\n if (Array.isArray(dir)) {\n dir = path.join(...dir)\n }\n if (!path.isAbsolute(dir)) {\n dir = path.resolve(process.cwd(), dir)\n }\n return dir\n }\n\n if (config.tmpDir) {\n config.tmpDir = resolveTmpDir(config.tmpDir)\n } else if (process.env.TSR_TMP_DIR) {\n config.tmpDir = resolveTmpDir(process.env.TSR_TMP_DIR)\n } else {\n config.tmpDir = resolveTmpDir(['.tanstack', 'tmp'])\n }\n\n validateConfig(config)\n return config\n}\n\nfunction validateConfig(config: Config) {\n if (typeof config.experimental?.enableCodeSplitting !== 'undefined') {\n const message = `\n------\n⚠️ ⚠️ ⚠️\nERROR: The \"experimental.enableCodeSplitting\" flag has been made stable and is now \"autoCodeSplitting\". Please update your configuration file to use \"autoCodeSplitting\" instead of \"experimental.enableCodeSplitting\".\n------\n`\n console.error(message)\n throw new Error(message)\n }\n\n // Check that indexToken and routeToken are not identical\n // Works for strings, RegExp, and JSON regex objects\n if (areTokensEqual(config.indexToken, config.routeToken)) {\n throw new Error(\n `The \"indexToken\" and \"routeToken\" options must be different.`,\n )\n }\n\n if (\n config.routeFileIgnorePrefix &&\n config.routeFileIgnorePrefix.trim() === '_'\n ) {\n throw new Error(\n `The \"routeFileIgnorePrefix\" cannot be an underscore (\"_\"). This is a reserved character used to denote a pathless route. Please use a different prefix.`,\n )\n }\n\n return config\n}\n\n/**\n * Compares two token matchers for equality.\n * Handles strings, RegExp instances, and JSON regex objects.\n */\nfunction areTokensEqual(a: TokenMatcher, b: TokenMatcher): boolean {\n // Both strings\n if (typeof a === 'string' && typeof b === 'string') {\n return a === b\n }\n\n // Both RegExp instances\n if (a instanceof RegExp && b instanceof RegExp) {\n return a.source === b.source && a.flags === b.flags\n }\n\n // Both JSON regex objects\n if (\n typeof a === 'object' &&\n 'regex' in a &&\n typeof b === 'object' &&\n 'regex' in b\n ) {\n return a.regex === b.regex && (a.flags ?? '') === (b.flags ?? '')\n }\n\n // Mixed types - not equal\n return false\n}\n"],"mappings":"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"} |
@@ -134,3 +134,2 @@ import { z } from 'zod'; | ||
| disableTypes: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>; | ||
| verboseFileRoutes: z.ZodOptional<z.ZodBoolean>; | ||
| addExtensions: z.ZodEffects<z.ZodDefault<z.ZodOptional<z.ZodUnion<[z.ZodBoolean, z.ZodString]>>>, string | boolean, string | boolean | undefined>; | ||
@@ -187,3 +186,2 @@ enableRouteTreeFormatting: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>; | ||
| pathParamsAllowedCharacters?: (":" | "$" | ";" | "@" | "&" | "=" | "+" | ",")[] | undefined; | ||
| verboseFileRoutes?: boolean | undefined; | ||
| routeTreeFileFooter?: string[] | ((...args: unknown[]) => string[]) | undefined; | ||
@@ -221,3 +219,2 @@ autoCodeSplitting?: boolean | undefined; | ||
| disableTypes?: boolean | undefined; | ||
| verboseFileRoutes?: boolean | undefined; | ||
| addExtensions?: string | boolean | undefined; | ||
@@ -224,0 +221,0 @@ enableRouteTreeFormatting?: boolean | undefined; |
@@ -316,24 +316,2 @@ const require_runtime = require("./_virtual/_rolldown/runtime.cjs"); | ||
| } | ||
| if (config.verboseFileRoutes === false) { | ||
| const typeImport = { | ||
| specifiers: [], | ||
| source: this.targetTemplate.fullPkg, | ||
| importKind: "type" | ||
| }; | ||
| let needsCreateFileRoute = false; | ||
| let needsCreateLazyFileRoute = false; | ||
| for (const node of sortedRouteNodes) { | ||
| if (require_utils.isRouteNodeValidForAugmentation(node)) { | ||
| if (node._fsRouteType !== "lazy") needsCreateFileRoute = true; | ||
| if (acc.routePiecesByPath[node.routePath]?.lazy) needsCreateLazyFileRoute = true; | ||
| } | ||
| if (needsCreateFileRoute && needsCreateLazyFileRoute) break; | ||
| } | ||
| if (needsCreateFileRoute) typeImport.specifiers.push({ imported: "CreateFileRoute" }); | ||
| if (needsCreateLazyFileRoute) typeImport.specifiers.push({ imported: "CreateLazyFileRoute" }); | ||
| if (typeImport.specifiers.length > 0) { | ||
| typeImport.specifiers.push({ imported: "FileRoutesByPath" }); | ||
| imports.push(typeImport); | ||
| } | ||
| } | ||
| const routeTreeConfig = require_utils.buildRouteTreeConfig(acc.routeTree, config.disableTypes); | ||
@@ -446,21 +424,2 @@ const createUpdateRoutes = sortedRouteNodes.map((node) => { | ||
| const importStatements = mergedImports.map(require_utils.buildImportString); | ||
| let moduleAugmentation = ""; | ||
| if (config.verboseFileRoutes === false && !config.disableTypes) moduleAugmentation = opts.routeFileResult.map((node) => { | ||
| const getModuleDeclaration = (routeNode) => { | ||
| if (!require_utils.isRouteNodeValidForAugmentation(routeNode)) return ""; | ||
| let moduleAugmentation = ""; | ||
| if (routeNode._fsRouteType === "lazy") moduleAugmentation = `const createLazyFileRoute: CreateLazyFileRoute<FileRoutesByPath['${routeNode.routePath}']['preLoaderRoute']>`; | ||
| else moduleAugmentation = `const createFileRoute: CreateFileRoute<'${routeNode.routePath}', | ||
| FileRoutesByPath['${routeNode.routePath}']['parentRoute'], | ||
| FileRoutesByPath['${routeNode.routePath}']['id'], | ||
| FileRoutesByPath['${routeNode.routePath}']['path'], | ||
| FileRoutesByPath['${routeNode.routePath}']['fullPath'] | ||
| > | ||
| `; | ||
| return `declare module './${require_utils.getImportPath(routeNode, config, this.generatedRouteTreePath)}' { | ||
| ${moduleAugmentation} | ||
| }`; | ||
| }; | ||
| return getModuleDeclaration(node); | ||
| }).join("\n"); | ||
| const rootRouteImport = require_utils.getImportForRouteNode(rootRouteNode, config, this.generatedRouteTreePath, this.root); | ||
@@ -483,3 +442,2 @@ routeImports.unshift(rootRouteImport); | ||
| fileRoutesByPathInterface, | ||
| moduleAugmentation, | ||
| routeTreeConfig.join("\n"), | ||
@@ -546,4 +504,3 @@ routeTree, | ||
| routeId: escapedRoutePath, | ||
| lazy: node._fsRouteType === "lazy", | ||
| verboseFileRoutes: !(this.config.verboseFileRoutes === false) | ||
| lazy: node._fsRouteType === "lazy" | ||
| }, | ||
@@ -550,0 +507,0 @@ node |
@@ -7,3 +7,2 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); | ||
| const require_getRouteNodes$1 = require("./filesystem/physical/getRouteNodes.cjs"); | ||
| const require_utils$1 = require("./transform/utils.cjs"); | ||
| const require_generator = require("./generator.cjs"); | ||
@@ -17,3 +16,2 @@ exports.Generator = require_generator.Generator; | ||
| exports.determineInitialRoutePath = require_utils.determineInitialRoutePath; | ||
| exports.ensureStringArgument = require_utils$1.ensureStringArgument; | ||
| exports.format = require_utils.format; | ||
@@ -20,0 +18,0 @@ exports.getConfig = require_config.getConfig; |
@@ -11,3 +11,2 @@ export { configSchema, getConfig, resolveConfigPath, baseConfigSchema, } from './config.cjs'; | ||
| export { rootPathId } from './filesystem/physical/rootPathId.cjs'; | ||
| export { ensureStringArgument } from './transform/utils.cjs'; | ||
| export type { TransformImportsConfig, TransformContext, TransformOptions, } from './transform/types.cjs'; | ||
| export type { TransformContext, TransformOptions } from './transform/types.cjs'; |
+15
-12
@@ -6,2 +6,5 @@ const require_utils = require("./utils.cjs"); | ||
| } | ||
| function serializeRoutePath(routePath) { | ||
| return JSON.stringify(routePath); | ||
| } | ||
| function getTargetTemplate(config) { | ||
@@ -35,4 +38,4 @@ const target = config.target; | ||
| imports: { | ||
| tsrImports: () => config.verboseFileRoutes === false ? "" : "import { createFileRoute } from '@tanstack/react-router';", | ||
| tsrExportStart: (routePath) => config.verboseFileRoutes === false ? "export const Route = createFileRoute(" : `export const Route = createFileRoute('${routePath}')(`, | ||
| tsrImports: () => "import { createFileRoute } from '@tanstack/react-router';", | ||
| tsrExportStart: (routePath) => `export const Route = createFileRoute(${serializeRoutePath(routePath)})(`, | ||
| tsrExportEnd: () => ");" | ||
@@ -49,4 +52,4 @@ } | ||
| imports: { | ||
| tsrImports: () => config.verboseFileRoutes === false ? "" : "import { createLazyFileRoute } from '@tanstack/react-router';", | ||
| tsrExportStart: (routePath) => config.verboseFileRoutes === false ? "export const Route = createLazyFileRoute(" : `export const Route = createLazyFileRoute('${routePath}')(`, | ||
| tsrImports: () => "import { createLazyFileRoute } from '@tanstack/react-router';", | ||
| tsrExportStart: (routePath) => `export const Route = createLazyFileRoute(${serializeRoutePath(routePath)})(`, | ||
| tsrExportEnd: () => ");" | ||
@@ -81,4 +84,4 @@ } | ||
| imports: { | ||
| tsrImports: () => config.verboseFileRoutes === false ? "" : "import { createFileRoute } from '@tanstack/solid-router';", | ||
| tsrExportStart: (routePath) => config.verboseFileRoutes === false ? "export const Route = createFileRoute(" : `export const Route = createFileRoute('${routePath}')(`, | ||
| tsrImports: () => "import { createFileRoute } from '@tanstack/solid-router';", | ||
| tsrExportStart: (routePath) => `export const Route = createFileRoute(${serializeRoutePath(routePath)})(`, | ||
| tsrExportEnd: () => ");" | ||
@@ -95,4 +98,4 @@ } | ||
| imports: { | ||
| tsrImports: () => config.verboseFileRoutes === false ? "" : "import { createLazyFileRoute } from '@tanstack/solid-router';", | ||
| tsrExportStart: (routePath) => config.verboseFileRoutes === false ? "export const Route = createLazyFileRoute(" : `export const Route = createLazyFileRoute('${routePath}')(`, | ||
| tsrImports: () => "import { createLazyFileRoute } from '@tanstack/solid-router';", | ||
| tsrExportStart: (routePath) => `export const Route = createLazyFileRoute(${serializeRoutePath(routePath)})(`, | ||
| tsrExportEnd: () => ");" | ||
@@ -128,4 +131,4 @@ } | ||
| imports: { | ||
| tsrImports: () => config.verboseFileRoutes === false ? "" : "import { createFileRoute } from '@tanstack/vue-router';", | ||
| tsrExportStart: (routePath) => config.verboseFileRoutes === false ? "export const Route = createFileRoute(" : `export const Route = createFileRoute('${routePath}')(`, | ||
| tsrImports: () => "import { createFileRoute } from '@tanstack/vue-router';", | ||
| tsrExportStart: (routePath) => `export const Route = createFileRoute(${serializeRoutePath(routePath)})(`, | ||
| tsrExportEnd: () => ");" | ||
@@ -143,4 +146,4 @@ } | ||
| imports: { | ||
| tsrImports: () => config.verboseFileRoutes === false ? "" : "import { createLazyFileRoute } from '@tanstack/vue-router';", | ||
| tsrExportStart: (routePath) => config.verboseFileRoutes === false ? "export const Route = createLazyFileRoute(" : `export const Route = createLazyFileRoute('${routePath}')(`, | ||
| tsrImports: () => "import { createLazyFileRoute } from '@tanstack/vue-router';", | ||
| tsrExportStart: (routePath) => `export const Route = createLazyFileRoute(${serializeRoutePath(routePath)})(`, | ||
| tsrExportEnd: () => ");" | ||
@@ -147,0 +150,0 @@ } |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"template.cjs","names":[],"sources":["../../src/template.ts"],"sourcesContent":["import { format } from './utils'\nimport type { Config } from './config'\n\ntype TemplateTag = 'tsrImports' | 'tsrPath' | 'tsrExportStart' | 'tsrExportEnd'\n\nexport function fillTemplate(\n config: Config,\n template: string,\n values: Record<TemplateTag, string>,\n) {\n const replaced = template.replace(\n /%%(\\w+)%%/g,\n (_, key) => values[key as TemplateTag] || '',\n )\n return format(replaced, config)\n}\n\nexport type TargetTemplate = {\n fullPkg: string\n subPkg: string\n rootRoute: {\n template: () => string\n imports: {\n tsrImports: () => string\n tsrExportStart: () => string\n tsrExportEnd: () => string\n }\n }\n route: {\n template: () => string\n imports: {\n tsrImports: () => string\n tsrExportStart: (routePath: string) => string\n tsrExportEnd: () => string\n }\n }\n lazyRoute: {\n template: () => string\n imports: {\n tsrImports: () => string\n tsrExportStart: (routePath: string) => string\n tsrExportEnd: () => string\n }\n }\n}\n\nexport function getTargetTemplate(config: Config): TargetTemplate {\n const target = config.target\n switch (target) {\n case 'react':\n return {\n fullPkg: '@tanstack/react-router',\n subPkg: 'react-router',\n rootRoute: {\n template: () =>\n [\n 'import * as React from \"react\"\\n',\n '%%tsrImports%%',\n '\\n\\n',\n '%%tsrExportStart%%{\\n component: RootComponent\\n }%%tsrExportEnd%%\\n\\n',\n 'function RootComponent() { return (<React.Fragment><div>Hello \"%%tsrPath%%\"!</div><Outlet /></React.Fragment>) };\\n',\n ].join(''),\n imports: {\n tsrImports: () =>\n \"import { Outlet, createRootRoute } from '@tanstack/react-router';\",\n tsrExportStart: () => 'export const Route = createRootRoute(',\n tsrExportEnd: () => ');',\n },\n },\n route: {\n template: () =>\n [\n '%%tsrImports%%',\n '\\n\\n',\n '%%tsrExportStart%%{\\n component: RouteComponent\\n }%%tsrExportEnd%%\\n\\n',\n 'function RouteComponent() { return <div>Hello \"%%tsrPath%%\"!</div> };\\n',\n ].join(''),\n imports: {\n tsrImports: () =>\n config.verboseFileRoutes === false\n ? ''\n : \"import { createFileRoute } from '@tanstack/react-router';\",\n tsrExportStart: (routePath) =>\n config.verboseFileRoutes === false\n ? 'export const Route = createFileRoute('\n : `export const Route = createFileRoute('${routePath}')(`,\n tsrExportEnd: () => ');',\n },\n },\n lazyRoute: {\n template: () =>\n [\n '%%tsrImports%%',\n '\\n\\n',\n '%%tsrExportStart%%{\\n component: RouteComponent\\n }%%tsrExportEnd%%\\n\\n',\n 'function RouteComponent() { return <div>Hello \"%%tsrPath%%\"!</div> };\\n',\n ].join(''),\n imports: {\n tsrImports: () =>\n config.verboseFileRoutes === false\n ? ''\n : \"import { createLazyFileRoute } from '@tanstack/react-router';\",\n tsrExportStart: (routePath) =>\n config.verboseFileRoutes === false\n ? 'export const Route = createLazyFileRoute('\n : `export const Route = createLazyFileRoute('${routePath}')(`,\n tsrExportEnd: () => ');',\n },\n },\n }\n case 'solid':\n return {\n fullPkg: '@tanstack/solid-router',\n subPkg: 'solid-router',\n rootRoute: {\n template: () =>\n [\n 'import * as Solid from \"solid-js\"\\n',\n '%%tsrImports%%',\n '\\n\\n',\n '%%tsrExportStart%%{\\n component: RootComponent\\n }%%tsrExportEnd%%\\n\\n',\n 'function RootComponent() { return (<><div>Hello \"%%tsrPath%%\"!</div><Outlet /></>) };\\n',\n ].join(''),\n imports: {\n tsrImports: () =>\n \"import { Outlet, createRootRoute } from '@tanstack/solid-router';\",\n tsrExportStart: () => 'export const Route = createRootRoute(',\n tsrExportEnd: () => ');',\n },\n },\n route: {\n template: () =>\n [\n '%%tsrImports%%',\n '\\n\\n',\n '%%tsrExportStart%%{\\n component: RouteComponent\\n }%%tsrExportEnd%%\\n\\n',\n 'function RouteComponent() { return <div>Hello \"%%tsrPath%%\"!</div> };\\n',\n ].join(''),\n imports: {\n tsrImports: () =>\n config.verboseFileRoutes === false\n ? ''\n : \"import { createFileRoute } from '@tanstack/solid-router';\",\n tsrExportStart: (routePath) =>\n config.verboseFileRoutes === false\n ? 'export const Route = createFileRoute('\n : `export const Route = createFileRoute('${routePath}')(`,\n tsrExportEnd: () => ');',\n },\n },\n lazyRoute: {\n template: () =>\n [\n '%%tsrImports%%',\n '\\n\\n',\n '%%tsrExportStart%%{\\n component: RouteComponent\\n }%%tsrExportEnd%%\\n\\n',\n 'function RouteComponent() { return <div>Hello \"%%tsrPath%%\"!</div> };\\n',\n ].join(''),\n imports: {\n tsrImports: () =>\n config.verboseFileRoutes === false\n ? ''\n : \"import { createLazyFileRoute } from '@tanstack/solid-router';\",\n\n tsrExportStart: (routePath) =>\n config.verboseFileRoutes === false\n ? 'export const Route = createLazyFileRoute('\n : `export const Route = createLazyFileRoute('${routePath}')(`,\n\n tsrExportEnd: () => ');',\n },\n },\n }\n case 'vue':\n return {\n fullPkg: '@tanstack/vue-router',\n subPkg: 'vue-router',\n rootRoute: {\n template: () =>\n [\n 'import { h } from \"vue\"\\n',\n '%%tsrImports%%',\n '\\n\\n',\n '%%tsrExportStart%%{\\n component: RootComponent\\n }%%tsrExportEnd%%\\n\\n',\n 'function RootComponent() { return h(\"div\", {}, [\"Hello \\\\\"%%tsrPath%%\\\\\"!\", h(Outlet)]) };\\n',\n ].join(''),\n imports: {\n tsrImports: () =>\n \"import { Outlet, createRootRoute } from '@tanstack/vue-router';\",\n tsrExportStart: () => 'export const Route = createRootRoute(',\n tsrExportEnd: () => ');',\n },\n },\n route: {\n template: () =>\n [\n 'import { h } from \"vue\"\\n',\n '%%tsrImports%%',\n '\\n\\n',\n '%%tsrExportStart%%{\\n component: RouteComponent\\n }%%tsrExportEnd%%\\n\\n',\n 'function RouteComponent() { return h(\"div\", {}, \"Hello \\\\\"%%tsrPath%%\\\\\"!\") };\\n',\n ].join(''),\n imports: {\n tsrImports: () =>\n config.verboseFileRoutes === false\n ? ''\n : \"import { createFileRoute } from '@tanstack/vue-router';\",\n tsrExportStart: (routePath) =>\n config.verboseFileRoutes === false\n ? 'export const Route = createFileRoute('\n : `export const Route = createFileRoute('${routePath}')(`,\n tsrExportEnd: () => ');',\n },\n },\n lazyRoute: {\n template: () =>\n [\n 'import { h } from \"vue\"\\n',\n '%%tsrImports%%',\n '\\n\\n',\n '%%tsrExportStart%%{\\n component: RouteComponent\\n }%%tsrExportEnd%%\\n\\n',\n 'function RouteComponent() { return h(\"div\", {}, \"Hello \\\\\"%%tsrPath%%\\\\\"!\") };\\n',\n ].join(''),\n imports: {\n tsrImports: () =>\n config.verboseFileRoutes === false\n ? ''\n : \"import { createLazyFileRoute } from '@tanstack/vue-router';\",\n\n tsrExportStart: (routePath) =>\n config.verboseFileRoutes === false\n ? 'export const Route = createLazyFileRoute('\n : `export const Route = createLazyFileRoute('${routePath}')(`,\n\n tsrExportEnd: () => ');',\n },\n },\n }\n default:\n throw new Error(`router-generator: Unknown target type: ${target}`)\n }\n}\n"],"mappings":";;AAKA,SAAgB,aACd,QACA,UACA,QACA;AAKA,QAAO,cAAA,OAJU,SAAS,QACxB,eACC,GAAG,QAAQ,OAAO,QAAuB,GAC3C,EACuB,OAAO;;AAgCjC,SAAgB,kBAAkB,QAAgC;CAChE,MAAM,SAAS,OAAO;AACtB,SAAQ,QAAR;EACE,KAAK,QACH,QAAO;GACL,SAAS;GACT,QAAQ;GACR,WAAW;IACT,gBACE;KACE;KACA;KACA;KACA;KACA;KACD,CAAC,KAAK,GAAG;IACZ,SAAS;KACP,kBACE;KACF,sBAAsB;KACtB,oBAAoB;KACrB;IACF;GACD,OAAO;IACL,gBACE;KACE;KACA;KACA;KACA;KACD,CAAC,KAAK,GAAG;IACZ,SAAS;KACP,kBACE,OAAO,sBAAsB,QACzB,KACA;KACN,iBAAiB,cACf,OAAO,sBAAsB,QACzB,0CACA,yCAAyC,UAAU;KACzD,oBAAoB;KACrB;IACF;GACD,WAAW;IACT,gBACE;KACE;KACA;KACA;KACA;KACD,CAAC,KAAK,GAAG;IACZ,SAAS;KACP,kBACE,OAAO,sBAAsB,QACzB,KACA;KACN,iBAAiB,cACf,OAAO,sBAAsB,QACzB,8CACA,6CAA6C,UAAU;KAC7D,oBAAoB;KACrB;IACF;GACF;EACH,KAAK,QACH,QAAO;GACL,SAAS;GACT,QAAQ;GACR,WAAW;IACT,gBACE;KACE;KACA;KACA;KACA;KACA;KACD,CAAC,KAAK,GAAG;IACZ,SAAS;KACP,kBACE;KACF,sBAAsB;KACtB,oBAAoB;KACrB;IACF;GACD,OAAO;IACL,gBACE;KACE;KACA;KACA;KACA;KACD,CAAC,KAAK,GAAG;IACZ,SAAS;KACP,kBACE,OAAO,sBAAsB,QACzB,KACA;KACN,iBAAiB,cACf,OAAO,sBAAsB,QACzB,0CACA,yCAAyC,UAAU;KACzD,oBAAoB;KACrB;IACF;GACD,WAAW;IACT,gBACE;KACE;KACA;KACA;KACA;KACD,CAAC,KAAK,GAAG;IACZ,SAAS;KACP,kBACE,OAAO,sBAAsB,QACzB,KACA;KAEN,iBAAiB,cACf,OAAO,sBAAsB,QACzB,8CACA,6CAA6C,UAAU;KAE7D,oBAAoB;KACrB;IACF;GACF;EACH,KAAK,MACH,QAAO;GACL,SAAS;GACT,QAAQ;GACR,WAAW;IACT,gBACE;KACE;KACA;KACA;KACA;KACA;KACD,CAAC,KAAK,GAAG;IACZ,SAAS;KACP,kBACE;KACF,sBAAsB;KACtB,oBAAoB;KACrB;IACF;GACD,OAAO;IACL,gBACE;KACE;KACA;KACA;KACA;KACA;KACD,CAAC,KAAK,GAAG;IACZ,SAAS;KACP,kBACE,OAAO,sBAAsB,QACzB,KACA;KACN,iBAAiB,cACf,OAAO,sBAAsB,QACzB,0CACA,yCAAyC,UAAU;KACzD,oBAAoB;KACrB;IACF;GACD,WAAW;IACT,gBACE;KACE;KACA;KACA;KACA;KACA;KACD,CAAC,KAAK,GAAG;IACZ,SAAS;KACP,kBACE,OAAO,sBAAsB,QACzB,KACA;KAEN,iBAAiB,cACf,OAAO,sBAAsB,QACzB,8CACA,6CAA6C,UAAU;KAE7D,oBAAoB;KACrB;IACF;GACF;EACH,QACE,OAAM,IAAI,MAAM,0CAA0C,SAAS"} | ||
| {"version":3,"file":"template.cjs","names":[],"sources":["../../src/template.ts"],"sourcesContent":["import { format } from './utils'\nimport type { Config } from './config'\n\ntype TemplateTag = 'tsrImports' | 'tsrPath' | 'tsrExportStart' | 'tsrExportEnd'\n\nexport function fillTemplate(\n config: Config,\n template: string,\n values: Record<TemplateTag, string>,\n) {\n const replaced = template.replace(\n /%%(\\w+)%%/g,\n (_, key) => values[key as TemplateTag] || '',\n )\n return format(replaced, config)\n}\n\nexport type TargetTemplate = {\n fullPkg: string\n subPkg: string\n rootRoute: {\n template: () => string\n imports: {\n tsrImports: () => string\n tsrExportStart: () => string\n tsrExportEnd: () => string\n }\n }\n route: {\n template: () => string\n imports: {\n tsrImports: () => string\n tsrExportStart: (routePath: string) => string\n tsrExportEnd: () => string\n }\n }\n lazyRoute: {\n template: () => string\n imports: {\n tsrImports: () => string\n tsrExportStart: (routePath: string) => string\n tsrExportEnd: () => string\n }\n }\n}\n\nfunction serializeRoutePath(routePath: string) {\n return JSON.stringify(routePath)\n}\n\nexport function getTargetTemplate(config: Config): TargetTemplate {\n const target = config.target\n switch (target) {\n case 'react':\n return {\n fullPkg: '@tanstack/react-router',\n subPkg: 'react-router',\n rootRoute: {\n template: () =>\n [\n 'import * as React from \"react\"\\n',\n '%%tsrImports%%',\n '\\n\\n',\n '%%tsrExportStart%%{\\n component: RootComponent\\n }%%tsrExportEnd%%\\n\\n',\n 'function RootComponent() { return (<React.Fragment><div>Hello \"%%tsrPath%%\"!</div><Outlet /></React.Fragment>) };\\n',\n ].join(''),\n imports: {\n tsrImports: () =>\n \"import { Outlet, createRootRoute } from '@tanstack/react-router';\",\n tsrExportStart: () => 'export const Route = createRootRoute(',\n tsrExportEnd: () => ');',\n },\n },\n route: {\n template: () =>\n [\n '%%tsrImports%%',\n '\\n\\n',\n '%%tsrExportStart%%{\\n component: RouteComponent\\n }%%tsrExportEnd%%\\n\\n',\n 'function RouteComponent() { return <div>Hello \"%%tsrPath%%\"!</div> };\\n',\n ].join(''),\n imports: {\n tsrImports: () =>\n \"import { createFileRoute } from '@tanstack/react-router';\",\n tsrExportStart: (routePath) =>\n `export const Route = createFileRoute(${serializeRoutePath(routePath)})(`,\n tsrExportEnd: () => ');',\n },\n },\n lazyRoute: {\n template: () =>\n [\n '%%tsrImports%%',\n '\\n\\n',\n '%%tsrExportStart%%{\\n component: RouteComponent\\n }%%tsrExportEnd%%\\n\\n',\n 'function RouteComponent() { return <div>Hello \"%%tsrPath%%\"!</div> };\\n',\n ].join(''),\n imports: {\n tsrImports: () =>\n \"import { createLazyFileRoute } from '@tanstack/react-router';\",\n tsrExportStart: (routePath) =>\n `export const Route = createLazyFileRoute(${serializeRoutePath(routePath)})(`,\n tsrExportEnd: () => ');',\n },\n },\n }\n case 'solid':\n return {\n fullPkg: '@tanstack/solid-router',\n subPkg: 'solid-router',\n rootRoute: {\n template: () =>\n [\n 'import * as Solid from \"solid-js\"\\n',\n '%%tsrImports%%',\n '\\n\\n',\n '%%tsrExportStart%%{\\n component: RootComponent\\n }%%tsrExportEnd%%\\n\\n',\n 'function RootComponent() { return (<><div>Hello \"%%tsrPath%%\"!</div><Outlet /></>) };\\n',\n ].join(''),\n imports: {\n tsrImports: () =>\n \"import { Outlet, createRootRoute } from '@tanstack/solid-router';\",\n tsrExportStart: () => 'export const Route = createRootRoute(',\n tsrExportEnd: () => ');',\n },\n },\n route: {\n template: () =>\n [\n '%%tsrImports%%',\n '\\n\\n',\n '%%tsrExportStart%%{\\n component: RouteComponent\\n }%%tsrExportEnd%%\\n\\n',\n 'function RouteComponent() { return <div>Hello \"%%tsrPath%%\"!</div> };\\n',\n ].join(''),\n imports: {\n tsrImports: () =>\n \"import { createFileRoute } from '@tanstack/solid-router';\",\n tsrExportStart: (routePath) =>\n `export const Route = createFileRoute(${serializeRoutePath(routePath)})(`,\n tsrExportEnd: () => ');',\n },\n },\n lazyRoute: {\n template: () =>\n [\n '%%tsrImports%%',\n '\\n\\n',\n '%%tsrExportStart%%{\\n component: RouteComponent\\n }%%tsrExportEnd%%\\n\\n',\n 'function RouteComponent() { return <div>Hello \"%%tsrPath%%\"!</div> };\\n',\n ].join(''),\n imports: {\n tsrImports: () =>\n \"import { createLazyFileRoute } from '@tanstack/solid-router';\",\n\n tsrExportStart: (routePath) =>\n `export const Route = createLazyFileRoute(${serializeRoutePath(routePath)})(`,\n\n tsrExportEnd: () => ');',\n },\n },\n }\n case 'vue':\n return {\n fullPkg: '@tanstack/vue-router',\n subPkg: 'vue-router',\n rootRoute: {\n template: () =>\n [\n 'import { h } from \"vue\"\\n',\n '%%tsrImports%%',\n '\\n\\n',\n '%%tsrExportStart%%{\\n component: RootComponent\\n }%%tsrExportEnd%%\\n\\n',\n 'function RootComponent() { return h(\"div\", {}, [\"Hello \\\\\"%%tsrPath%%\\\\\"!\", h(Outlet)]) };\\n',\n ].join(''),\n imports: {\n tsrImports: () =>\n \"import { Outlet, createRootRoute } from '@tanstack/vue-router';\",\n tsrExportStart: () => 'export const Route = createRootRoute(',\n tsrExportEnd: () => ');',\n },\n },\n route: {\n template: () =>\n [\n 'import { h } from \"vue\"\\n',\n '%%tsrImports%%',\n '\\n\\n',\n '%%tsrExportStart%%{\\n component: RouteComponent\\n }%%tsrExportEnd%%\\n\\n',\n 'function RouteComponent() { return h(\"div\", {}, \"Hello \\\\\"%%tsrPath%%\\\\\"!\") };\\n',\n ].join(''),\n imports: {\n tsrImports: () =>\n \"import { createFileRoute } from '@tanstack/vue-router';\",\n tsrExportStart: (routePath) =>\n `export const Route = createFileRoute(${serializeRoutePath(routePath)})(`,\n tsrExportEnd: () => ');',\n },\n },\n lazyRoute: {\n template: () =>\n [\n 'import { h } from \"vue\"\\n',\n '%%tsrImports%%',\n '\\n\\n',\n '%%tsrExportStart%%{\\n component: RouteComponent\\n }%%tsrExportEnd%%\\n\\n',\n 'function RouteComponent() { return h(\"div\", {}, \"Hello \\\\\"%%tsrPath%%\\\\\"!\") };\\n',\n ].join(''),\n imports: {\n tsrImports: () =>\n \"import { createLazyFileRoute } from '@tanstack/vue-router';\",\n\n tsrExportStart: (routePath) =>\n `export const Route = createLazyFileRoute(${serializeRoutePath(routePath)})(`,\n\n tsrExportEnd: () => ');',\n },\n },\n }\n default:\n throw new Error(`router-generator: Unknown target type: ${target}`)\n }\n}\n"],"mappings":";;AAKA,SAAgB,aACd,QACA,UACA,QACA;AAKA,QAAO,cAAA,OAJU,SAAS,QACxB,eACC,GAAG,QAAQ,OAAO,QAAuB,GAC3C,EACuB,OAAO;;AAgCjC,SAAS,mBAAmB,WAAmB;AAC7C,QAAO,KAAK,UAAU,UAAU;;AAGlC,SAAgB,kBAAkB,QAAgC;CAChE,MAAM,SAAS,OAAO;AACtB,SAAQ,QAAR;EACE,KAAK,QACH,QAAO;GACL,SAAS;GACT,QAAQ;GACR,WAAW;IACT,gBACE;KACE;KACA;KACA;KACA;KACA;KACD,CAAC,KAAK,GAAG;IACZ,SAAS;KACP,kBACE;KACF,sBAAsB;KACtB,oBAAoB;KACrB;IACF;GACD,OAAO;IACL,gBACE;KACE;KACA;KACA;KACA;KACD,CAAC,KAAK,GAAG;IACZ,SAAS;KACP,kBACE;KACF,iBAAiB,cACf,wCAAwC,mBAAmB,UAAU,CAAC;KACxE,oBAAoB;KACrB;IACF;GACD,WAAW;IACT,gBACE;KACE;KACA;KACA;KACA;KACD,CAAC,KAAK,GAAG;IACZ,SAAS;KACP,kBACE;KACF,iBAAiB,cACf,4CAA4C,mBAAmB,UAAU,CAAC;KAC5E,oBAAoB;KACrB;IACF;GACF;EACH,KAAK,QACH,QAAO;GACL,SAAS;GACT,QAAQ;GACR,WAAW;IACT,gBACE;KACE;KACA;KACA;KACA;KACA;KACD,CAAC,KAAK,GAAG;IACZ,SAAS;KACP,kBACE;KACF,sBAAsB;KACtB,oBAAoB;KACrB;IACF;GACD,OAAO;IACL,gBACE;KACE;KACA;KACA;KACA;KACD,CAAC,KAAK,GAAG;IACZ,SAAS;KACP,kBACE;KACF,iBAAiB,cACf,wCAAwC,mBAAmB,UAAU,CAAC;KACxE,oBAAoB;KACrB;IACF;GACD,WAAW;IACT,gBACE;KACE;KACA;KACA;KACA;KACD,CAAC,KAAK,GAAG;IACZ,SAAS;KACP,kBACE;KAEF,iBAAiB,cACf,4CAA4C,mBAAmB,UAAU,CAAC;KAE5E,oBAAoB;KACrB;IACF;GACF;EACH,KAAK,MACH,QAAO;GACL,SAAS;GACT,QAAQ;GACR,WAAW;IACT,gBACE;KACE;KACA;KACA;KACA;KACA;KACD,CAAC,KAAK,GAAG;IACZ,SAAS;KACP,kBACE;KACF,sBAAsB;KACtB,oBAAoB;KACrB;IACF;GACD,OAAO;IACL,gBACE;KACE;KACA;KACA;KACA;KACA;KACD,CAAC,KAAK,GAAG;IACZ,SAAS;KACP,kBACE;KACF,iBAAiB,cACf,wCAAwC,mBAAmB,UAAU,CAAC;KACxE,oBAAoB;KACrB;IACF;GACD,WAAW;IACT,gBACE;KACE;KACA;KACA;KACA;KACA;KACD,CAAC,KAAK,GAAG;IACZ,SAAS;KACP,kBACE;KAEF,iBAAiB,cACf,4CAA4C,mBAAmB,UAAU,CAAC;KAE5E,oBAAoB;KACrB;IACF;GACF;EACH,QACE,OAAM,IAAI,MAAM,0CAA0C,SAAS"} |
+283
-266
@@ -1,298 +0,315 @@ | ||
| require("../_virtual/_rolldown/runtime.cjs"); | ||
| const require_utils = require("../utils.cjs"); | ||
| const require_utils$1 = require("./utils.cjs"); | ||
| const require_runtime = require("../_virtual/_rolldown/runtime.cjs"); | ||
| let magic_string = require("magic-string"); | ||
| magic_string = require_runtime.__toESM(magic_string); | ||
| let _babel_types = require("@babel/types"); | ||
| _babel_types = require_runtime.__toESM(_babel_types); | ||
| let _tanstack_router_utils = require("@tanstack/router-utils"); | ||
| let recast = require("recast"); | ||
| let source_map = require("source-map"); | ||
| //#region src/transform/transform.ts | ||
| var b = recast.types.builders; | ||
| async function transform({ ctx, source, node }) { | ||
| let appliedChanges = false; | ||
| var routeConstructors = ["createFileRoute", "createLazyFileRoute"]; | ||
| function transform({ ctx, source, node }) { | ||
| let ast; | ||
| try { | ||
| ast = (0, recast.parse)(source, { | ||
| sourceFileName: "output.ts", | ||
| parser: { parse(code) { | ||
| return (0, _tanstack_router_utils.parseAst)({ | ||
| code, | ||
| tokens: true | ||
| }); | ||
| } } | ||
| }); | ||
| } catch (e) { | ||
| console.error("Error parsing code", ctx.routeId, source, e); | ||
| ast = (0, _tanstack_router_utils.parseAst)({ code: source }); | ||
| } catch (error) { | ||
| return { | ||
| result: "error", | ||
| error: e | ||
| error | ||
| }; | ||
| } | ||
| const preferredQuote = detectPreferredQuoteStyle(ast); | ||
| let routeExportHandled = false; | ||
| function onExportFound(decl) { | ||
| if (decl.init?.type === "CallExpression") { | ||
| const callExpression = decl.init; | ||
| const firstArgument = callExpression.arguments[0]; | ||
| if (firstArgument) { | ||
| if (firstArgument.type === "ObjectExpression") { | ||
| const staticProperties = firstArgument.properties.flatMap((p) => { | ||
| if (p.type === "ObjectProperty" && p.key.type === "Identifier") return p.key.name; | ||
| return []; | ||
| }); | ||
| node.createFileRouteProps = new Set(staticProperties); | ||
| } | ||
| } | ||
| let identifier; | ||
| if (callExpression.callee.type === "Identifier") { | ||
| identifier = callExpression.callee; | ||
| if (ctx.verboseFileRoutes) { | ||
| callExpression.callee = b.callExpression(identifier, [b.stringLiteral(ctx.routeId)]); | ||
| appliedChanges = true; | ||
| } | ||
| } else if (callExpression.callee.type === "CallExpression" && callExpression.callee.callee.type === "Identifier") { | ||
| identifier = callExpression.callee.callee; | ||
| if (!ctx.verboseFileRoutes) { | ||
| callExpression.callee = identifier; | ||
| appliedChanges = true; | ||
| } else appliedChanges = require_utils$1.ensureStringArgument(callExpression.callee, ctx.routeId, ctx.preferredQuote); | ||
| } | ||
| if (identifier === void 0) throw new Error(`expected identifier to be present in ${ctx.routeId} for export "Route"`); | ||
| if (identifier.name === "createFileRoute" && ctx.lazy) { | ||
| identifier.name = "createLazyFileRoute"; | ||
| appliedChanges = true; | ||
| } else if (identifier.name === "createLazyFileRoute" && !ctx.lazy) { | ||
| identifier.name = "createFileRoute"; | ||
| appliedChanges = true; | ||
| } | ||
| } else throw new Error(`expected "Route" export to be initialized by a CallExpression`); | ||
| routeExportHandled = true; | ||
| const exportedRouteNames = getExportedRouteNames(ast.program.body); | ||
| if (exportedRouteNames.size === 0) return { result: "no-route-export" }; | ||
| const { calls: routeCalls, hasUnsupportedRouteId, hasMalformedRouteCall } = findExportedRouteCalls(ast.program.body, exportedRouteNames); | ||
| if (routeCalls.length === 0 && hasMalformedRouteCall) return { | ||
| result: "error", | ||
| error: /* @__PURE__ */ new Error(`expected Route export in ${ctx.routeId} to use createFileRoute('/path')({...}) or createLazyFileRoute('/path')({...})`) | ||
| }; | ||
| if (routeCalls.length === 0 && hasUnsupportedRouteId) return { | ||
| result: "error", | ||
| error: /* @__PURE__ */ new Error(`expected route id to be a string literal or plain template literal in ${ctx.routeId}`) | ||
| }; | ||
| if (routeCalls.length === 0) return { result: "not-modified" }; | ||
| if (routeCalls.length > 1) return { | ||
| result: "error", | ||
| error: /* @__PURE__ */ new Error(`expected exactly one createFileRoute/createLazyFileRoute call in ${ctx.routeId}`) | ||
| }; | ||
| const routeCall = routeCalls[0]; | ||
| const routeIdQuote = getRouteIdQuote(source, routeCall.routeIdArg); | ||
| const createFileRouteProps = getCreateFileRouteProps(routeCall.optionsArg); | ||
| if (createFileRouteProps) node.createFileRouteProps = createFileRouteProps; | ||
| const expectedCallee = getExpectedRouteConstructor(ctx.lazy); | ||
| const expectedRouteId = `${routeIdQuote}${ctx.routeId}${routeIdQuote}`; | ||
| const currentRouteId = source.slice(routeCall.routeIdArg.start, routeCall.routeIdArg.end); | ||
| const targetModule = `@tanstack/${ctx.target}-router`; | ||
| const imports = parseTargetImports(ast.program.body, source, targetModule); | ||
| const s = new magic_string.default(source); | ||
| let modified = false; | ||
| if (routeCall.callee.name !== expectedCallee) { | ||
| s.update(routeCall.callee.start, routeCall.callee.end, expectedCallee); | ||
| modified = true; | ||
| } | ||
| const program = ast.program; | ||
| for (const n of program.body) { | ||
| if (n.type === "ExportNamedDeclaration") { | ||
| if (n.declaration?.type === "VariableDeclaration") { | ||
| const decl = n.declaration.declarations[0]; | ||
| if (decl && decl.type === "VariableDeclarator" && decl.id.type === "Identifier") { | ||
| if (decl.id.name === "Route") onExportFound(decl); | ||
| } | ||
| } else if (n.declaration === null && n.specifiers) { | ||
| for (const spec of n.specifiers) if (typeof spec.exported.name === "string") { | ||
| if (spec.exported.name === "Route") { | ||
| const variableName = spec.local?.name || spec.exported.name; | ||
| for (const decl of program.body) if (decl.type === "VariableDeclaration" && decl.declarations[0]) { | ||
| const variable = decl.declarations[0]; | ||
| if (variable.type === "VariableDeclarator" && variable.id.type === "Identifier" && variable.id.name === variableName) { | ||
| onExportFound(variable); | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| if (routeExportHandled) break; | ||
| if (currentRouteId !== expectedRouteId) { | ||
| s.update(routeCall.routeIdArg.start, routeCall.routeIdArg.end, expectedRouteId); | ||
| modified = true; | ||
| } | ||
| if (!routeExportHandled) return { result: "no-route-export" }; | ||
| const imports = { | ||
| required: [], | ||
| banned: [] | ||
| if (updateRouteImports({ | ||
| imports, | ||
| source, | ||
| s, | ||
| targetModule, | ||
| required: expectedCallee, | ||
| lineEnding: getLineEnding(source) | ||
| })) modified = true; | ||
| if (!modified) return { result: "not-modified" }; | ||
| return { | ||
| result: "modified", | ||
| output: s.toString() | ||
| }; | ||
| const targetModule = `@tanstack/${ctx.target}-router`; | ||
| if (ctx.verboseFileRoutes === false) imports.banned = [{ | ||
| source: targetModule, | ||
| specifiers: [{ imported: "createLazyFileRoute" }, { imported: "createFileRoute" }] | ||
| }]; | ||
| else if (ctx.lazy) { | ||
| imports.required = [{ | ||
| source: targetModule, | ||
| specifiers: [{ imported: "createLazyFileRoute" }] | ||
| }]; | ||
| imports.banned = [{ | ||
| source: targetModule, | ||
| specifiers: [{ imported: "createFileRoute" }] | ||
| }]; | ||
| } else { | ||
| imports.required = [{ | ||
| source: targetModule, | ||
| specifiers: [{ imported: "createFileRoute" }] | ||
| }]; | ||
| imports.banned = [{ | ||
| source: targetModule, | ||
| specifiers: [{ imported: "createLazyFileRoute" }] | ||
| }]; | ||
| } | ||
| function getExportedRouteNames(body) { | ||
| const exportedRouteNames = /* @__PURE__ */ new Set(); | ||
| for (const statement of body) { | ||
| if (!_babel_types.isExportNamedDeclaration(statement) || statement.source) continue; | ||
| if (_babel_types.isVariableDeclaration(statement.declaration)) { | ||
| for (const declarator of statement.declaration.declarations) if (_babel_types.isIdentifier(declarator.id) && declarator.id.name === "Route") exportedRouteNames.add("Route"); | ||
| } | ||
| for (const specifier of statement.specifiers) { | ||
| if (!_babel_types.isExportSpecifier(specifier) || getExportedName(specifier.exported) !== "Route") continue; | ||
| const localName = getLocalBindingName(specifier.local); | ||
| if (localName) exportedRouteNames.add(localName); | ||
| } | ||
| } | ||
| imports.required = require_utils.mergeImportDeclarations(imports.required); | ||
| imports.banned = require_utils.mergeImportDeclarations(imports.banned); | ||
| const importStatementCandidates = []; | ||
| const importDeclarationsToRemove = []; | ||
| for (const n of program.body) { | ||
| const findImport = (opts) => (i) => { | ||
| if (i.source === opts.source) { | ||
| const importKind = i.importKind || "value"; | ||
| return (opts.importKind || "value") === importKind; | ||
| return exportedRouteNames; | ||
| } | ||
| function findExportedRouteCalls(body, exportedRouteNames) { | ||
| const calls = []; | ||
| let hasUnsupportedRouteId = false; | ||
| let hasMalformedRouteCall = false; | ||
| for (const statement of body) { | ||
| const declaration = getVariableDeclaration(statement); | ||
| if (!declaration) continue; | ||
| for (const declarator of declaration.declarations) { | ||
| if (!_babel_types.isIdentifier(declarator.id) || !exportedRouteNames.has(declarator.id.name)) continue; | ||
| const init = getRouteConstructorInit(declarator.init); | ||
| if (!init) { | ||
| if (isDirectRouteConstructorCall(declarator.init)) hasMalformedRouteCall = true; | ||
| continue; | ||
| } | ||
| return false; | ||
| }; | ||
| if (n.type === "ImportDeclaration" && typeof n.source.value === "string") { | ||
| const filterImport = findImport({ | ||
| source: n.source.value, | ||
| importKind: n.importKind | ||
| const routeIdArg = init.innerCall.arguments[0]; | ||
| if (isSupportedRouteId(routeIdArg)) calls.push({ | ||
| callee: init.callee, | ||
| routeIdArg, | ||
| optionsArg: init.outerCall.arguments[0] | ||
| }); | ||
| let requiredImports = imports.required.filter(filterImport)[0]; | ||
| const bannedImports = imports.banned.filter(filterImport)[0]; | ||
| if (!requiredImports && !bannedImports) continue; | ||
| const importSpecifiersToRemove = []; | ||
| if (n.specifiers) { | ||
| for (const spec of n.specifiers) { | ||
| if (!requiredImports && !bannedImports) break; | ||
| if (spec.type === "ImportSpecifier" && typeof spec.imported.name === "string") { | ||
| if (requiredImports) { | ||
| const requiredImportIndex = requiredImports.specifiers.findIndex((imp) => imp.imported === spec.imported.name); | ||
| if (requiredImportIndex !== -1) { | ||
| requiredImports.specifiers.splice(requiredImportIndex, 1); | ||
| if (requiredImports.specifiers.length === 0) { | ||
| imports.required = imports.required.splice(imports.required.indexOf(requiredImports), 1); | ||
| requiredImports = void 0; | ||
| } | ||
| } else importStatementCandidates.push(n); | ||
| } | ||
| if (bannedImports) { | ||
| if (bannedImports.specifiers.findIndex((imp) => imp.imported === spec.imported.name) !== -1) importSpecifiersToRemove.push(spec); | ||
| } | ||
| } | ||
| } | ||
| if (importSpecifiersToRemove.length > 0) { | ||
| appliedChanges = true; | ||
| n.specifiers = n.specifiers.filter((spec) => !importSpecifiersToRemove.includes(spec)); | ||
| if (n.specifiers.length === 0) importDeclarationsToRemove.push(n); | ||
| } | ||
| } | ||
| else hasUnsupportedRouteId = true; | ||
| } | ||
| } | ||
| imports.required.forEach((requiredImport) => { | ||
| if (requiredImport.specifiers.length > 0) { | ||
| appliedChanges = true; | ||
| if (importStatementCandidates.length > 0) { | ||
| const importStatement = importStatementCandidates.find((importStatement) => { | ||
| if (importStatement.source.value === requiredImport.source) return (importStatement.importKind || "value") === (requiredImport.importKind || "value"); | ||
| return false; | ||
| }); | ||
| if (importStatement) { | ||
| if (importStatement.specifiers === void 0) importStatement.specifiers = []; | ||
| const importSpecifiersToAdd = requiredImport.specifiers.map((spec) => b.importSpecifier(b.identifier(spec.imported), b.identifier(spec.imported))); | ||
| importStatement.specifiers = [...importStatement.specifiers, ...importSpecifiersToAdd]; | ||
| return; | ||
| } | ||
| } | ||
| const importStatement = b.importDeclaration(requiredImport.specifiers.map((spec) => b.importSpecifier(b.identifier(spec.imported), spec.local ? b.identifier(spec.local) : null)), b.stringLiteral(requiredImport.source)); | ||
| program.body.unshift(importStatement); | ||
| return { | ||
| calls, | ||
| hasUnsupportedRouteId, | ||
| hasMalformedRouteCall | ||
| }; | ||
| } | ||
| function getVariableDeclaration(statement) { | ||
| const declaration = _babel_types.isExportNamedDeclaration(statement) ? statement.declaration : statement; | ||
| return _babel_types.isVariableDeclaration(declaration) ? declaration : null; | ||
| } | ||
| function getExportedName(node) { | ||
| return _babel_types.isIdentifier(node) ? node.name : node.value; | ||
| } | ||
| function getLocalBindingName(node) { | ||
| return _babel_types.isIdentifier(node) ? node.name : null; | ||
| } | ||
| function getRouteConstructorInit(expression) { | ||
| if (!expression || !_babel_types.isCallExpression(expression)) return null; | ||
| if (!_babel_types.isCallExpression(expression.callee)) return null; | ||
| const innerCall = expression.callee; | ||
| if (!_babel_types.isIdentifier(innerCall.callee) || !isRouteConstructor(innerCall.callee)) return null; | ||
| return { | ||
| callee: innerCall.callee, | ||
| outerCall: expression, | ||
| innerCall | ||
| }; | ||
| } | ||
| function isDirectRouteConstructorCall(expression) { | ||
| return !!expression && _babel_types.isCallExpression(expression) && _babel_types.isIdentifier(expression.callee) && isRouteConstructor(expression.callee); | ||
| } | ||
| function isRouteConstructor(callee) { | ||
| return routeConstructors.includes(callee.name); | ||
| } | ||
| function isSupportedRouteId(arg) { | ||
| return !!arg && (_babel_types.isStringLiteral(arg) || _babel_types.isTemplateLiteral(arg) && arg.expressions.length === 0); | ||
| } | ||
| function getRouteIdQuote(source, arg) { | ||
| const raw = source.slice(arg.start, arg.end); | ||
| if (raw.startsWith("'")) return "'"; | ||
| if (raw.startsWith("\"")) return "\""; | ||
| return "`"; | ||
| } | ||
| function getCreateFileRouteProps(arg) { | ||
| if (!arg || !_babel_types.isObjectExpression(arg)) return; | ||
| const props = /* @__PURE__ */ new Set(); | ||
| for (const property of arg.properties) { | ||
| if (!_babel_types.isObjectProperty(property) || property.computed) continue; | ||
| if (_babel_types.isIdentifier(property.key)) { | ||
| props.add(property.key.name); | ||
| continue; | ||
| } | ||
| if (_babel_types.isStringLiteral(property.key)) props.add(property.key.value); | ||
| } | ||
| return props; | ||
| } | ||
| function parseTargetImports(body, source, targetModule) { | ||
| const imports = []; | ||
| for (const statement of body) { | ||
| if (!_babel_types.isImportDeclaration(statement) || statement.importKind === "type" || statement.source.value !== targetModule) continue; | ||
| const rawSource = source.slice(statement.source.start, statement.source.end); | ||
| const importStatement = source.slice(statement.start, statement.end); | ||
| imports.push({ | ||
| declaration: statement, | ||
| defaultImport: statement.specifiers.find((specifier) => _babel_types.isImportDefaultSpecifier(specifier))?.local.name, | ||
| namespace: statement.specifiers.find((specifier) => _babel_types.isImportNamespaceSpecifier(specifier))?.local.name, | ||
| named: statement.specifiers.filter((specifier) => _babel_types.isImportSpecifier(specifier)).map((specifier) => ({ | ||
| imported: _babel_types.isIdentifier(specifier.imported) ? specifier.imported.name : specifier.imported.value, | ||
| local: specifier.local.name, | ||
| importKind: specifier.importKind ?? void 0 | ||
| })), | ||
| moduleName: statement.source.value, | ||
| quote: rawSource[0], | ||
| semicolon: importStatement.trimEnd().endsWith(";") | ||
| }); | ||
| } | ||
| return imports; | ||
| } | ||
| function updateRouteImports({ imports, source, s, targetModule, required, lineEnding }) { | ||
| const analysis = analyzeRouteImports(imports, required); | ||
| if (analysis.kind === "ok") return false; | ||
| if (analysis.kind === "rename") { | ||
| s.update(analysis.imported.start, analysis.imported.end, analysis.next); | ||
| s.update(analysis.local.start, analysis.local.end, analysis.next); | ||
| return true; | ||
| } | ||
| return normalizeRouteImports({ | ||
| imports, | ||
| source, | ||
| s, | ||
| targetModule, | ||
| required, | ||
| lineEnding | ||
| }); | ||
| if (importDeclarationsToRemove.length > 0) { | ||
| appliedChanges = true; | ||
| for (const importDeclaration of importDeclarationsToRemove) if (importDeclaration.specifiers?.length === 0) { | ||
| const index = program.body.indexOf(importDeclaration); | ||
| if (index !== -1) program.body.splice(index, 1); | ||
| } | ||
| function analyzeRouteImports(imports, required) { | ||
| const opposite = getOtherRouteConstructor(required); | ||
| let requiredCount = 0; | ||
| let oppositeCount = 0; | ||
| let renameCandidate; | ||
| for (const declaration of imports) for (const specifier of declaration.declaration.specifiers) { | ||
| if (!_babel_types.isImportSpecifier(specifier)) continue; | ||
| const imported = specifier.imported; | ||
| if (!_babel_types.isIdentifier(imported)) return { kind: "normalize" }; | ||
| if (!isRouteConstructorName(imported.name)) continue; | ||
| if (specifier.local.name !== imported.name) return { kind: "normalize" }; | ||
| if (imported.name === required) { | ||
| requiredCount++; | ||
| continue; | ||
| } | ||
| if (imported.name === opposite) { | ||
| oppositeCount++; | ||
| renameCandidate = { | ||
| imported, | ||
| local: specifier.local, | ||
| next: required | ||
| }; | ||
| } | ||
| } | ||
| if (!appliedChanges) return { result: "not-modified" }; | ||
| const printResult = (0, recast.print)(ast, { | ||
| reuseWhitespace: true, | ||
| sourceMapName: "output.map" | ||
| }); | ||
| let transformedCode = printResult.code; | ||
| if (printResult.map) transformedCode = await fixTransformedOutputText({ | ||
| originalCode: source, | ||
| transformedCode, | ||
| sourceMap: printResult.map, | ||
| preferredQuote | ||
| }); | ||
| return { | ||
| result: "modified", | ||
| output: transformedCode | ||
| if (requiredCount === 1 && oppositeCount === 0) return { kind: "ok" }; | ||
| if (requiredCount === 0 && oppositeCount === 1 && renameCandidate) return { | ||
| kind: "rename", | ||
| ...renameCandidate | ||
| }; | ||
| return { kind: "normalize" }; | ||
| } | ||
| async function fixTransformedOutputText({ originalCode, transformedCode, sourceMap, preferredQuote }) { | ||
| const originalLines = originalCode.split("\n"); | ||
| const transformedLines = transformedCode.split("\n"); | ||
| const defaultUsesSemicolons = detectSemicolonUsage(originalCode); | ||
| const consumer = await new source_map.SourceMapConsumer(sourceMap); | ||
| return transformedLines.map((line, i) => { | ||
| const transformedLineNum = i + 1; | ||
| let origLineText = void 0; | ||
| for (let col = 0; col < line.length; col++) { | ||
| const mapped = consumer.originalPositionFor({ | ||
| line: transformedLineNum, | ||
| column: col | ||
| }); | ||
| if (mapped.line != null && mapped.line > 0) { | ||
| origLineText = originalLines[mapped.line - 1]; | ||
| break; | ||
| } | ||
| function normalizeRouteImports({ imports, source, s, targetModule, required, lineEnding }) { | ||
| const owner = imports.find((declaration) => hasNamedImport(declaration.named, required)) ?? imports.find((declaration) => !declaration.namespace); | ||
| let modified = false; | ||
| for (const declaration of imports) { | ||
| const named = normalizeNamedImports({ | ||
| named: declaration.named, | ||
| required, | ||
| isOwner: declaration === owner | ||
| }); | ||
| if (sameNamedImports(declaration.named, named)) continue; | ||
| const replacement = renderImportDeclaration({ | ||
| ...declaration, | ||
| named | ||
| }); | ||
| if (replacement === null) { | ||
| s.remove(declaration.declaration.start, getRemovalEnd(source, declaration.declaration.end)); | ||
| modified = true; | ||
| continue; | ||
| } | ||
| if (origLineText !== void 0) { | ||
| if (origLineText === line) return origLineText; | ||
| return fixLine(line, { | ||
| originalLine: origLineText, | ||
| useOriginalSemicolon: true, | ||
| useOriginalQuotes: true, | ||
| fallbackQuote: preferredQuote | ||
| }); | ||
| } else return fixLine(line, { | ||
| originalLine: null, | ||
| useOriginalSemicolon: false, | ||
| useOriginalQuotes: false, | ||
| fallbackQuote: preferredQuote, | ||
| fallbackSemicolon: defaultUsesSemicolons | ||
| }); | ||
| }).join("\n"); | ||
| s.update(declaration.declaration.start, declaration.declaration.end, replacement); | ||
| modified = true; | ||
| } | ||
| if (!owner) { | ||
| const quote = imports[0]?.quote ?? "'"; | ||
| const semicolon = imports[0]?.semicolon ?? false; | ||
| s.prepend(`import { ${required} } from ${quote}${targetModule}${quote}${semicolon ? ";" : ""}${lineEnding}`); | ||
| modified = true; | ||
| } | ||
| return modified; | ||
| } | ||
| function fixLine(line, { originalLine, useOriginalSemicolon, useOriginalQuotes, fallbackQuote, fallbackSemicolon = true }) { | ||
| let result = line; | ||
| if (useOriginalQuotes && originalLine) result = fixQuotes(result, originalLine, fallbackQuote); | ||
| else if (!useOriginalQuotes && fallbackQuote) result = fixQuotesToPreferred(result, fallbackQuote); | ||
| if (useOriginalSemicolon && originalLine) { | ||
| const hadSemicolon = originalLine.trimEnd().endsWith(";"); | ||
| const hasSemicolon = result.trimEnd().endsWith(";"); | ||
| if (hadSemicolon && !hasSemicolon) result += ";"; | ||
| if (!hadSemicolon && hasSemicolon) result = result.replace(/;\s*$/, ""); | ||
| } else if (!useOriginalSemicolon) { | ||
| const hasSemicolon = result.trimEnd().endsWith(";"); | ||
| if (!fallbackSemicolon && hasSemicolon) result = result.replace(/;\s*$/, ""); | ||
| if (fallbackSemicolon && !hasSemicolon && result.trim()) result += ";"; | ||
| function normalizeNamedImports({ named, required, isOwner }) { | ||
| const banned = getOtherRouteConstructor(required); | ||
| const nextNamed = []; | ||
| const seen = /* @__PURE__ */ new Set(); | ||
| for (const specifier of named) { | ||
| if (specifier.imported === banned) continue; | ||
| if (specifier.local === required && (specifier.imported !== required || !isOwner)) continue; | ||
| const key = `${specifier.importKind ?? "value"}:${specifier.imported}:${specifier.local}`; | ||
| if (seen.has(key)) continue; | ||
| seen.add(key); | ||
| nextNamed.push(specifier); | ||
| } | ||
| return result; | ||
| if (isOwner && !hasNamedImport(nextNamed, required)) nextNamed.push({ | ||
| imported: required, | ||
| local: required | ||
| }); | ||
| return nextNamed; | ||
| } | ||
| function fixQuotes(line, originalLine, fallbackQuote) { | ||
| let originalQuote = detectQuoteFromLine(originalLine); | ||
| if (!originalQuote) originalQuote = fallbackQuote; | ||
| return fixQuotesToPreferred(line, originalQuote); | ||
| function getExpectedRouteConstructor(lazy) { | ||
| return lazy ? "createLazyFileRoute" : "createFileRoute"; | ||
| } | ||
| function fixQuotesToPreferred(line, quote) { | ||
| return line.replace(/(['"`])([^'"`\\]*(?:\\.[^'"`\\]*)*)\1/g, (_, q, content) => { | ||
| return `${quote}${content.replaceAll(quote, `\\${quote}`)}${quote}`; | ||
| }); | ||
| function getOtherRouteConstructor(constructor) { | ||
| return constructor === "createFileRoute" ? "createLazyFileRoute" : "createFileRoute"; | ||
| } | ||
| function detectQuoteFromLine(line) { | ||
| const match = line.match(/(['"`])(?:\\.|[^\\])*?\1/); | ||
| return match ? match[1] : null; | ||
| function hasNamedImport(named, required) { | ||
| return named.some((specifier) => specifier.imported === required && specifier.local === required && specifier.importKind !== "type"); | ||
| } | ||
| function detectSemicolonUsage(code) { | ||
| const lines = code.split("\n").map((l) => l.trim()); | ||
| const total = lines.length; | ||
| return lines.filter((l) => l.endsWith(";")).length > total / 2; | ||
| function sameNamedImports(left, right) { | ||
| return left.length === right.length && left.every((specifier, index) => specifier.imported === right[index].imported && specifier.local === right[index].local && specifier.importKind === right[index].importKind); | ||
| } | ||
| function detectPreferredQuoteStyle(ast) { | ||
| let single = 0; | ||
| let double = 0; | ||
| (0, recast.visit)(ast, { visitStringLiteral(path) { | ||
| if (path.parent.node.type !== "JSXAttribute") { | ||
| const raw = path.node.extra?.raw; | ||
| if (raw?.startsWith("'")) single++; | ||
| else if (raw?.startsWith("\"")) double++; | ||
| } | ||
| return false; | ||
| } }); | ||
| if (single >= double) return "'"; | ||
| return "\""; | ||
| function isRouteConstructorName(value) { | ||
| return routeConstructors.includes(value); | ||
| } | ||
| function renderImportDeclaration(importDeclaration) { | ||
| const parts = []; | ||
| if (importDeclaration.defaultImport) parts.push(importDeclaration.defaultImport); | ||
| if (importDeclaration.namespace) parts.push(`* as ${importDeclaration.namespace}`); | ||
| if (importDeclaration.named.length > 0) parts.push(`{ ${importDeclaration.named.map((specifier) => `${specifier.importKind === "type" ? "type " : ""}${specifier.imported === specifier.local ? specifier.imported : `${specifier.imported} as ${specifier.local}`}`).join(", ")} }`); | ||
| if (parts.length === 0) return null; | ||
| return `import ${parts.join(", ")} from ${importDeclaration.quote}${importDeclaration.moduleName}${importDeclaration.quote}${importDeclaration.semicolon ? ";" : ""}`; | ||
| } | ||
| function getLineEnding(source) { | ||
| if (source.includes("\r\n")) return "\r\n"; | ||
| if (source.includes("\n")) return "\n"; | ||
| if (source.includes("\r")) return "\r"; | ||
| return "\n"; | ||
| } | ||
| function getRemovalEnd(source, end) { | ||
| let pos = end; | ||
| while (pos < source.length && (source[pos] === " " || source[pos] === " ")) pos++; | ||
| if (source[pos] === "\r" && source[pos + 1] === "\n") return pos + 2; | ||
| if (source[pos] === "\n" || source[pos] === "\r") return pos + 1; | ||
| return end; | ||
| } | ||
| //#endregion | ||
@@ -299,0 +316,0 @@ exports.transform = transform; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"transform.cjs","names":[],"sources":["../../../src/transform/transform.ts"],"sourcesContent":["import { parseAst } from '@tanstack/router-utils'\nimport { parse, print, types, visit } from 'recast'\nimport { SourceMapConsumer } from 'source-map'\nimport { mergeImportDeclarations } from '../utils'\nimport { ensureStringArgument } from './utils'\nimport type { ImportDeclaration } from '../types'\nimport type { RawSourceMap } from 'source-map'\nimport type { TransformOptions, TransformResult } from './types'\n\nconst b = types.builders\n\nexport async function transform({\n ctx,\n source,\n node,\n}: TransformOptions): Promise<TransformResult> {\n let appliedChanges = false as boolean\n let ast: types.namedTypes.File\n try {\n ast = parse(source, {\n sourceFileName: 'output.ts',\n parser: {\n parse(code: string) {\n return parseAst({\n code,\n // we need to instruct babel to produce tokens,\n // otherwise recast will try to generate the tokens via its own parser and will fail\n tokens: true,\n })\n },\n },\n })\n } catch (e) {\n console.error('Error parsing code', ctx.routeId, source, e)\n return {\n result: 'error',\n error: e,\n }\n }\n\n const preferredQuote = detectPreferredQuoteStyle(ast)\n\n let routeExportHandled = false as boolean\n function onExportFound(decl: types.namedTypes.VariableDeclarator) {\n if (decl.init?.type === 'CallExpression') {\n const callExpression = decl.init\n const firstArgument = callExpression.arguments[0]\n if (firstArgument) {\n if (firstArgument.type === 'ObjectExpression') {\n const staticProperties = firstArgument.properties.flatMap((p) => {\n if (p.type === 'ObjectProperty' && p.key.type === 'Identifier') {\n return p.key.name\n }\n return []\n })\n node.createFileRouteProps = new Set(staticProperties)\n }\n }\n let identifier: types.namedTypes.Identifier | undefined\n // `const Route = createFileRoute({ ... })`\n if (callExpression.callee.type === 'Identifier') {\n identifier = callExpression.callee\n if (ctx.verboseFileRoutes) {\n // we need to add the string literal via another CallExpression\n callExpression.callee = b.callExpression(identifier, [\n b.stringLiteral(ctx.routeId),\n ])\n appliedChanges = true\n }\n }\n // `const Route = createFileRoute('/path')({ ... })`\n else if (\n callExpression.callee.type === 'CallExpression' &&\n callExpression.callee.callee.type === 'Identifier'\n ) {\n identifier = callExpression.callee.callee\n if (!ctx.verboseFileRoutes) {\n // we need to remove the route id\n callExpression.callee = identifier\n appliedChanges = true\n } else {\n // check if the route id is correct\n appliedChanges = ensureStringArgument(\n callExpression.callee,\n ctx.routeId,\n ctx.preferredQuote,\n )\n }\n }\n if (identifier === undefined) {\n throw new Error(\n `expected identifier to be present in ${ctx.routeId} for export \"Route\"`,\n )\n }\n if (identifier.name === 'createFileRoute' && ctx.lazy) {\n identifier.name = 'createLazyFileRoute'\n appliedChanges = true\n } else if (identifier.name === 'createLazyFileRoute' && !ctx.lazy) {\n identifier.name = 'createFileRoute'\n appliedChanges = true\n }\n } else {\n throw new Error(\n `expected \"Route\" export to be initialized by a CallExpression`,\n )\n }\n routeExportHandled = true\n }\n\n const program: types.namedTypes.Program = ast.program\n // first pass: find Route export\n for (const n of program.body) {\n if (n.type === 'ExportNamedDeclaration') {\n // direct export of a variable declaration, e.g. `export const Route = createFileRoute('/path')`\n if (n.declaration?.type === 'VariableDeclaration') {\n const decl = n.declaration.declarations[0]\n if (\n decl &&\n decl.type === 'VariableDeclarator' &&\n decl.id.type === 'Identifier'\n ) {\n if (decl.id.name === 'Route') {\n onExportFound(decl)\n }\n }\n }\n // this is an export without a declaration, e.g. `export { Route }`\n else if (n.declaration === null && n.specifiers) {\n for (const spec of n.specifiers) {\n if (typeof spec.exported.name === 'string') {\n if (spec.exported.name === 'Route') {\n const variableName = spec.local?.name || spec.exported.name\n // find the matching variable declaration by iterating over the top-level declarations\n for (const decl of program.body) {\n if (\n decl.type === 'VariableDeclaration' &&\n decl.declarations[0]\n ) {\n const variable = decl.declarations[0]\n if (\n variable.type === 'VariableDeclarator' &&\n variable.id.type === 'Identifier' &&\n variable.id.name === variableName\n ) {\n onExportFound(variable)\n break\n }\n }\n }\n }\n }\n }\n }\n }\n if (routeExportHandled) {\n break\n }\n }\n\n if (!routeExportHandled) {\n return {\n result: 'no-route-export',\n }\n }\n\n const imports: {\n required: Array<ImportDeclaration>\n banned: Array<ImportDeclaration>\n } = {\n required: [],\n banned: [],\n }\n\n const targetModule = `@tanstack/${ctx.target}-router`\n if (ctx.verboseFileRoutes === false) {\n imports.banned = [\n {\n source: targetModule,\n specifiers: [\n { imported: 'createLazyFileRoute' },\n { imported: 'createFileRoute' },\n ],\n },\n ]\n } else {\n if (ctx.lazy) {\n imports.required = [\n {\n source: targetModule,\n specifiers: [{ imported: 'createLazyFileRoute' }],\n },\n ]\n imports.banned = [\n {\n source: targetModule,\n specifiers: [{ imported: 'createFileRoute' }],\n },\n ]\n } else {\n imports.required = [\n {\n source: targetModule,\n specifiers: [{ imported: 'createFileRoute' }],\n },\n ]\n imports.banned = [\n {\n source: targetModule,\n specifiers: [{ imported: 'createLazyFileRoute' }],\n },\n ]\n }\n }\n\n imports.required = mergeImportDeclarations(imports.required)\n imports.banned = mergeImportDeclarations(imports.banned)\n\n const importStatementCandidates: Array<types.namedTypes.ImportDeclaration> =\n []\n const importDeclarationsToRemove: Array<types.namedTypes.ImportDeclaration> =\n []\n\n // second pass: apply import rules, but only if a matching export for the plugin was found\n for (const n of program.body) {\n const findImport =\n (opts: { source: string; importKind?: 'type' | 'value' | 'typeof' }) =>\n (i: ImportDeclaration) => {\n if (i.source === opts.source) {\n const importKind = i.importKind || 'value'\n const expectedImportKind = opts.importKind || 'value'\n return expectedImportKind === importKind\n }\n return false\n }\n if (n.type === 'ImportDeclaration' && typeof n.source.value === 'string') {\n const filterImport = findImport({\n source: n.source.value,\n importKind: n.importKind,\n })\n let requiredImports = imports.required.filter(filterImport)[0]\n\n const bannedImports = imports.banned.filter(filterImport)[0]\n if (!requiredImports && !bannedImports) {\n continue\n }\n const importSpecifiersToRemove: types.namedTypes.ImportDeclaration['specifiers'] =\n []\n if (n.specifiers) {\n for (const spec of n.specifiers) {\n if (!requiredImports && !bannedImports) {\n break\n }\n if (\n spec.type === 'ImportSpecifier' &&\n typeof spec.imported.name === 'string'\n ) {\n if (requiredImports) {\n const requiredImportIndex = requiredImports.specifiers.findIndex(\n (imp) => imp.imported === spec.imported.name,\n )\n if (requiredImportIndex !== -1) {\n // import is already present, remove it from requiredImports\n requiredImports.specifiers.splice(requiredImportIndex, 1)\n if (requiredImports.specifiers.length === 0) {\n imports.required = imports.required.splice(\n imports.required.indexOf(requiredImports),\n 1,\n )\n requiredImports = undefined\n }\n } else {\n // add the import statement to the candidates\n importStatementCandidates.push(n)\n }\n }\n if (bannedImports) {\n const bannedImportIndex = bannedImports.specifiers.findIndex(\n (imp) => imp.imported === spec.imported.name,\n )\n if (bannedImportIndex !== -1) {\n importSpecifiersToRemove.push(spec)\n }\n }\n }\n }\n if (importSpecifiersToRemove.length > 0) {\n appliedChanges = true\n n.specifiers = n.specifiers.filter(\n (spec) => !importSpecifiersToRemove.includes(spec),\n )\n\n // mark the import statement as to be deleted if it is now empty\n if (n.specifiers.length === 0) {\n importDeclarationsToRemove.push(n)\n }\n }\n }\n }\n }\n imports.required.forEach((requiredImport) => {\n if (requiredImport.specifiers.length > 0) {\n appliedChanges = true\n if (importStatementCandidates.length > 0) {\n // find the first import statement that matches both the module and the import kind\n const importStatement = importStatementCandidates.find(\n (importStatement) => {\n if (importStatement.source.value === requiredImport.source) {\n const importKind = importStatement.importKind || 'value'\n const requiredImportKind = requiredImport.importKind || 'value'\n return importKind === requiredImportKind\n }\n return false\n },\n )\n if (importStatement) {\n if (importStatement.specifiers === undefined) {\n importStatement.specifiers = []\n }\n const importSpecifiersToAdd = requiredImport.specifiers.map((spec) =>\n b.importSpecifier(\n b.identifier(spec.imported),\n b.identifier(spec.imported),\n ),\n )\n importStatement.specifiers = [\n ...importStatement.specifiers,\n ...importSpecifiersToAdd,\n ]\n return\n }\n }\n const importStatement = b.importDeclaration(\n requiredImport.specifiers.map((spec) =>\n b.importSpecifier(\n b.identifier(spec.imported),\n spec.local ? b.identifier(spec.local) : null,\n ),\n ),\n b.stringLiteral(requiredImport.source),\n )\n program.body.unshift(importStatement)\n }\n })\n if (importDeclarationsToRemove.length > 0) {\n appliedChanges = true\n for (const importDeclaration of importDeclarationsToRemove) {\n // check if the import declaration is still empty\n if (importDeclaration.specifiers?.length === 0) {\n const index = program.body.indexOf(importDeclaration)\n if (index !== -1) {\n program.body.splice(index, 1)\n }\n }\n }\n }\n\n if (!appliedChanges) {\n return {\n result: 'not-modified',\n }\n }\n\n const printResult = print(ast, {\n reuseWhitespace: true,\n sourceMapName: 'output.map',\n })\n let transformedCode = printResult.code\n if (printResult.map) {\n const fixedOutput = await fixTransformedOutputText({\n originalCode: source,\n transformedCode,\n sourceMap: printResult.map as RawSourceMap,\n preferredQuote,\n })\n transformedCode = fixedOutput\n }\n return {\n result: 'modified',\n output: transformedCode,\n }\n}\n\nasync function fixTransformedOutputText({\n originalCode,\n transformedCode,\n sourceMap,\n preferredQuote,\n}: {\n originalCode: string\n transformedCode: string\n sourceMap: RawSourceMap\n preferredQuote: '\"' | \"'\"\n}) {\n const originalLines = originalCode.split('\\n')\n const transformedLines = transformedCode.split('\\n')\n\n const defaultUsesSemicolons = detectSemicolonUsage(originalCode)\n\n const consumer = await new SourceMapConsumer(sourceMap)\n\n const fixedLines = transformedLines.map((line, i) => {\n const transformedLineNum = i + 1\n\n let origLineText: string | undefined = undefined\n\n for (let col = 0; col < line.length; col++) {\n const mapped = consumer.originalPositionFor({\n line: transformedLineNum,\n column: col,\n })\n if (mapped.line != null && mapped.line > 0) {\n origLineText = originalLines[mapped.line - 1]\n break\n }\n }\n\n if (origLineText !== undefined) {\n if (origLineText === line) {\n return origLineText\n }\n return fixLine(line, {\n originalLine: origLineText,\n useOriginalSemicolon: true,\n useOriginalQuotes: true,\n fallbackQuote: preferredQuote,\n })\n } else {\n return fixLine(line, {\n originalLine: null,\n useOriginalSemicolon: false,\n useOriginalQuotes: false,\n fallbackQuote: preferredQuote,\n fallbackSemicolon: defaultUsesSemicolons,\n })\n }\n })\n\n return fixedLines.join('\\n')\n}\n\nfunction fixLine(\n line: string,\n {\n originalLine,\n useOriginalSemicolon,\n useOriginalQuotes,\n fallbackQuote,\n fallbackSemicolon = true,\n }: {\n originalLine: string | null\n useOriginalSemicolon: boolean\n useOriginalQuotes: boolean\n fallbackQuote: string\n fallbackSemicolon?: boolean\n },\n) {\n let result = line\n\n if (useOriginalQuotes && originalLine) {\n result = fixQuotes(result, originalLine, fallbackQuote)\n } else if (!useOriginalQuotes && fallbackQuote) {\n result = fixQuotesToPreferred(result, fallbackQuote)\n }\n\n if (useOriginalSemicolon && originalLine) {\n const hadSemicolon = originalLine.trimEnd().endsWith(';')\n const hasSemicolon = result.trimEnd().endsWith(';')\n if (hadSemicolon && !hasSemicolon) result += ';'\n if (!hadSemicolon && hasSemicolon) result = result.replace(/;\\s*$/, '')\n } else if (!useOriginalSemicolon) {\n const hasSemicolon = result.trimEnd().endsWith(';')\n if (!fallbackSemicolon && hasSemicolon) result = result.replace(/;\\s*$/, '')\n if (fallbackSemicolon && !hasSemicolon && result.trim()) result += ';'\n }\n\n return result\n}\n\nfunction fixQuotes(line: string, originalLine: string, fallbackQuote: string) {\n let originalQuote = detectQuoteFromLine(originalLine)\n if (!originalQuote) {\n originalQuote = fallbackQuote\n }\n return fixQuotesToPreferred(line, originalQuote)\n}\n\nfunction fixQuotesToPreferred(line: string, quote: string) {\n // Replace existing quotes with preferred quote\n return line.replace(\n /(['\"`])([^'\"`\\\\]*(?:\\\\.[^'\"`\\\\]*)*)\\1/g,\n (_, q, content) => {\n const escaped = content.replaceAll(quote, `\\\\${quote}`)\n return `${quote}${escaped}${quote}`\n },\n )\n}\n\nfunction detectQuoteFromLine(line: string) {\n const match = line.match(/(['\"`])(?:\\\\.|[^\\\\])*?\\1/)\n return match ? match[1] : null\n}\n\nfunction detectSemicolonUsage(code: string) {\n const lines = code.split('\\n').map((l) => l.trim())\n const total = lines.length\n const withSemis = lines.filter((l) => l.endsWith(';')).length\n return withSemis > total / 2\n}\n\nexport function detectPreferredQuoteStyle(ast: types.ASTNode): \"'\" | '\"' {\n let single = 0\n let double = 0\n\n visit(ast, {\n visitStringLiteral(path) {\n if (path.parent.node.type !== 'JSXAttribute') {\n const raw = path.node.extra?.raw\n if (raw?.startsWith(\"'\")) single++\n else if (raw?.startsWith('\"')) double++\n }\n return false\n },\n })\n\n if (single >= double) {\n return \"'\"\n }\n return '\"'\n}\n"],"mappings":"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"} | ||
| {"version":3,"file":"transform.cjs","names":[],"sources":["../../../src/transform/transform.ts"],"sourcesContent":["import MagicString from 'magic-string'\nimport * as t from '@babel/types'\nimport { parseAst } from '@tanstack/router-utils'\nimport type { TransformOptions, TransformResult } from './types'\n\nconst routeConstructors = ['createFileRoute', 'createLazyFileRoute'] as const\n\ntype RouteConstructorName = (typeof routeConstructors)[number]\ntype SupportedRouteId = t.StringLiteral | t.TemplateLiteral\ntype NamedImport = {\n imported: string\n local: string\n importKind?: 'type' | 'typeof' | 'value'\n}\n\ntype ParsedImportDeclaration = {\n declaration: t.ImportDeclaration\n defaultImport?: string\n namespace?: string\n named: Array<NamedImport>\n moduleName: string\n quote: '\"' | \"'\"\n semicolon: boolean\n}\n\ntype RouteImportAnalysis =\n | { kind: 'ok' }\n | {\n kind: 'rename'\n imported: t.Identifier\n local: t.Identifier\n next: RouteConstructorName\n }\n | { kind: 'normalize' }\n\ntype RouteCall = {\n callee: t.Identifier & { name: RouteConstructorName }\n routeIdArg: SupportedRouteId\n optionsArg: t.CallExpression['arguments'][number] | undefined\n}\n\ntype RouteCallAnalysis = {\n calls: Array<RouteCall>\n hasUnsupportedRouteId: boolean\n hasMalformedRouteCall: boolean\n}\n\nexport function transform({\n ctx,\n source,\n node,\n}: TransformOptions): TransformResult {\n let ast: ReturnType<typeof parseAst>\n\n try {\n ast = parseAst({ code: source })\n } catch (error) {\n return {\n result: 'error',\n error,\n }\n }\n\n const exportedRouteNames = getExportedRouteNames(ast.program.body)\n\n if (exportedRouteNames.size === 0) {\n return { result: 'no-route-export' }\n }\n\n const {\n calls: routeCalls,\n hasUnsupportedRouteId,\n hasMalformedRouteCall,\n } = findExportedRouteCalls(ast.program.body, exportedRouteNames)\n\n if (routeCalls.length === 0 && hasMalformedRouteCall) {\n return {\n result: 'error',\n error: new Error(\n `expected Route export in ${ctx.routeId} to use createFileRoute('/path')({...}) or createLazyFileRoute('/path')({...})`,\n ),\n }\n }\n\n if (routeCalls.length === 0 && hasUnsupportedRouteId) {\n return {\n result: 'error',\n error: new Error(\n `expected route id to be a string literal or plain template literal in ${ctx.routeId}`,\n ),\n }\n }\n\n if (routeCalls.length === 0) {\n return { result: 'not-modified' }\n }\n\n if (routeCalls.length > 1) {\n return {\n result: 'error',\n error: new Error(\n `expected exactly one createFileRoute/createLazyFileRoute call in ${ctx.routeId}`,\n ),\n }\n }\n\n const routeCall = routeCalls[0]!\n const routeIdQuote = getRouteIdQuote(source, routeCall.routeIdArg)\n\n const createFileRouteProps = getCreateFileRouteProps(routeCall.optionsArg)\n if (createFileRouteProps) {\n node.createFileRouteProps = createFileRouteProps\n }\n\n const expectedCallee = getExpectedRouteConstructor(ctx.lazy)\n const expectedRouteId = `${routeIdQuote}${ctx.routeId}${routeIdQuote}`\n const currentRouteId = source.slice(\n routeCall.routeIdArg.start!,\n routeCall.routeIdArg.end!,\n )\n const targetModule = `@tanstack/${ctx.target}-router`\n const imports = parseTargetImports(ast.program.body, source, targetModule)\n\n const s = new MagicString(source)\n let modified = false\n\n if (routeCall.callee.name !== expectedCallee) {\n s.update(routeCall.callee.start!, routeCall.callee.end!, expectedCallee)\n modified = true\n }\n\n if (currentRouteId !== expectedRouteId) {\n s.update(\n routeCall.routeIdArg.start!,\n routeCall.routeIdArg.end!,\n expectedRouteId,\n )\n modified = true\n }\n\n if (\n updateRouteImports({\n imports,\n source,\n s,\n targetModule,\n required: expectedCallee,\n lineEnding: getLineEnding(source),\n })\n ) {\n modified = true\n }\n\n if (!modified) {\n return { result: 'not-modified' }\n }\n\n return {\n result: 'modified',\n output: s.toString(),\n }\n}\n\nfunction getExportedRouteNames(body: Array<t.Statement>) {\n const exportedRouteNames = new Set<string>()\n\n for (const statement of body) {\n if (!t.isExportNamedDeclaration(statement) || statement.source) {\n continue\n }\n\n if (t.isVariableDeclaration(statement.declaration)) {\n for (const declarator of statement.declaration.declarations) {\n if (t.isIdentifier(declarator.id) && declarator.id.name === 'Route') {\n exportedRouteNames.add('Route')\n }\n }\n }\n\n for (const specifier of statement.specifiers) {\n if (\n !t.isExportSpecifier(specifier) ||\n getExportedName(specifier.exported) !== 'Route'\n ) {\n continue\n }\n\n const localName = getLocalBindingName(specifier.local)\n if (localName) {\n exportedRouteNames.add(localName)\n }\n }\n }\n\n return exportedRouteNames\n}\n\nfunction findExportedRouteCalls(\n body: Array<t.Statement>,\n exportedRouteNames: Set<string>,\n): RouteCallAnalysis {\n const calls: Array<RouteCall> = []\n let hasUnsupportedRouteId = false\n let hasMalformedRouteCall = false\n\n for (const statement of body) {\n const declaration = getVariableDeclaration(statement)\n if (!declaration) {\n continue\n }\n\n for (const declarator of declaration.declarations) {\n if (\n !t.isIdentifier(declarator.id) ||\n !exportedRouteNames.has(declarator.id.name)\n ) {\n continue\n }\n\n const init = getRouteConstructorInit(declarator.init)\n if (!init) {\n if (isDirectRouteConstructorCall(declarator.init)) {\n hasMalformedRouteCall = true\n }\n continue\n }\n\n const routeIdArg = init.innerCall.arguments[0]\n if (isSupportedRouteId(routeIdArg)) {\n calls.push({\n callee: init.callee,\n routeIdArg,\n optionsArg: init.outerCall.arguments[0],\n })\n } else {\n hasUnsupportedRouteId = true\n }\n }\n }\n\n return { calls, hasUnsupportedRouteId, hasMalformedRouteCall }\n}\n\nfunction getVariableDeclaration(statement: t.Statement) {\n const declaration = t.isExportNamedDeclaration(statement)\n ? statement.declaration\n : statement\n\n return t.isVariableDeclaration(declaration) ? declaration : null\n}\n\nfunction getExportedName(node: t.Identifier | t.StringLiteral) {\n return t.isIdentifier(node) ? node.name : node.value\n}\n\nfunction getLocalBindingName(node: t.Identifier | t.StringLiteral) {\n return t.isIdentifier(node) ? node.name : null\n}\n\nfunction getRouteConstructorInit(expression: t.Expression | null | undefined) {\n if (!expression || !t.isCallExpression(expression)) {\n return null\n }\n\n if (!t.isCallExpression(expression.callee)) {\n return null\n }\n\n const innerCall = expression.callee\n\n if (\n !t.isIdentifier(innerCall.callee) ||\n !isRouteConstructor(innerCall.callee)\n ) {\n return null\n }\n\n return {\n callee: innerCall.callee,\n outerCall: expression,\n innerCall,\n }\n}\n\nfunction isDirectRouteConstructorCall(\n expression: t.Expression | null | undefined,\n) {\n return (\n !!expression &&\n t.isCallExpression(expression) &&\n t.isIdentifier(expression.callee) &&\n isRouteConstructor(expression.callee)\n )\n}\n\nfunction isRouteConstructor(callee: t.Identifier): callee is t.Identifier & {\n name: RouteConstructorName\n} {\n return routeConstructors.includes(callee.name as RouteConstructorName)\n}\n\nfunction isSupportedRouteId(\n arg: t.CallExpression['arguments'][number] | undefined,\n): arg is SupportedRouteId {\n return (\n !!arg &&\n (t.isStringLiteral(arg) ||\n (t.isTemplateLiteral(arg) && arg.expressions.length === 0))\n )\n}\n\nfunction getRouteIdQuote(\n source: string,\n arg: SupportedRouteId,\n): '\"' | \"'\" | '`' {\n const raw = source.slice(arg.start!, arg.end!)\n\n if (raw.startsWith(\"'\")) return \"'\"\n if (raw.startsWith('\"')) return '\"'\n return '`'\n}\n\nfunction getCreateFileRouteProps(\n arg: t.CallExpression['arguments'][number] | undefined,\n) {\n if (!arg || !t.isObjectExpression(arg)) {\n return undefined\n }\n\n const props = new Set<string>()\n\n for (const property of arg.properties) {\n if (!t.isObjectProperty(property) || property.computed) {\n continue\n }\n\n if (t.isIdentifier(property.key)) {\n props.add(property.key.name)\n continue\n }\n\n if (t.isStringLiteral(property.key)) {\n props.add(property.key.value)\n }\n }\n\n return props\n}\n\nfunction parseTargetImports(\n body: Array<t.Statement>,\n source: string,\n targetModule: string,\n) {\n const imports: Array<ParsedImportDeclaration> = []\n\n for (const statement of body) {\n if (\n !t.isImportDeclaration(statement) ||\n statement.importKind === 'type' ||\n statement.source.value !== targetModule\n ) {\n continue\n }\n\n const rawSource = source.slice(\n statement.source.start!,\n statement.source.end!,\n )\n const importStatement = source.slice(statement.start!, statement.end!)\n\n imports.push({\n declaration: statement,\n defaultImport: statement.specifiers.find((specifier) =>\n t.isImportDefaultSpecifier(specifier),\n )?.local.name,\n namespace: statement.specifiers.find((specifier) =>\n t.isImportNamespaceSpecifier(specifier),\n )?.local.name,\n named: statement.specifiers\n .filter((specifier): specifier is t.ImportSpecifier =>\n t.isImportSpecifier(specifier),\n )\n .map((specifier) => ({\n imported: t.isIdentifier(specifier.imported)\n ? specifier.imported.name\n : specifier.imported.value,\n local: specifier.local.name,\n importKind: specifier.importKind ?? undefined,\n })),\n moduleName: statement.source.value,\n quote: rawSource[0] as '\"' | \"'\",\n semicolon: importStatement.trimEnd().endsWith(';'),\n })\n }\n\n return imports\n}\n\nfunction updateRouteImports({\n imports,\n source,\n s,\n targetModule,\n required,\n lineEnding,\n}: {\n imports: Array<ParsedImportDeclaration>\n source: string\n s: MagicString\n targetModule: string\n required: RouteConstructorName\n lineEnding: '\\r\\n' | '\\n' | '\\r'\n}) {\n const analysis = analyzeRouteImports(imports, required)\n\n if (analysis.kind === 'ok') {\n return false\n }\n\n if (analysis.kind === 'rename') {\n s.update(analysis.imported.start!, analysis.imported.end!, analysis.next)\n s.update(analysis.local.start!, analysis.local.end!, analysis.next)\n return true\n }\n\n return normalizeRouteImports({\n imports,\n source,\n s,\n targetModule,\n required,\n lineEnding,\n })\n}\n\nfunction analyzeRouteImports(\n imports: Array<ParsedImportDeclaration>,\n required: RouteConstructorName,\n): RouteImportAnalysis {\n const opposite = getOtherRouteConstructor(required)\n let requiredCount = 0\n let oppositeCount = 0\n let renameCandidate:\n | {\n imported: t.Identifier\n local: t.Identifier\n next: RouteConstructorName\n }\n | undefined\n\n for (const declaration of imports) {\n for (const specifier of declaration.declaration.specifiers) {\n if (!t.isImportSpecifier(specifier)) {\n continue\n }\n\n const imported = specifier.imported\n if (!t.isIdentifier(imported)) {\n return { kind: 'normalize' }\n }\n\n if (!isRouteConstructorName(imported.name)) {\n continue\n }\n\n if (specifier.local.name !== imported.name) {\n return { kind: 'normalize' }\n }\n\n if (imported.name === required) {\n requiredCount++\n continue\n }\n\n if (imported.name === opposite) {\n oppositeCount++\n renameCandidate = {\n imported,\n local: specifier.local,\n next: required,\n }\n }\n }\n }\n\n if (requiredCount === 1 && oppositeCount === 0) {\n return { kind: 'ok' }\n }\n\n if (requiredCount === 0 && oppositeCount === 1 && renameCandidate) {\n return {\n kind: 'rename',\n ...renameCandidate,\n }\n }\n\n return { kind: 'normalize' }\n}\n\nfunction normalizeRouteImports({\n imports,\n source,\n s,\n targetModule,\n required,\n lineEnding,\n}: {\n imports: Array<ParsedImportDeclaration>\n source: string\n s: MagicString\n targetModule: string\n required: RouteConstructorName\n lineEnding: '\\r\\n' | '\\n' | '\\r'\n}) {\n const owner =\n imports.find((declaration) =>\n hasNamedImport(declaration.named, required),\n ) ?? imports.find((declaration) => !declaration.namespace)\n\n let modified = false\n\n for (const declaration of imports) {\n const named = normalizeNamedImports({\n named: declaration.named,\n required,\n isOwner: declaration === owner,\n })\n\n if (sameNamedImports(declaration.named, named)) {\n continue\n }\n\n const replacement = renderImportDeclaration({\n ...declaration,\n named,\n })\n\n if (replacement === null) {\n s.remove(\n declaration.declaration.start!,\n getRemovalEnd(source, declaration.declaration.end!),\n )\n modified = true\n continue\n }\n\n s.update(\n declaration.declaration.start!,\n declaration.declaration.end!,\n replacement,\n )\n modified = true\n }\n\n if (!owner) {\n const quote = imports[0]?.quote ?? \"'\"\n const semicolon = imports[0]?.semicolon ?? false\n s.prepend(\n `import { ${required} } from ${quote}${targetModule}${quote}${semicolon ? ';' : ''}${lineEnding}`,\n )\n modified = true\n }\n\n return modified\n}\n\nfunction normalizeNamedImports({\n named,\n required,\n isOwner,\n}: {\n named: Array<NamedImport>\n required: RouteConstructorName\n isOwner: boolean\n}) {\n const banned = getOtherRouteConstructor(required)\n const nextNamed: Array<NamedImport> = []\n const seen = new Set<string>()\n\n for (const specifier of named) {\n if (specifier.imported === banned) {\n continue\n }\n\n if (\n specifier.local === required &&\n (specifier.imported !== required || !isOwner)\n ) {\n continue\n }\n\n const key = `${specifier.importKind ?? 'value'}:${specifier.imported}:${specifier.local}`\n if (seen.has(key)) {\n continue\n }\n\n seen.add(key)\n nextNamed.push(specifier)\n }\n\n if (isOwner && !hasNamedImport(nextNamed, required)) {\n nextNamed.push({ imported: required, local: required })\n }\n\n return nextNamed\n}\n\nfunction getExpectedRouteConstructor(lazy: boolean): RouteConstructorName {\n return lazy ? 'createLazyFileRoute' : 'createFileRoute'\n}\n\nfunction getOtherRouteConstructor(\n constructor: RouteConstructorName,\n): RouteConstructorName {\n return constructor === 'createFileRoute'\n ? 'createLazyFileRoute'\n : 'createFileRoute'\n}\n\nfunction hasNamedImport(\n named: Array<NamedImport>,\n required: RouteConstructorName,\n) {\n return named.some(\n (specifier) =>\n specifier.imported === required &&\n specifier.local === required &&\n specifier.importKind !== 'type',\n )\n}\n\nfunction sameNamedImports(left: Array<NamedImport>, right: Array<NamedImport>) {\n return (\n left.length === right.length &&\n left.every(\n (specifier, index) =>\n specifier.imported === right[index]!.imported &&\n specifier.local === right[index]!.local &&\n specifier.importKind === right[index]!.importKind,\n )\n )\n}\n\nfunction isRouteConstructorName(value: string): value is RouteConstructorName {\n return routeConstructors.includes(value as RouteConstructorName)\n}\n\nfunction renderImportDeclaration(importDeclaration: {\n defaultImport?: string\n namespace?: string\n named: Array<NamedImport>\n moduleName: string\n quote: '\"' | \"'\"\n semicolon: boolean\n}) {\n const parts: Array<string> = []\n\n if (importDeclaration.defaultImport) {\n parts.push(importDeclaration.defaultImport)\n }\n\n if (importDeclaration.namespace) {\n parts.push(`* as ${importDeclaration.namespace}`)\n }\n\n if (importDeclaration.named.length > 0) {\n parts.push(\n `{ ${importDeclaration.named\n .map(\n (specifier) =>\n `${specifier.importKind === 'type' ? 'type ' : ''}${specifier.imported === specifier.local ? specifier.imported : `${specifier.imported} as ${specifier.local}`}`,\n )\n .join(', ')} }`,\n )\n }\n\n if (parts.length === 0) {\n return null\n }\n\n return `import ${parts.join(', ')} from ${importDeclaration.quote}${importDeclaration.moduleName}${importDeclaration.quote}${importDeclaration.semicolon ? ';' : ''}`\n}\n\nfunction getLineEnding(source: string): '\\r\\n' | '\\n' | '\\r' {\n if (source.includes('\\r\\n')) {\n return '\\r\\n'\n }\n\n if (source.includes('\\n')) {\n return '\\n'\n }\n\n if (source.includes('\\r')) {\n return '\\r'\n }\n\n return '\\n'\n}\n\nfunction getRemovalEnd(source: string, end: number) {\n let pos = end\n while (pos < source.length && (source[pos] === ' ' || source[pos] === '\\t')) {\n pos++\n }\n\n if (source[pos] === '\\r' && source[pos + 1] === '\\n') {\n return pos + 2\n }\n\n if (source[pos] === '\\n' || source[pos] === '\\r') {\n return pos + 1\n }\n\n return end\n}\n"],"mappings":"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"} |
@@ -1,4 +0,2 @@ | ||
| import { types } from 'recast'; | ||
| import { TransformOptions, TransformResult } from './types.cjs'; | ||
| export declare function transform({ ctx, source, node, }: TransformOptions): Promise<TransformResult>; | ||
| export declare function detectPreferredQuoteStyle(ast: types.ASTNode): "'" | '"'; | ||
| export declare function transform({ ctx, source, node, }: TransformOptions): TransformResult; |
@@ -1,2 +0,2 @@ | ||
| import { ImportDeclaration, RouteNode } from '../types.cjs'; | ||
| import { RouteNode } from '../types.cjs'; | ||
| import { Config } from '../config.cjs'; | ||
@@ -19,6 +19,2 @@ export interface TransformOptions { | ||
| }; | ||
| export interface TransformImportsConfig { | ||
| banned?: Array<ImportDeclaration>; | ||
| required?: Array<ImportDeclaration>; | ||
| } | ||
| export interface TransformContext { | ||
@@ -28,4 +24,2 @@ target: Config['target']; | ||
| lazy: boolean; | ||
| verboseFileRoutes: boolean; | ||
| preferredQuote?: '"' | "'"; | ||
| } |
+0
-16
@@ -18,3 +18,2 @@ const require_runtime = require("./_virtual/_rolldown/runtime.cjs"); | ||
| this.prefixToRoute = /* @__PURE__ */ new Map(); | ||
| this.layoutRoutes = []; | ||
| for (const route of routes) { | ||
@@ -24,5 +23,3 @@ if (!route.routePath || route.routePath === `/__root`) continue; | ||
| this.prefixToRoute.set(route.routePath, route); | ||
| if (route._fsRouteType === "pathless_layout" || route._fsRouteType === "layout" || route._fsRouteType === "__root") this.layoutRoutes.push(route); | ||
| } | ||
| this.layoutRoutes.sort((a, b) => (b.routePath?.length ?? 0) - (a.routePath?.length ?? 0)); | ||
| } | ||
@@ -409,13 +406,2 @@ /** | ||
| /** | ||
| * Checks if a given RouteNode is valid for augmenting it with typing based on conditions. | ||
| * Also asserts that the RouteNode is defined. | ||
| * | ||
| * @param routeNode - The RouteNode to check. | ||
| * @returns A boolean indicating whether the RouteNode is defined. | ||
| */ | ||
| function isRouteNodeValidForAugmentation(routeNode) { | ||
| if (!routeNode || routeNode.isVirtual) return false; | ||
| return true; | ||
| } | ||
| /** | ||
| * Infers the path for use by TS | ||
@@ -632,3 +618,2 @@ */ | ||
| exports.getImportForRouteNode = getImportForRouteNode; | ||
| exports.getImportPath = getImportPath; | ||
| exports.getResolvedRouteNodeVariableName = getResolvedRouteNodeVariableName; | ||
@@ -638,3 +623,2 @@ exports.hasEscapedLeadingUnderscore = hasEscapedLeadingUnderscore; | ||
| exports.inferFullPath = inferFullPath; | ||
| exports.isRouteNodeValidForAugmentation = isRouteNodeValidForAugmentation; | ||
| exports.isSegmentPathless = isSegmentPathless; | ||
@@ -641,0 +625,0 @@ exports.mergeImportDeclarations = mergeImportDeclarations; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"utils.cjs","names":[],"sources":["../../src/utils.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/prefer-for-of */\nimport * as fsp from 'node:fs/promises'\nimport path from 'node:path'\nimport * as prettier from 'prettier'\nimport { rootPathId } from './filesystem/physical/rootPathId'\nimport type { Config, TokenMatcher } from './config'\nimport type { ImportDeclaration, RouteNode } from './types'\n\n/**\n * Prefix map for O(1) parent route lookups.\n * Maps each route path prefix to the route node that owns that prefix.\n * Enables finding longest matching parent without linear search.\n */\nexport class RoutePrefixMap {\n private prefixToRoute: Map<string, RouteNode> = new Map()\n private layoutRoutes: Array<RouteNode> = []\n\n constructor(routes: Array<RouteNode>) {\n for (const route of routes) {\n if (!route.routePath || route.routePath === `/${rootPathId}`) continue\n\n // Skip route pieces (lazy, loader, component, etc.) - they are merged with main routes\n // and should not be valid parent candidates\n if (\n route._fsRouteType === 'lazy' ||\n route._fsRouteType === 'loader' ||\n route._fsRouteType === 'component' ||\n route._fsRouteType === 'pendingComponent' ||\n route._fsRouteType === 'errorComponent' ||\n route._fsRouteType === 'notFoundComponent'\n ) {\n continue\n }\n\n // Index by exact path for direct lookups\n this.prefixToRoute.set(route.routePath, route)\n\n if (\n route._fsRouteType === 'pathless_layout' ||\n route._fsRouteType === 'layout' ||\n route._fsRouteType === '__root'\n ) {\n this.layoutRoutes.push(route)\n }\n }\n\n // Sort by path length descending for longest-match-first\n this.layoutRoutes.sort(\n (a, b) => (b.routePath?.length ?? 0) - (a.routePath?.length ?? 0),\n )\n }\n\n /**\n * Find the longest matching parent route for a given path.\n * O(k) where k is the number of path segments, not O(n) routes.\n */\n findParent(routePath: string): RouteNode | null {\n if (!routePath || routePath === '/') return null\n\n // Walk up the path segments\n let searchPath = routePath\n while (searchPath.length > 0) {\n const lastSlash = searchPath.lastIndexOf('/')\n if (lastSlash <= 0) break\n\n searchPath = searchPath.substring(0, lastSlash)\n const parent = this.prefixToRoute.get(searchPath)\n if (parent && parent.routePath !== routePath) {\n return parent\n }\n }\n return null\n }\n\n /**\n * Check if a route exists at the given path.\n */\n has(routePath: string): boolean {\n return this.prefixToRoute.has(routePath)\n }\n\n /**\n * Get a route by exact path.\n */\n get(routePath: string): RouteNode | undefined {\n return this.prefixToRoute.get(routePath)\n }\n}\n\nexport function multiSortBy<T>(\n arr: Array<T>,\n accessors: Array<(item: T) => any> = [(d) => d],\n): Array<T> {\n const len = arr.length\n // Pre-compute all accessor values to avoid repeated function calls during sort\n const indexed: Array<{ item: T; index: number; keys: Array<any> }> =\n new Array(len)\n for (let i = 0; i < len; i++) {\n const item = arr[i]!\n const keys = new Array(accessors.length)\n for (let j = 0; j < accessors.length; j++) {\n keys[j] = accessors[j]!(item)\n }\n indexed[i] = { item, index: i, keys }\n }\n\n indexed.sort((a, b) => {\n for (let j = 0; j < accessors.length; j++) {\n const ao = a.keys[j]\n const bo = b.keys[j]\n\n if (typeof ao === 'undefined') {\n if (typeof bo === 'undefined') {\n continue\n }\n return 1\n }\n\n if (ao === bo) {\n continue\n }\n\n return ao > bo ? 1 : -1\n }\n\n return a.index - b.index\n })\n\n const result: Array<T> = new Array(len)\n for (let i = 0; i < len; i++) {\n result[i] = indexed[i]!.item\n }\n return result\n}\n\nexport function cleanPath(path: string) {\n // remove double slashes\n return path.replace(/\\/{2,}/g, '/')\n}\n\nexport function trimPathLeft(path: string) {\n return path === '/' ? path : path.replace(/^\\/{1,}/, '')\n}\n\nexport function removeLeadingSlash(path: string): string {\n return path.replace(/^\\//, '')\n}\n\nexport function removeTrailingSlash(s: string) {\n return s.replace(/\\/$/, '')\n}\n\nconst BRACKET_CONTENT_RE = /\\[(.*?)\\]/g\nconst SPLIT_REGEX = /(?<!\\[)\\.(?!\\])/g\n\n/**\n * Characters that cannot be escaped in square brackets.\n * These are characters that would cause issues in URLs or file systems.\n */\nconst DISALLOWED_ESCAPE_CHARS = new Set([\n '/',\n '\\\\',\n '?',\n '#',\n ':',\n '*',\n '<',\n '>',\n '|',\n '!',\n '$',\n '%',\n])\n\nexport function determineInitialRoutePath(routePath: string) {\n const originalRoutePath =\n cleanPath(\n `/${(cleanPath(routePath) || '').split(SPLIT_REGEX).join('/')}`,\n ) || ''\n\n const parts = routePath.split(SPLIT_REGEX)\n\n // Escape any characters that in square brackets\n // we keep the original path untouched\n const escapedParts = parts.map((part) => {\n // Check if any disallowed characters are used in brackets\n\n let match\n while ((match = BRACKET_CONTENT_RE.exec(part)) !== null) {\n const character = match[1]\n if (character === undefined) continue\n if (DISALLOWED_ESCAPE_CHARS.has(character)) {\n console.error(\n `Error: Disallowed character \"${character}\" found in square brackets in route path \"${routePath}\".\\nYou cannot use any of the following characters in square brackets: ${Array.from(\n DISALLOWED_ESCAPE_CHARS,\n ).join(', ')}\\nPlease remove and/or replace them.`,\n )\n process.exit(1)\n }\n }\n\n // Since this split segment is safe at this point, we can\n // remove the brackets and replace them with the content inside\n return part.replace(BRACKET_CONTENT_RE, '$1')\n })\n\n // If the syntax for prefix/suffix is different, from the path\n // matching internals of router-core, we'd perform those changes here\n // on the `escapedParts` array before it is joined back together in\n // `final`\n\n const final = cleanPath(`/${escapedParts.join('/')}`) || ''\n\n return {\n routePath: final,\n originalRoutePath,\n }\n}\n\n/**\n * Checks if a segment is fully escaped (entirely wrapped in brackets with no nested brackets).\n * E.g., \"[index]\" -> true, \"[_layout]\" -> true, \"foo[.]bar\" -> false, \"index\" -> false\n */\nfunction isFullyEscapedSegment(originalSegment: string): boolean {\n return (\n originalSegment.startsWith('[') &&\n originalSegment.endsWith(']') &&\n !originalSegment.slice(1, -1).includes('[') &&\n !originalSegment.slice(1, -1).includes(']')\n )\n}\n\n/**\n * Checks if the leading underscore in a segment is escaped.\n * Returns true if:\n * - Segment starts with [_] pattern: \"[_]layout\" -> \"_layout\"\n * - Segment is fully escaped and content starts with _: \"[_1nd3x]\" -> \"_1nd3x\"\n */\nexport function hasEscapedLeadingUnderscore(originalSegment: string): boolean {\n // Pattern: [_]something or [_something]\n return (\n originalSegment.startsWith('[_]') ||\n (originalSegment.startsWith('[_') && isFullyEscapedSegment(originalSegment))\n )\n}\n\n/**\n * Checks if the trailing underscore in a segment is escaped.\n * Returns true if:\n * - Segment ends with [_] pattern: \"blog[_]\" -> \"blog_\"\n * - Segment is fully escaped and content ends with _: \"[_r0ut3_]\" -> \"_r0ut3_\"\n */\nexport function hasEscapedTrailingUnderscore(originalSegment: string): boolean {\n // Pattern: something[_] or [something_]\n return (\n originalSegment.endsWith('[_]') ||\n (originalSegment.endsWith('_]') && isFullyEscapedSegment(originalSegment))\n )\n}\n\nconst backslashRegex = /\\\\/g\n\nexport function replaceBackslash(s: string) {\n return s.replace(backslashRegex, '/')\n}\n\nconst alphanumericRegex = /[a-zA-Z0-9_]/\nconst splatSlashRegex = /\\/\\$\\//g\nconst trailingSplatRegex = /\\$$/g\nconst bracketSplatRegex = /\\$\\{\\$\\}/g\nconst dollarSignRegex = /\\$/g\nconst splitPathRegex = /[/-]/g\nconst leadingDigitRegex = /^(\\d)/g\n\nconst toVariableSafeChar = (char: string): string => {\n if (alphanumericRegex.test(char)) {\n return char // Keep alphanumeric characters and underscores as is\n }\n\n // Replace special characters with meaningful text equivalents\n switch (char) {\n case '.':\n return 'Dot'\n case '-':\n return 'Dash'\n case '@':\n return 'At'\n case '(':\n return '' // Removed since route groups use parentheses\n case ')':\n return '' // Removed since route groups use parentheses\n case ' ':\n return '' // Remove spaces\n default:\n return `Char${char.charCodeAt(0)}` // For any other characters\n }\n}\n\nexport function routePathToVariable(routePath: string): string {\n const cleaned = removeUnderscores(routePath)\n if (!cleaned) return ''\n\n const parts = cleaned\n .replace(splatSlashRegex, '/splat/')\n .replace(trailingSplatRegex, 'splat')\n .replace(bracketSplatRegex, 'splat')\n .replace(dollarSignRegex, '')\n .split(splitPathRegex)\n\n let result = ''\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i]!\n const segment = i > 0 ? capitalize(part) : part\n for (let j = 0; j < segment.length; j++) {\n result += toVariableSafeChar(segment[j]!)\n }\n }\n\n return result.replace(leadingDigitRegex, 'R$1')\n}\n\nconst underscoreStartEndRegex = /(^_|_$)/gi\nconst underscoreSlashRegex = /(\\/_|_\\/)/gi\n\nexport function removeUnderscores(s?: string) {\n return s\n ?.replace(underscoreStartEndRegex, '')\n .replace(underscoreSlashRegex, '/')\n}\n\n/**\n * Removes underscores from a path, but preserves underscores that were escaped\n * in the original path (indicated by [_] syntax).\n *\n * @param routePath - The path with brackets removed\n * @param originalPath - The original path that may contain [_] escape sequences\n * @returns The path with non-escaped underscores removed\n */\nexport function removeUnderscoresWithEscape(\n routePath?: string,\n originalPath?: string,\n): string {\n if (!routePath) return ''\n if (!originalPath) return removeUnderscores(routePath) ?? ''\n\n const routeSegments = routePath.split('/')\n const originalSegments = originalPath.split('/')\n\n const newSegments = routeSegments.map((segment, i) => {\n const originalSegment = originalSegments[i] || ''\n\n // Check if leading underscore is escaped\n const leadingEscaped = hasEscapedLeadingUnderscore(originalSegment)\n // Check if trailing underscore is escaped\n const trailingEscaped = hasEscapedTrailingUnderscore(originalSegment)\n\n let result = segment\n\n // Remove leading underscore only if not escaped\n if (result.startsWith('_') && !leadingEscaped) {\n result = result.slice(1)\n }\n\n // Remove trailing underscore only if not escaped\n if (result.endsWith('_') && !trailingEscaped) {\n result = result.slice(0, -1)\n }\n\n return result\n })\n\n return newSegments.join('/')\n}\n\n/**\n * Removes layout segments (segments starting with underscore) from a path,\n * but preserves segments where the underscore was escaped.\n *\n * @param routePath - The path with brackets removed\n * @param originalPath - The original path that may contain [_] escape sequences\n * @returns The path with non-escaped layout segments removed\n */\nexport function removeLayoutSegmentsWithEscape(\n routePath: string = '/',\n originalPath?: string,\n): string {\n if (!originalPath) return removeLayoutSegments(routePath)\n\n const routeSegments = routePath.split('/')\n const originalSegments = originalPath.split('/')\n\n // Keep segments that are NOT pathless (i.e., don't start with unescaped underscore)\n const newSegments = routeSegments.filter((segment, i) => {\n const originalSegment = originalSegments[i] || ''\n return !isSegmentPathless(segment, originalSegment)\n })\n\n return newSegments.join('/')\n}\n\n/**\n * Checks if a segment should be treated as a pathless/layout segment.\n * A segment is pathless if it starts with underscore and the underscore is not escaped.\n *\n * @param segment - The segment from routePath (brackets removed)\n * @param originalSegment - The segment from originalRoutePath (may contain brackets)\n * @returns true if the segment is pathless (has non-escaped leading underscore)\n */\nexport function isSegmentPathless(\n segment: string,\n originalSegment: string,\n): boolean {\n if (!segment.startsWith('_')) return false\n return !hasEscapedLeadingUnderscore(originalSegment)\n}\n\nexport function escapeRegExp(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\nfunction sanitizeTokenFlags(flags?: string): string | undefined {\n if (!flags) return flags\n\n // Prevent stateful behavior with RegExp.prototype.test/exec\n // g = global, y = sticky\n return flags.replace(/[gy]/g, '')\n}\n\nexport function createTokenRegex(\n token: TokenMatcher,\n opts: {\n type: 'segment' | 'filename'\n },\n): RegExp {\n // Defensive check: if token is undefined/null, throw a clear error\n // (runtime safety for config loading edge cases)\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (token === undefined || token === null) {\n throw new Error(\n `createTokenRegex: token is ${token}. This usually means the config was not properly parsed with defaults.`,\n )\n }\n\n try {\n if (typeof token === 'string') {\n return opts.type === 'segment'\n ? new RegExp(`^${escapeRegExp(token)}$`)\n : new RegExp(`[./]${escapeRegExp(token)}[.]`)\n }\n\n if (token instanceof RegExp) {\n const flags = sanitizeTokenFlags(token.flags)\n return opts.type === 'segment'\n ? new RegExp(`^(?:${token.source})$`, flags)\n : new RegExp(`[./](?:${token.source})[.]`, flags)\n }\n\n // Handle JSON regex object form: { regex: string, flags?: string }\n if (typeof token === 'object' && 'regex' in token) {\n const flags = sanitizeTokenFlags(token.flags)\n return opts.type === 'segment'\n ? new RegExp(`^(?:${token.regex})$`, flags)\n : new RegExp(`[./](?:${token.regex})[.]`, flags)\n }\n\n throw new Error(\n `createTokenRegex: invalid token type. Expected string, RegExp, or { regex, flags } object, got: ${typeof token}`,\n )\n } catch (e) {\n if (e instanceof SyntaxError) {\n const pattern =\n typeof token === 'string'\n ? token\n : token instanceof RegExp\n ? token.source\n : token.regex\n throw new Error(\n `Invalid regex pattern in token config: \"${pattern}\". ${e.message}`,\n )\n }\n throw e\n }\n}\n\nexport function isBracketWrappedSegment(segment: string): boolean {\n return segment.startsWith('[') && segment.endsWith(']')\n}\n\nexport function unwrapBracketWrappedSegment(segment: string): string {\n return isBracketWrappedSegment(segment) ? segment.slice(1, -1) : segment\n}\n\nexport function removeLeadingUnderscores(s: string, routeToken: string) {\n if (!s) return s\n\n const hasLeadingUnderscore = routeToken[0] === '_'\n\n const routeTokenToExclude = hasLeadingUnderscore\n ? routeToken.slice(1)\n : routeToken\n\n const escapedRouteToken = escapeRegExp(routeTokenToExclude)\n\n const leadingUnderscoreRegex = hasLeadingUnderscore\n ? new RegExp(`(?<=^|\\\\/)_(?!${escapedRouteToken})`, 'g')\n : new RegExp(`(?<=^|\\\\/)_`, 'g')\n\n return s.replaceAll(leadingUnderscoreRegex, '')\n}\n\nexport function removeTrailingUnderscores(s: string, routeToken: string) {\n if (!s) return s\n\n const hasTrailingUnderscore = routeToken.slice(-1) === '_'\n\n const routeTokenToExclude = hasTrailingUnderscore\n ? routeToken.slice(0, -1)\n : routeToken\n\n const escapedRouteToken = escapeRegExp(routeTokenToExclude)\n\n const trailingUnderscoreRegex = hasTrailingUnderscore\n ? new RegExp(`(?<!${escapedRouteToken})_(?=\\\\/|$)`, 'g')\n : new RegExp(`_(?=\\\\/)|_$`, 'g')\n\n return s.replaceAll(trailingUnderscoreRegex, '')\n}\n\nexport function capitalize(s: string) {\n if (typeof s !== 'string') return ''\n return s.charAt(0).toUpperCase() + s.slice(1)\n}\n\nexport function removeExt(d: string, addExtensions: boolean | string = false) {\n if (typeof addExtensions === 'string') {\n const dotIndex = d.lastIndexOf('.')\n if (dotIndex === -1) return d\n return d.substring(0, dotIndex) + addExtensions\n }\n return addExtensions ? d : d.substring(0, d.lastIndexOf('.')) || d\n}\n\n/**\n * This function writes to a file if the content is different.\n *\n * @param filepath The path to the file\n * @param content Original content\n * @param incomingContent New content\n * @param callbacks Callbacks to run before and after writing\n * @returns Whether the file was written\n */\nexport async function writeIfDifferent(\n filepath: string,\n content: string,\n incomingContent: string,\n callbacks?: { beforeWrite?: () => void; afterWrite?: () => void },\n): Promise<boolean> {\n if (content !== incomingContent) {\n callbacks?.beforeWrite?.()\n await fsp.writeFile(filepath, incomingContent)\n callbacks?.afterWrite?.()\n return true\n }\n return false\n}\n\n/**\n * This function formats the source code using the default formatter (Prettier).\n *\n * @param source The content to format\n * @param config The configuration object\n * @returns The formatted content\n */\nexport async function format(\n source: string,\n config: {\n quoteStyle: 'single' | 'double'\n semicolons: boolean\n },\n): Promise<string> {\n const prettierOptions: prettier.Config = {\n semi: config.semicolons,\n singleQuote: config.quoteStyle === 'single',\n parser: 'typescript',\n }\n return prettier.format(source, prettierOptions)\n}\n\n/**\n * This function resets the regex index to 0 so that it can be reused\n * without having to create a new regex object or worry about the last\n * state when using the global flag.\n *\n * @param regex The regex object to reset\n * @returns\n */\nexport function resetRegex(regex: RegExp) {\n regex.lastIndex = 0\n return\n}\n\n/**\n * This function checks if a file exists.\n *\n * @param file The path to the file\n * @returns Whether the file exists\n */\nexport async function checkFileExists(file: string) {\n try {\n await fsp.access(file, fsp.constants.F_OK)\n return true\n } catch {\n return false\n }\n}\n\nconst possiblyNestedRouteGroupPatternRegex = /\\([^/]+\\)\\/?/g\nexport function removeGroups(s: string) {\n return s.replace(possiblyNestedRouteGroupPatternRegex, '')\n}\n\n/**\n * Removes all segments from a given path that start with an underscore ('_').\n *\n * @param {string} routePath - The path from which to remove segments. Defaults to '/'.\n * @returns {string} The path with all underscore-prefixed segments removed.\n * @example\n * removeLayoutSegments('/workspace/_auth/foo') // '/workspace/foo'\n */\nexport function removeLayoutSegments(routePath: string = '/'): string {\n const segments = routePath.split('/')\n const newSegments = segments.filter((segment) => !segment.startsWith('_'))\n return newSegments.join('/')\n}\n\n/**\n * The `node.path` is used as the `id` in the route definition.\n * This function checks if the given node has a parent and if so, it determines the correct path for the given node.\n * @param node - The node to determine the path for.\n * @returns The correct path for the given node.\n */\nexport function determineNodePath(node: RouteNode) {\n return (node.path = node.parent\n ? node.routePath?.replace(node.parent.routePath ?? '', '') || '/'\n : node.routePath)\n}\n\n/**\n * Removes the last segment from a given path. Segments are considered to be separated by a '/'.\n *\n * @param {string} routePath - The path from which to remove the last segment. Defaults to '/'.\n * @returns {string} The path with the last segment removed.\n * @example\n * removeLastSegmentFromPath('/workspace/_auth/foo') // '/workspace/_auth'\n */\nexport function removeLastSegmentFromPath(routePath: string = '/'): string {\n const segments = routePath.split('/')\n segments.pop() // Remove the last segment\n return segments.join('/')\n}\n\n/**\n * Find parent route using RoutePrefixMap for O(k) lookups instead of O(n).\n */\nexport function hasParentRoute(\n prefixMap: RoutePrefixMap,\n node: RouteNode,\n routePathToCheck: string | undefined,\n): RouteNode | null {\n if (!routePathToCheck || routePathToCheck === '/') {\n return null\n }\n\n return prefixMap.findParent(routePathToCheck)\n}\n\n/**\n * Gets the final variable name for a route\n */\nexport const getResolvedRouteNodeVariableName = (\n routeNode: RouteNode,\n): string => {\n return routeNode.children?.length\n ? `${routeNode.variableName}RouteWithChildren`\n : `${routeNode.variableName}Route`\n}\n\n/**\n * Checks if a given RouteNode is valid for augmenting it with typing based on conditions.\n * Also asserts that the RouteNode is defined.\n *\n * @param routeNode - The RouteNode to check.\n * @returns A boolean indicating whether the RouteNode is defined.\n */\nexport function isRouteNodeValidForAugmentation(\n routeNode?: RouteNode,\n): routeNode is RouteNode {\n if (!routeNode || routeNode.isVirtual) {\n return false\n }\n return true\n}\n\n/**\n * Infers the path for use by TS\n */\nexport const inferPath = (routeNode: RouteNode): string => {\n if (routeNode.cleanedPath === '/') {\n return routeNode.cleanedPath ?? ''\n }\n return routeNode.cleanedPath?.replace(/\\/$/, '') ?? ''\n}\n\n/**\n * Infers the full path for use by TS\n */\nexport const inferFullPath = (routeNode: RouteNode): string => {\n const fullPath = removeGroups(\n removeUnderscoresWithEscape(\n removeLayoutSegmentsWithEscape(\n routeNode.routePath,\n routeNode.originalRoutePath,\n ),\n routeNode.originalRoutePath,\n ),\n )\n\n if (fullPath === '') {\n return '/'\n }\n\n // Preserve trailing slash for index routes (routePath ends with '/')\n // This ensures types match runtime behavior\n const isIndexRoute = routeNode.routePath?.endsWith('/')\n if (isIndexRoute) {\n return fullPath\n }\n\n return fullPath.replace(/\\/$/, '')\n}\n\nconst shouldPreferIndexRoute = (\n current: RouteNode,\n existing: RouteNode,\n): boolean => {\n return existing.cleanedPath === '/' && current.cleanedPath !== '/'\n}\n\n/**\n * Creates a map from fullPath to routeNode\n */\nexport const createRouteNodesByFullPath = (\n routeNodes: Array<RouteNode>,\n): Map<string, RouteNode> => {\n const map = new Map<string, RouteNode>()\n\n for (const routeNode of routeNodes) {\n const fullPath = inferFullPath(routeNode)\n\n if (fullPath === '/' && map.has('/')) {\n const existing = map.get('/')!\n if (shouldPreferIndexRoute(routeNode, existing)) {\n continue\n }\n }\n\n map.set(fullPath, routeNode)\n }\n\n return map\n}\n\n/**\n * Create a map from 'to' to a routeNode\n */\nexport const createRouteNodesByTo = (\n routeNodes: Array<RouteNode>,\n): Map<string, RouteNode> => {\n const map = new Map<string, RouteNode>()\n\n for (const routeNode of dedupeBranchesAndIndexRoutes(routeNodes)) {\n const to = inferTo(routeNode)\n\n if (to === '/' && map.has('/')) {\n const existing = map.get('/')!\n if (shouldPreferIndexRoute(routeNode, existing)) {\n continue\n }\n }\n\n map.set(to, routeNode)\n }\n\n return map\n}\n\n/**\n * Create a map from 'id' to a routeNode\n */\nexport const createRouteNodesById = (\n routeNodes: Array<RouteNode>,\n): Map<string, RouteNode> => {\n return new Map(\n routeNodes.map((routeNode) => {\n const id = routeNode.routePath ?? ''\n return [id, routeNode]\n }),\n )\n}\n\n/**\n * Infers to path\n */\nexport const inferTo = (routeNode: RouteNode): string => {\n const fullPath = inferFullPath(routeNode)\n\n if (fullPath === '/') return fullPath\n\n return fullPath.replace(/\\/$/, '')\n}\n\n/**\n * Dedupes branches and index routes\n */\nexport const dedupeBranchesAndIndexRoutes = (\n routes: Array<RouteNode>,\n): Array<RouteNode> => {\n return routes.filter((route) => {\n if (route.children?.find((child) => child.cleanedPath === '/')) return false\n return true\n })\n}\n\nfunction checkUnique<TElement>(routes: Array<TElement>, key: keyof TElement) {\n // Check no two routes have the same `key`\n // if they do, throw an error with the conflicting filePaths\n const keys = routes.map((d) => d[key])\n const uniqueKeys = new Set(keys)\n if (keys.length !== uniqueKeys.size) {\n const duplicateKeys = keys.filter((d, i) => keys.indexOf(d) !== i)\n const conflictingFiles = routes.filter((d) =>\n duplicateKeys.includes(d[key]),\n )\n return conflictingFiles\n }\n return undefined\n}\n\nexport function checkRouteFullPathUniqueness(\n _routes: Array<RouteNode>,\n config: Config,\n) {\n const emptyPathRoutes = _routes.filter((d) => d.routePath === '')\n if (emptyPathRoutes.length) {\n const errorMessage = `Invalid route path \"\" was found. Root routes must be defined via __root.tsx (createRootRoute), not createFileRoute('') or a route file that resolves to an empty path.\nConflicting files: \\n ${emptyPathRoutes\n .map((d) => path.resolve(config.routesDirectory, d.filePath))\n .join('\\n ')}\\n`\n throw new Error(errorMessage)\n }\n\n const routes = _routes.map((d) => {\n const inferredFullPath = inferFullPath(d)\n return { ...d, inferredFullPath }\n })\n\n const conflictingFiles = checkUnique(routes, 'inferredFullPath')\n\n if (conflictingFiles !== undefined) {\n const errorMessage = `Conflicting configuration paths were found for the following route${conflictingFiles.length > 1 ? 's' : ''}: ${conflictingFiles\n .map((p) => `\"${p.inferredFullPath}\"`)\n .join(', ')}.\nPlease ensure each Route has a unique full path.\nConflicting files: \\n ${conflictingFiles.map((d) => path.resolve(config.routesDirectory, d.filePath)).join('\\n ')}\\n`\n throw new Error(errorMessage)\n }\n}\n\nexport function buildRouteTreeConfig(\n nodes: Array<RouteNode>,\n disableTypes: boolean,\n depth = 1,\n): Array<string> {\n const children = nodes.map((node) => {\n if (node._fsRouteType === '__root') {\n return\n }\n\n if (node._fsRouteType === 'pathless_layout' && !node.children?.length) {\n return\n }\n\n const route = `${node.variableName}`\n\n if (node.children?.length) {\n const childConfigs = buildRouteTreeConfig(\n node.children,\n disableTypes,\n depth + 1,\n )\n\n const childrenDeclaration = disableTypes\n ? ''\n : `interface ${route}RouteChildren {\n ${node.children\n .map(\n (child) =>\n `${child.variableName}Route: typeof ${getResolvedRouteNodeVariableName(child)}`,\n )\n .join(',')}\n}`\n\n const children = `const ${route}RouteChildren${disableTypes ? '' : `: ${route}RouteChildren`} = {\n ${node.children\n .map(\n (child) =>\n `${child.variableName}Route: ${getResolvedRouteNodeVariableName(child)}`,\n )\n .join(',')}\n}`\n\n const routeWithChildren = `const ${route}RouteWithChildren = ${route}Route._addFileChildren(${route}RouteChildren)`\n\n return [\n childConfigs.join('\\n'),\n childrenDeclaration,\n children,\n routeWithChildren,\n ].join('\\n\\n')\n }\n\n return undefined\n })\n\n return children.filter((x) => x !== undefined)\n}\n\nexport function buildImportString(\n importDeclaration: ImportDeclaration,\n): string {\n const { source, specifiers, importKind } = importDeclaration\n return specifiers.length\n ? `import ${importKind === 'type' ? 'type ' : ''}{ ${specifiers.map((s) => (s.local ? `${s.imported} as ${s.local}` : s.imported)).join(', ')} } from '${source}'`\n : ''\n}\n\nexport function lowerCaseFirstChar(value: string) {\n if (!value[0]) {\n return value\n }\n\n return value[0].toLowerCase() + value.slice(1)\n}\n\nexport function mergeImportDeclarations(\n imports: Array<ImportDeclaration>,\n): Array<ImportDeclaration> {\n const merged = new Map<string, ImportDeclaration>()\n\n for (const imp of imports) {\n const key = `${imp.source}-${imp.importKind ?? ''}`\n let existing = merged.get(key)\n if (!existing) {\n existing = { ...imp, specifiers: [] }\n merged.set(key, existing)\n }\n\n const existingSpecs = existing.specifiers\n for (const specifier of imp.specifiers) {\n let found = false\n for (let i = 0; i < existingSpecs.length; i++) {\n const e = existingSpecs[i]!\n if (e.imported === specifier.imported && e.local === specifier.local) {\n found = true\n break\n }\n }\n if (!found) {\n existingSpecs.push(specifier)\n }\n }\n }\n\n return [...merged.values()]\n}\n\nexport const findParent = (node: RouteNode | undefined): string => {\n if (!node) {\n return `rootRouteImport`\n }\n if (node.parent) {\n return `${node.parent.variableName}Route`\n }\n return findParent(node.parent)\n}\n\nexport function buildFileRoutesByPathInterface(opts: {\n routeNodes: Array<RouteNode>\n module: string\n interfaceName: string\n config?: Pick<Config, 'routeToken'>\n}): string {\n return `declare module '${opts.module}' {\n interface ${opts.interfaceName} {\n ${opts.routeNodes\n .map((routeNode) => {\n const filePathId = routeNode.routePath\n const preloaderRoute = `typeof ${routeNode.variableName}RouteImport`\n\n const parent = findParent(routeNode)\n\n return `'${filePathId}': {\n id: '${filePathId}'\n path: '${inferPath(routeNode)}'\n fullPath: '${inferFullPath(routeNode)}'\n preLoaderRoute: ${preloaderRoute}\n parentRoute: typeof ${parent}\n }`\n })\n .join('\\n')}\n }\n}`\n}\n\nexport function getImportPath(\n node: RouteNode,\n config: Config,\n generatedRouteTreePath: string,\n): string {\n return replaceBackslash(\n removeExt(\n path.relative(\n path.dirname(generatedRouteTreePath),\n path.resolve(config.routesDirectory, node.filePath),\n ),\n config.addExtensions,\n ),\n )\n}\n\nexport function getImportForRouteNode(\n node: RouteNode,\n config: Config,\n generatedRouteTreePath: string,\n root: string,\n): ImportDeclaration {\n let source = ''\n if (config.importRoutesUsingAbsolutePaths) {\n source = replaceBackslash(\n removeExt(\n path.resolve(root, config.routesDirectory, node.filePath),\n config.addExtensions,\n ),\n )\n } else {\n source = `./${getImportPath(node, config, generatedRouteTreePath)}`\n }\n return {\n source,\n specifiers: [\n {\n imported: 'Route',\n local: `${node.variableName}RouteImport`,\n },\n ],\n } satisfies ImportDeclaration\n}\n"],"mappings":"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"} | ||
| {"version":3,"file":"utils.cjs","names":[],"sources":["../../src/utils.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/prefer-for-of */\nimport * as fsp from 'node:fs/promises'\nimport path from 'node:path'\nimport * as prettier from 'prettier'\nimport { rootPathId } from './filesystem/physical/rootPathId'\nimport type { Config, TokenMatcher } from './config'\nimport type { ImportDeclaration, RouteNode } from './types'\n\n/**\n * Prefix map for O(1) parent route lookups.\n * Maps each route path prefix to the route node that owns that prefix.\n * Enables finding longest matching parent without linear search.\n */\nexport class RoutePrefixMap {\n private prefixToRoute: Map<string, RouteNode> = new Map()\n\n constructor(routes: Array<RouteNode>) {\n for (const route of routes) {\n if (!route.routePath || route.routePath === `/${rootPathId}`) continue\n\n // Skip route pieces (lazy, loader, component, etc.) - they are merged with main routes\n // and should not be valid parent candidates\n if (\n route._fsRouteType === 'lazy' ||\n route._fsRouteType === 'loader' ||\n route._fsRouteType === 'component' ||\n route._fsRouteType === 'pendingComponent' ||\n route._fsRouteType === 'errorComponent' ||\n route._fsRouteType === 'notFoundComponent'\n ) {\n continue\n }\n\n // Index by exact path for direct lookups\n this.prefixToRoute.set(route.routePath, route)\n }\n }\n\n /**\n * Find the longest matching parent route for a given path.\n * O(k) where k is the number of path segments, not O(n) routes.\n */\n findParent(routePath: string): RouteNode | null {\n if (!routePath || routePath === '/') return null\n\n // Walk up the path segments\n let searchPath = routePath\n while (searchPath.length > 0) {\n const lastSlash = searchPath.lastIndexOf('/')\n if (lastSlash <= 0) break\n\n searchPath = searchPath.substring(0, lastSlash)\n const parent = this.prefixToRoute.get(searchPath)\n if (parent && parent.routePath !== routePath) {\n return parent\n }\n }\n return null\n }\n\n /**\n * Check if a route exists at the given path.\n */\n has(routePath: string): boolean {\n return this.prefixToRoute.has(routePath)\n }\n\n /**\n * Get a route by exact path.\n */\n get(routePath: string): RouteNode | undefined {\n return this.prefixToRoute.get(routePath)\n }\n}\n\nexport function multiSortBy<T>(\n arr: Array<T>,\n accessors: Array<(item: T) => any> = [(d) => d],\n): Array<T> {\n const len = arr.length\n // Pre-compute all accessor values to avoid repeated function calls during sort\n const indexed: Array<{ item: T; index: number; keys: Array<any> }> =\n new Array(len)\n for (let i = 0; i < len; i++) {\n const item = arr[i]!\n const keys = new Array(accessors.length)\n for (let j = 0; j < accessors.length; j++) {\n keys[j] = accessors[j]!(item)\n }\n indexed[i] = { item, index: i, keys }\n }\n\n indexed.sort((a, b) => {\n for (let j = 0; j < accessors.length; j++) {\n const ao = a.keys[j]\n const bo = b.keys[j]\n\n if (typeof ao === 'undefined') {\n if (typeof bo === 'undefined') {\n continue\n }\n return 1\n }\n\n if (ao === bo) {\n continue\n }\n\n return ao > bo ? 1 : -1\n }\n\n return a.index - b.index\n })\n\n const result: Array<T> = new Array(len)\n for (let i = 0; i < len; i++) {\n result[i] = indexed[i]!.item\n }\n return result\n}\n\nexport function cleanPath(path: string) {\n // remove double slashes\n return path.replace(/\\/{2,}/g, '/')\n}\n\nexport function trimPathLeft(path: string) {\n return path === '/' ? path : path.replace(/^\\/{1,}/, '')\n}\n\nexport function removeLeadingSlash(path: string): string {\n return path.replace(/^\\//, '')\n}\n\nexport function removeTrailingSlash(s: string) {\n return s.replace(/\\/$/, '')\n}\n\nconst BRACKET_CONTENT_RE = /\\[(.*?)\\]/g\nconst SPLIT_REGEX = /(?<!\\[)\\.(?!\\])/g\n\n/**\n * Characters that cannot be escaped in square brackets.\n * These are characters that would cause issues in URLs or file systems.\n */\nconst DISALLOWED_ESCAPE_CHARS = new Set([\n '/',\n '\\\\',\n '?',\n '#',\n ':',\n '*',\n '<',\n '>',\n '|',\n '!',\n '$',\n '%',\n])\n\nexport function determineInitialRoutePath(routePath: string) {\n const originalRoutePath =\n cleanPath(\n `/${(cleanPath(routePath) || '').split(SPLIT_REGEX).join('/')}`,\n ) || ''\n\n const parts = routePath.split(SPLIT_REGEX)\n\n // Escape any characters that in square brackets\n // we keep the original path untouched\n const escapedParts = parts.map((part) => {\n // Check if any disallowed characters are used in brackets\n\n let match\n while ((match = BRACKET_CONTENT_RE.exec(part)) !== null) {\n const character = match[1]\n if (character === undefined) continue\n if (DISALLOWED_ESCAPE_CHARS.has(character)) {\n console.error(\n `Error: Disallowed character \"${character}\" found in square brackets in route path \"${routePath}\".\\nYou cannot use any of the following characters in square brackets: ${Array.from(\n DISALLOWED_ESCAPE_CHARS,\n ).join(', ')}\\nPlease remove and/or replace them.`,\n )\n process.exit(1)\n }\n }\n\n // Since this split segment is safe at this point, we can\n // remove the brackets and replace them with the content inside\n return part.replace(BRACKET_CONTENT_RE, '$1')\n })\n\n // If the syntax for prefix/suffix is different, from the path\n // matching internals of router-core, we'd perform those changes here\n // on the `escapedParts` array before it is joined back together in\n // `final`\n\n const final = cleanPath(`/${escapedParts.join('/')}`) || ''\n\n return {\n routePath: final,\n originalRoutePath,\n }\n}\n\n/**\n * Checks if a segment is fully escaped (entirely wrapped in brackets with no nested brackets).\n * E.g., \"[index]\" -> true, \"[_layout]\" -> true, \"foo[.]bar\" -> false, \"index\" -> false\n */\nfunction isFullyEscapedSegment(originalSegment: string): boolean {\n return (\n originalSegment.startsWith('[') &&\n originalSegment.endsWith(']') &&\n !originalSegment.slice(1, -1).includes('[') &&\n !originalSegment.slice(1, -1).includes(']')\n )\n}\n\n/**\n * Checks if the leading underscore in a segment is escaped.\n * Returns true if:\n * - Segment starts with [_] pattern: \"[_]layout\" -> \"_layout\"\n * - Segment is fully escaped and content starts with _: \"[_1nd3x]\" -> \"_1nd3x\"\n */\nexport function hasEscapedLeadingUnderscore(originalSegment: string): boolean {\n // Pattern: [_]something or [_something]\n return (\n originalSegment.startsWith('[_]') ||\n (originalSegment.startsWith('[_') && isFullyEscapedSegment(originalSegment))\n )\n}\n\n/**\n * Checks if the trailing underscore in a segment is escaped.\n * Returns true if:\n * - Segment ends with [_] pattern: \"blog[_]\" -> \"blog_\"\n * - Segment is fully escaped and content ends with _: \"[_r0ut3_]\" -> \"_r0ut3_\"\n */\nexport function hasEscapedTrailingUnderscore(originalSegment: string): boolean {\n // Pattern: something[_] or [something_]\n return (\n originalSegment.endsWith('[_]') ||\n (originalSegment.endsWith('_]') && isFullyEscapedSegment(originalSegment))\n )\n}\n\nconst backslashRegex = /\\\\/g\n\nexport function replaceBackslash(s: string) {\n return s.replace(backslashRegex, '/')\n}\n\nconst alphanumericRegex = /[a-zA-Z0-9_]/\nconst splatSlashRegex = /\\/\\$\\//g\nconst trailingSplatRegex = /\\$$/g\nconst bracketSplatRegex = /\\$\\{\\$\\}/g\nconst dollarSignRegex = /\\$/g\nconst splitPathRegex = /[/-]/g\nconst leadingDigitRegex = /^(\\d)/g\n\nconst toVariableSafeChar = (char: string): string => {\n if (alphanumericRegex.test(char)) {\n return char // Keep alphanumeric characters and underscores as is\n }\n\n // Replace special characters with meaningful text equivalents\n switch (char) {\n case '.':\n return 'Dot'\n case '-':\n return 'Dash'\n case '@':\n return 'At'\n case '(':\n return '' // Removed since route groups use parentheses\n case ')':\n return '' // Removed since route groups use parentheses\n case ' ':\n return '' // Remove spaces\n default:\n return `Char${char.charCodeAt(0)}` // For any other characters\n }\n}\n\nexport function routePathToVariable(routePath: string): string {\n const cleaned = removeUnderscores(routePath)\n if (!cleaned) return ''\n\n const parts = cleaned\n .replace(splatSlashRegex, '/splat/')\n .replace(trailingSplatRegex, 'splat')\n .replace(bracketSplatRegex, 'splat')\n .replace(dollarSignRegex, '')\n .split(splitPathRegex)\n\n let result = ''\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i]!\n const segment = i > 0 ? capitalize(part) : part\n for (let j = 0; j < segment.length; j++) {\n result += toVariableSafeChar(segment[j]!)\n }\n }\n\n return result.replace(leadingDigitRegex, 'R$1')\n}\n\nconst underscoreStartEndRegex = /(^_|_$)/gi\nconst underscoreSlashRegex = /(\\/_|_\\/)/gi\n\nexport function removeUnderscores(s?: string) {\n return s\n ?.replace(underscoreStartEndRegex, '')\n .replace(underscoreSlashRegex, '/')\n}\n\n/**\n * Removes underscores from a path, but preserves underscores that were escaped\n * in the original path (indicated by [_] syntax).\n *\n * @param routePath - The path with brackets removed\n * @param originalPath - The original path that may contain [_] escape sequences\n * @returns The path with non-escaped underscores removed\n */\nexport function removeUnderscoresWithEscape(\n routePath?: string,\n originalPath?: string,\n): string {\n if (!routePath) return ''\n if (!originalPath) return removeUnderscores(routePath) ?? ''\n\n const routeSegments = routePath.split('/')\n const originalSegments = originalPath.split('/')\n\n const newSegments = routeSegments.map((segment, i) => {\n const originalSegment = originalSegments[i] || ''\n\n // Check if leading underscore is escaped\n const leadingEscaped = hasEscapedLeadingUnderscore(originalSegment)\n // Check if trailing underscore is escaped\n const trailingEscaped = hasEscapedTrailingUnderscore(originalSegment)\n\n let result = segment\n\n // Remove leading underscore only if not escaped\n if (result.startsWith('_') && !leadingEscaped) {\n result = result.slice(1)\n }\n\n // Remove trailing underscore only if not escaped\n if (result.endsWith('_') && !trailingEscaped) {\n result = result.slice(0, -1)\n }\n\n return result\n })\n\n return newSegments.join('/')\n}\n\n/**\n * Removes layout segments (segments starting with underscore) from a path,\n * but preserves segments where the underscore was escaped.\n *\n * @param routePath - The path with brackets removed\n * @param originalPath - The original path that may contain [_] escape sequences\n * @returns The path with non-escaped layout segments removed\n */\nexport function removeLayoutSegmentsWithEscape(\n routePath: string = '/',\n originalPath?: string,\n): string {\n if (!originalPath) return removeLayoutSegments(routePath)\n\n const routeSegments = routePath.split('/')\n const originalSegments = originalPath.split('/')\n\n // Keep segments that are NOT pathless (i.e., don't start with unescaped underscore)\n const newSegments = routeSegments.filter((segment, i) => {\n const originalSegment = originalSegments[i] || ''\n return !isSegmentPathless(segment, originalSegment)\n })\n\n return newSegments.join('/')\n}\n\n/**\n * Checks if a segment should be treated as a pathless/layout segment.\n * A segment is pathless if it starts with underscore and the underscore is not escaped.\n *\n * @param segment - The segment from routePath (brackets removed)\n * @param originalSegment - The segment from originalRoutePath (may contain brackets)\n * @returns true if the segment is pathless (has non-escaped leading underscore)\n */\nexport function isSegmentPathless(\n segment: string,\n originalSegment: string,\n): boolean {\n if (!segment.startsWith('_')) return false\n return !hasEscapedLeadingUnderscore(originalSegment)\n}\n\nexport function escapeRegExp(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\nfunction sanitizeTokenFlags(flags?: string): string | undefined {\n if (!flags) return flags\n\n // Prevent stateful behavior with RegExp.prototype.test/exec\n // g = global, y = sticky\n return flags.replace(/[gy]/g, '')\n}\n\nexport function createTokenRegex(\n token: TokenMatcher,\n opts: {\n type: 'segment' | 'filename'\n },\n): RegExp {\n // Defensive check: if token is undefined/null, throw a clear error\n // (runtime safety for config loading edge cases)\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (token === undefined || token === null) {\n throw new Error(\n `createTokenRegex: token is ${token}. This usually means the config was not properly parsed with defaults.`,\n )\n }\n\n try {\n if (typeof token === 'string') {\n return opts.type === 'segment'\n ? new RegExp(`^${escapeRegExp(token)}$`)\n : new RegExp(`[./]${escapeRegExp(token)}[.]`)\n }\n\n if (token instanceof RegExp) {\n const flags = sanitizeTokenFlags(token.flags)\n return opts.type === 'segment'\n ? new RegExp(`^(?:${token.source})$`, flags)\n : new RegExp(`[./](?:${token.source})[.]`, flags)\n }\n\n // Handle JSON regex object form: { regex: string, flags?: string }\n if (typeof token === 'object' && 'regex' in token) {\n const flags = sanitizeTokenFlags(token.flags)\n return opts.type === 'segment'\n ? new RegExp(`^(?:${token.regex})$`, flags)\n : new RegExp(`[./](?:${token.regex})[.]`, flags)\n }\n\n throw new Error(\n `createTokenRegex: invalid token type. Expected string, RegExp, or { regex, flags } object, got: ${typeof token}`,\n )\n } catch (e) {\n if (e instanceof SyntaxError) {\n const pattern =\n typeof token === 'string'\n ? token\n : token instanceof RegExp\n ? token.source\n : token.regex\n throw new Error(\n `Invalid regex pattern in token config: \"${pattern}\". ${e.message}`,\n )\n }\n throw e\n }\n}\n\nfunction isBracketWrappedSegment(segment: string): boolean {\n return segment.startsWith('[') && segment.endsWith(']')\n}\n\nexport function unwrapBracketWrappedSegment(segment: string): string {\n return isBracketWrappedSegment(segment) ? segment.slice(1, -1) : segment\n}\n\nexport function removeLeadingUnderscores(s: string, routeToken: string) {\n if (!s) return s\n\n const hasLeadingUnderscore = routeToken[0] === '_'\n\n const routeTokenToExclude = hasLeadingUnderscore\n ? routeToken.slice(1)\n : routeToken\n\n const escapedRouteToken = escapeRegExp(routeTokenToExclude)\n\n const leadingUnderscoreRegex = hasLeadingUnderscore\n ? new RegExp(`(?<=^|\\\\/)_(?!${escapedRouteToken})`, 'g')\n : new RegExp(`(?<=^|\\\\/)_`, 'g')\n\n return s.replaceAll(leadingUnderscoreRegex, '')\n}\n\nexport function removeTrailingUnderscores(s: string, routeToken: string) {\n if (!s) return s\n\n const hasTrailingUnderscore = routeToken.slice(-1) === '_'\n\n const routeTokenToExclude = hasTrailingUnderscore\n ? routeToken.slice(0, -1)\n : routeToken\n\n const escapedRouteToken = escapeRegExp(routeTokenToExclude)\n\n const trailingUnderscoreRegex = hasTrailingUnderscore\n ? new RegExp(`(?<!${escapedRouteToken})_(?=\\\\/|$)`, 'g')\n : new RegExp(`_(?=\\\\/)|_$`, 'g')\n\n return s.replaceAll(trailingUnderscoreRegex, '')\n}\n\nexport function capitalize(s: string) {\n if (typeof s !== 'string') return ''\n return s.charAt(0).toUpperCase() + s.slice(1)\n}\n\nexport function removeExt(d: string, addExtensions: boolean | string = false) {\n if (typeof addExtensions === 'string') {\n const dotIndex = d.lastIndexOf('.')\n if (dotIndex === -1) return d\n return d.substring(0, dotIndex) + addExtensions\n }\n return addExtensions ? d : d.substring(0, d.lastIndexOf('.')) || d\n}\n\n/**\n * This function writes to a file if the content is different.\n *\n * @param filepath The path to the file\n * @param content Original content\n * @param incomingContent New content\n * @param callbacks Callbacks to run before and after writing\n * @returns Whether the file was written\n */\nexport async function writeIfDifferent(\n filepath: string,\n content: string,\n incomingContent: string,\n callbacks?: { beforeWrite?: () => void; afterWrite?: () => void },\n): Promise<boolean> {\n if (content !== incomingContent) {\n callbacks?.beforeWrite?.()\n await fsp.writeFile(filepath, incomingContent)\n callbacks?.afterWrite?.()\n return true\n }\n return false\n}\n\n/**\n * This function formats the source code using the default formatter (Prettier).\n *\n * @param source The content to format\n * @param config The configuration object\n * @returns The formatted content\n */\nexport async function format(\n source: string,\n config: {\n quoteStyle: 'single' | 'double'\n semicolons: boolean\n },\n): Promise<string> {\n const prettierOptions: prettier.Config = {\n semi: config.semicolons,\n singleQuote: config.quoteStyle === 'single',\n parser: 'typescript',\n }\n return prettier.format(source, prettierOptions)\n}\n\n/**\n * This function resets the regex index to 0 so that it can be reused\n * without having to create a new regex object or worry about the last\n * state when using the global flag.\n *\n * @param regex The regex object to reset\n * @returns\n */\nexport function resetRegex(regex: RegExp) {\n regex.lastIndex = 0\n return\n}\n\n/**\n * This function checks if a file exists.\n *\n * @param file The path to the file\n * @returns Whether the file exists\n */\nexport async function checkFileExists(file: string) {\n try {\n await fsp.access(file, fsp.constants.F_OK)\n return true\n } catch {\n return false\n }\n}\n\nconst possiblyNestedRouteGroupPatternRegex = /\\([^/]+\\)\\/?/g\nexport function removeGroups(s: string) {\n return s.replace(possiblyNestedRouteGroupPatternRegex, '')\n}\n\n/**\n * Removes all segments from a given path that start with an underscore ('_').\n *\n * @param {string} routePath - The path from which to remove segments. Defaults to '/'.\n * @returns {string} The path with all underscore-prefixed segments removed.\n * @example\n * removeLayoutSegments('/workspace/_auth/foo') // '/workspace/foo'\n */\nexport function removeLayoutSegments(routePath: string = '/'): string {\n const segments = routePath.split('/')\n const newSegments = segments.filter((segment) => !segment.startsWith('_'))\n return newSegments.join('/')\n}\n\n/**\n * The `node.path` is used as the `id` in the route definition.\n * This function checks if the given node has a parent and if so, it determines the correct path for the given node.\n * @param node - The node to determine the path for.\n * @returns The correct path for the given node.\n */\nexport function determineNodePath(node: RouteNode) {\n return (node.path = node.parent\n ? node.routePath?.replace(node.parent.routePath ?? '', '') || '/'\n : node.routePath)\n}\n\n/**\n * Removes the last segment from a given path. Segments are considered to be separated by a '/'.\n *\n * @param {string} routePath - The path from which to remove the last segment. Defaults to '/'.\n * @returns {string} The path with the last segment removed.\n * @example\n * removeLastSegmentFromPath('/workspace/_auth/foo') // '/workspace/_auth'\n */\nexport function removeLastSegmentFromPath(routePath: string = '/'): string {\n const segments = routePath.split('/')\n segments.pop() // Remove the last segment\n return segments.join('/')\n}\n\n/**\n * Find parent route using RoutePrefixMap for O(k) lookups instead of O(n).\n */\nexport function hasParentRoute(\n prefixMap: RoutePrefixMap,\n node: RouteNode,\n routePathToCheck: string | undefined,\n): RouteNode | null {\n if (!routePathToCheck || routePathToCheck === '/') {\n return null\n }\n\n return prefixMap.findParent(routePathToCheck)\n}\n\n/**\n * Gets the final variable name for a route\n */\nexport const getResolvedRouteNodeVariableName = (\n routeNode: RouteNode,\n): string => {\n return routeNode.children?.length\n ? `${routeNode.variableName}RouteWithChildren`\n : `${routeNode.variableName}Route`\n}\n\n/**\n * Infers the path for use by TS\n */\nconst inferPath = (routeNode: RouteNode): string => {\n if (routeNode.cleanedPath === '/') {\n return routeNode.cleanedPath ?? ''\n }\n return routeNode.cleanedPath?.replace(/\\/$/, '') ?? ''\n}\n\n/**\n * Infers the full path for use by TS\n */\nexport const inferFullPath = (routeNode: RouteNode): string => {\n const fullPath = removeGroups(\n removeUnderscoresWithEscape(\n removeLayoutSegmentsWithEscape(\n routeNode.routePath,\n routeNode.originalRoutePath,\n ),\n routeNode.originalRoutePath,\n ),\n )\n\n if (fullPath === '') {\n return '/'\n }\n\n // Preserve trailing slash for index routes (routePath ends with '/')\n // This ensures types match runtime behavior\n const isIndexRoute = routeNode.routePath?.endsWith('/')\n if (isIndexRoute) {\n return fullPath\n }\n\n return fullPath.replace(/\\/$/, '')\n}\n\nconst shouldPreferIndexRoute = (\n current: RouteNode,\n existing: RouteNode,\n): boolean => {\n return existing.cleanedPath === '/' && current.cleanedPath !== '/'\n}\n\n/**\n * Creates a map from fullPath to routeNode\n */\nexport const createRouteNodesByFullPath = (\n routeNodes: Array<RouteNode>,\n): Map<string, RouteNode> => {\n const map = new Map<string, RouteNode>()\n\n for (const routeNode of routeNodes) {\n const fullPath = inferFullPath(routeNode)\n\n if (fullPath === '/' && map.has('/')) {\n const existing = map.get('/')!\n if (shouldPreferIndexRoute(routeNode, existing)) {\n continue\n }\n }\n\n map.set(fullPath, routeNode)\n }\n\n return map\n}\n\n/**\n * Create a map from 'to' to a routeNode\n */\nexport const createRouteNodesByTo = (\n routeNodes: Array<RouteNode>,\n): Map<string, RouteNode> => {\n const map = new Map<string, RouteNode>()\n\n for (const routeNode of dedupeBranchesAndIndexRoutes(routeNodes)) {\n const to = inferTo(routeNode)\n\n if (to === '/' && map.has('/')) {\n const existing = map.get('/')!\n if (shouldPreferIndexRoute(routeNode, existing)) {\n continue\n }\n }\n\n map.set(to, routeNode)\n }\n\n return map\n}\n\n/**\n * Create a map from 'id' to a routeNode\n */\nexport const createRouteNodesById = (\n routeNodes: Array<RouteNode>,\n): Map<string, RouteNode> => {\n return new Map(\n routeNodes.map((routeNode) => {\n const id = routeNode.routePath ?? ''\n return [id, routeNode]\n }),\n )\n}\n\n/**\n * Infers to path\n */\nconst inferTo = (routeNode: RouteNode): string => {\n const fullPath = inferFullPath(routeNode)\n\n if (fullPath === '/') return fullPath\n\n return fullPath.replace(/\\/$/, '')\n}\n\n/**\n * Dedupes branches and index routes\n */\nconst dedupeBranchesAndIndexRoutes = (\n routes: Array<RouteNode>,\n): Array<RouteNode> => {\n return routes.filter((route) => {\n if (route.children?.find((child) => child.cleanedPath === '/')) return false\n return true\n })\n}\n\nfunction checkUnique<TElement>(routes: Array<TElement>, key: keyof TElement) {\n // Check no two routes have the same `key`\n // if they do, throw an error with the conflicting filePaths\n const keys = routes.map((d) => d[key])\n const uniqueKeys = new Set(keys)\n if (keys.length !== uniqueKeys.size) {\n const duplicateKeys = keys.filter((d, i) => keys.indexOf(d) !== i)\n const conflictingFiles = routes.filter((d) =>\n duplicateKeys.includes(d[key]),\n )\n return conflictingFiles\n }\n return undefined\n}\n\nexport function checkRouteFullPathUniqueness(\n _routes: Array<RouteNode>,\n config: Config,\n) {\n const emptyPathRoutes = _routes.filter((d) => d.routePath === '')\n if (emptyPathRoutes.length) {\n const errorMessage = `Invalid route path \"\" was found. Root routes must be defined via __root.tsx (createRootRoute), not createFileRoute('') or a route file that resolves to an empty path.\nConflicting files: \\n ${emptyPathRoutes\n .map((d) => path.resolve(config.routesDirectory, d.filePath))\n .join('\\n ')}\\n`\n throw new Error(errorMessage)\n }\n\n const routes = _routes.map((d) => {\n const inferredFullPath = inferFullPath(d)\n return { ...d, inferredFullPath }\n })\n\n const conflictingFiles = checkUnique(routes, 'inferredFullPath')\n\n if (conflictingFiles !== undefined) {\n const errorMessage = `Conflicting configuration paths were found for the following route${conflictingFiles.length > 1 ? 's' : ''}: ${conflictingFiles\n .map((p) => `\"${p.inferredFullPath}\"`)\n .join(', ')}.\nPlease ensure each Route has a unique full path.\nConflicting files: \\n ${conflictingFiles.map((d) => path.resolve(config.routesDirectory, d.filePath)).join('\\n ')}\\n`\n throw new Error(errorMessage)\n }\n}\n\nexport function buildRouteTreeConfig(\n nodes: Array<RouteNode>,\n disableTypes: boolean,\n depth = 1,\n): Array<string> {\n const children = nodes.map((node) => {\n if (node._fsRouteType === '__root') {\n return\n }\n\n if (node._fsRouteType === 'pathless_layout' && !node.children?.length) {\n return\n }\n\n const route = `${node.variableName}`\n\n if (node.children?.length) {\n const childConfigs = buildRouteTreeConfig(\n node.children,\n disableTypes,\n depth + 1,\n )\n\n const childrenDeclaration = disableTypes\n ? ''\n : `interface ${route}RouteChildren {\n ${node.children\n .map(\n (child) =>\n `${child.variableName}Route: typeof ${getResolvedRouteNodeVariableName(child)}`,\n )\n .join(',')}\n}`\n\n const children = `const ${route}RouteChildren${disableTypes ? '' : `: ${route}RouteChildren`} = {\n ${node.children\n .map(\n (child) =>\n `${child.variableName}Route: ${getResolvedRouteNodeVariableName(child)}`,\n )\n .join(',')}\n}`\n\n const routeWithChildren = `const ${route}RouteWithChildren = ${route}Route._addFileChildren(${route}RouteChildren)`\n\n return [\n childConfigs.join('\\n'),\n childrenDeclaration,\n children,\n routeWithChildren,\n ].join('\\n\\n')\n }\n\n return undefined\n })\n\n return children.filter((x) => x !== undefined)\n}\n\nexport function buildImportString(\n importDeclaration: ImportDeclaration,\n): string {\n const { source, specifiers, importKind } = importDeclaration\n return specifiers.length\n ? `import ${importKind === 'type' ? 'type ' : ''}{ ${specifiers.map((s) => (s.local ? `${s.imported} as ${s.local}` : s.imported)).join(', ')} } from '${source}'`\n : ''\n}\n\nexport function mergeImportDeclarations(\n imports: Array<ImportDeclaration>,\n): Array<ImportDeclaration> {\n const merged = new Map<string, ImportDeclaration>()\n\n for (const imp of imports) {\n const key = `${imp.source}-${imp.importKind ?? ''}`\n let existing = merged.get(key)\n if (!existing) {\n existing = { ...imp, specifiers: [] }\n merged.set(key, existing)\n }\n\n const existingSpecs = existing.specifiers\n for (const specifier of imp.specifiers) {\n let found = false\n for (let i = 0; i < existingSpecs.length; i++) {\n const e = existingSpecs[i]!\n if (e.imported === specifier.imported && e.local === specifier.local) {\n found = true\n break\n }\n }\n if (!found) {\n existingSpecs.push(specifier)\n }\n }\n }\n\n return [...merged.values()]\n}\n\nexport const findParent = (node: RouteNode | undefined): string => {\n if (!node) {\n return `rootRouteImport`\n }\n if (node.parent) {\n return `${node.parent.variableName}Route`\n }\n return findParent(node.parent)\n}\n\nexport function buildFileRoutesByPathInterface(opts: {\n routeNodes: Array<RouteNode>\n module: string\n interfaceName: string\n config?: Pick<Config, 'routeToken'>\n}): string {\n return `declare module '${opts.module}' {\n interface ${opts.interfaceName} {\n ${opts.routeNodes\n .map((routeNode) => {\n const filePathId = routeNode.routePath\n const preloaderRoute = `typeof ${routeNode.variableName}RouteImport`\n\n const parent = findParent(routeNode)\n\n return `'${filePathId}': {\n id: '${filePathId}'\n path: '${inferPath(routeNode)}'\n fullPath: '${inferFullPath(routeNode)}'\n preLoaderRoute: ${preloaderRoute}\n parentRoute: typeof ${parent}\n }`\n })\n .join('\\n')}\n }\n}`\n}\n\nfunction getImportPath(\n node: RouteNode,\n config: Config,\n generatedRouteTreePath: string,\n): string {\n return replaceBackslash(\n removeExt(\n path.relative(\n path.dirname(generatedRouteTreePath),\n path.resolve(config.routesDirectory, node.filePath),\n ),\n config.addExtensions,\n ),\n )\n}\n\nexport function getImportForRouteNode(\n node: RouteNode,\n config: Config,\n generatedRouteTreePath: string,\n root: string,\n): ImportDeclaration {\n let source = ''\n if (config.importRoutesUsingAbsolutePaths) {\n source = replaceBackslash(\n removeExt(\n path.resolve(root, config.routesDirectory, node.filePath),\n config.addExtensions,\n ),\n )\n } else {\n source = `./${getImportPath(node, config, generatedRouteTreePath)}`\n }\n return {\n source,\n specifiers: [\n {\n imported: 'Route',\n local: `${node.variableName}RouteImport`,\n },\n ],\n } satisfies ImportDeclaration\n}\n"],"mappings":"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"} |
+0
-24
@@ -10,3 +10,2 @@ import { Config, TokenMatcher } from './config.cjs'; | ||
| private prefixToRoute; | ||
| private layoutRoutes; | ||
| constructor(routes: Array<RouteNode>); | ||
@@ -84,3 +83,2 @@ /** | ||
| }): RegExp; | ||
| export declare function isBracketWrappedSegment(segment: string): boolean; | ||
| export declare function unwrapBracketWrappedSegment(segment: string): string; | ||
@@ -166,14 +164,2 @@ export declare function removeLeadingUnderscores(s: string, routeToken: string): string; | ||
| /** | ||
| * Checks if a given RouteNode is valid for augmenting it with typing based on conditions. | ||
| * Also asserts that the RouteNode is defined. | ||
| * | ||
| * @param routeNode - The RouteNode to check. | ||
| * @returns A boolean indicating whether the RouteNode is defined. | ||
| */ | ||
| export declare function isRouteNodeValidForAugmentation(routeNode?: RouteNode): routeNode is RouteNode; | ||
| /** | ||
| * Infers the path for use by TS | ||
| */ | ||
| export declare const inferPath: (routeNode: RouteNode) => string; | ||
| /** | ||
| * Infers the full path for use by TS | ||
@@ -194,14 +180,5 @@ */ | ||
| export declare const createRouteNodesById: (routeNodes: Array<RouteNode>) => Map<string, RouteNode>; | ||
| /** | ||
| * Infers to path | ||
| */ | ||
| export declare const inferTo: (routeNode: RouteNode) => string; | ||
| /** | ||
| * Dedupes branches and index routes | ||
| */ | ||
| export declare const dedupeBranchesAndIndexRoutes: (routes: Array<RouteNode>) => Array<RouteNode>; | ||
| export declare function checkRouteFullPathUniqueness(_routes: Array<RouteNode>, config: Config): void; | ||
| export declare function buildRouteTreeConfig(nodes: Array<RouteNode>, disableTypes: boolean, depth?: number): Array<string>; | ||
| export declare function buildImportString(importDeclaration: ImportDeclaration): string; | ||
| export declare function lowerCaseFirstChar(value: string): string; | ||
| export declare function mergeImportDeclarations(imports: Array<ImportDeclaration>): Array<ImportDeclaration>; | ||
@@ -215,3 +192,2 @@ export declare const findParent: (node: RouteNode | undefined) => string; | ||
| }): string; | ||
| export declare function getImportPath(node: RouteNode, config: Config, generatedRouteTreePath: string): string; | ||
| export declare function getImportForRouteNode(node: RouteNode, config: Config, generatedRouteTreePath: string, root: string): ImportDeclaration; |
@@ -134,3 +134,2 @@ import { z } from 'zod'; | ||
| disableTypes: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>; | ||
| verboseFileRoutes: z.ZodOptional<z.ZodBoolean>; | ||
| addExtensions: z.ZodEffects<z.ZodDefault<z.ZodOptional<z.ZodUnion<[z.ZodBoolean, z.ZodString]>>>, string | boolean, string | boolean | undefined>; | ||
@@ -187,3 +186,2 @@ enableRouteTreeFormatting: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>; | ||
| pathParamsAllowedCharacters?: (":" | "$" | ";" | "@" | "&" | "=" | "+" | ",")[] | undefined; | ||
| verboseFileRoutes?: boolean | undefined; | ||
| routeTreeFileFooter?: string[] | ((...args: unknown[]) => string[]) | undefined; | ||
@@ -221,3 +219,2 @@ autoCodeSplitting?: boolean | undefined; | ||
| disableTypes?: boolean | undefined; | ||
| verboseFileRoutes?: boolean | undefined; | ||
| addExtensions?: string | boolean | undefined; | ||
@@ -224,0 +221,0 @@ enableRouteTreeFormatting?: boolean | undefined; |
@@ -50,3 +50,2 @@ import { virtualRootRouteSchema } from "./filesystem/virtual/config.js"; | ||
| disableTypes: z.boolean().optional().default(false), | ||
| verboseFileRoutes: z.boolean().optional(), | ||
| addExtensions: z.union([z.boolean(), z.string()]).optional().default(false).transform((v) => typeof v === "string" ? v.startsWith(".") ? v : `.${v}` : v), | ||
@@ -53,0 +52,0 @@ enableRouteTreeFormatting: z.boolean().optional().default(true), |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"config.js","names":[],"sources":["../../src/config.ts"],"sourcesContent":["import path from 'node:path'\nimport { existsSync, readFileSync } from 'node:fs'\nimport { z } from 'zod'\nimport { virtualRootRouteSchema } from './filesystem/virtual/config'\nimport type { GeneratorPlugin } from './plugin/types'\n\nconst tokenJsonRegexSchema = z.object({\n regex: z.string(),\n flags: z.string().optional(),\n})\n\nconst tokenMatcherSchema = z.union([\n z.string(),\n z.instanceof(RegExp),\n tokenJsonRegexSchema,\n])\n\nexport type TokenMatcherJson = string | z.infer<typeof tokenJsonRegexSchema>\n\nexport type TokenMatcher = z.infer<typeof tokenMatcherSchema>\n\nexport const baseConfigSchema = z.object({\n target: z.enum(['react', 'solid', 'vue']).optional().default('react'),\n virtualRouteConfig: virtualRootRouteSchema.or(z.string()).optional(),\n routeFilePrefix: z.string().optional(),\n routeFileIgnorePrefix: z.string().optional().default('-'),\n routeFileIgnorePattern: z.string().optional(),\n routesDirectory: z.string().optional().default('./src/routes'),\n quoteStyle: z.enum(['single', 'double']).optional().default('single'),\n semicolons: z.boolean().optional().default(false),\n disableLogging: z.boolean().optional().default(false),\n routeTreeFileHeader: z\n .array(z.string())\n .optional()\n .default([\n '/* eslint-disable */',\n '// @ts-nocheck',\n '// noinspection JSUnusedGlobalSymbols',\n ]),\n indexToken: tokenMatcherSchema.optional().default('index'),\n routeToken: tokenMatcherSchema.optional().default('route'),\n pathParamsAllowedCharacters: z\n .array(z.enum([';', ':', '@', '&', '=', '+', '$', ',']))\n .optional(),\n})\n\nexport type BaseConfig = z.infer<typeof baseConfigSchema>\n\nexport const configSchema = baseConfigSchema.extend({\n generatedRouteTree: z.string().optional().default('./src/routeTree.gen.ts'),\n disableTypes: z.boolean().optional().default(false),\n verboseFileRoutes: z.boolean().optional(),\n addExtensions: z\n .union([z.boolean(), z.string()])\n .optional()\n .default(false)\n .transform((v) =>\n typeof v === 'string' ? (v.startsWith('.') ? v : `.${v}`) : v,\n ),\n enableRouteTreeFormatting: z.boolean().optional().default(true),\n routeTreeFileFooter: z\n .union([\n z.array(z.string()).optional().default([]),\n z.function().returns(z.array(z.string())),\n ])\n .optional(),\n autoCodeSplitting: z.boolean().optional(),\n customScaffolding: z\n .object({\n routeTemplate: z.string().optional(),\n lazyRouteTemplate: z.string().optional(),\n })\n .optional(),\n experimental: z\n .object({\n // TODO: This has been made stable and is now \"autoCodeSplitting\". Remove in next major version.\n enableCodeSplitting: z.boolean().optional(),\n })\n .optional(),\n plugins: z.array(z.custom<GeneratorPlugin>()).optional(),\n tmpDir: z.string().optional().default(''),\n importRoutesUsingAbsolutePaths: z.boolean().optional().default(false),\n})\n\nexport type Config = z.infer<typeof configSchema>\n\ntype ResolveParams = {\n configDirectory: string\n}\n\nexport function resolveConfigPath({ configDirectory }: ResolveParams) {\n return path.resolve(configDirectory, 'tsr.config.json')\n}\n\nexport function getConfig(\n inlineConfig: Partial<Config> = {},\n configDirectory?: string,\n): Config {\n if (configDirectory === undefined) {\n configDirectory = process.cwd()\n }\n const configFilePathJson = resolveConfigPath({ configDirectory })\n const exists = existsSync(configFilePathJson)\n\n let config: Config\n\n if (exists) {\n // Parse file config (allows JSON regex-object form)\n const fileConfigRaw = JSON.parse(readFileSync(configFilePathJson, 'utf-8'))\n\n // Merge raw configs (inline overrides file), then parse once to apply defaults\n // This ensures file config values aren't overwritten by inline defaults\n const merged = {\n ...fileConfigRaw,\n ...inlineConfig,\n }\n config = configSchema.parse(merged)\n } else {\n config = configSchema.parse(inlineConfig)\n }\n\n // If typescript is disabled, make sure the generated route tree is a .js file\n if (config.disableTypes) {\n config.generatedRouteTree = config.generatedRouteTree.replace(\n /\\.(ts|tsx)$/,\n '.js',\n )\n }\n\n // if a configDirectory is used, paths should be relative to that directory\n if (configDirectory) {\n // if absolute configDirectory is provided, use it as the root\n if (path.isAbsolute(configDirectory)) {\n config.routesDirectory = path.resolve(\n configDirectory,\n config.routesDirectory,\n )\n config.generatedRouteTree = path.resolve(\n configDirectory,\n config.generatedRouteTree,\n )\n } else {\n config.routesDirectory = path.resolve(\n process.cwd(),\n configDirectory,\n config.routesDirectory,\n )\n config.generatedRouteTree = path.resolve(\n process.cwd(),\n configDirectory,\n config.generatedRouteTree,\n )\n }\n }\n\n const resolveTmpDir = (dir: string | Array<string>) => {\n if (Array.isArray(dir)) {\n dir = path.join(...dir)\n }\n if (!path.isAbsolute(dir)) {\n dir = path.resolve(process.cwd(), dir)\n }\n return dir\n }\n\n if (config.tmpDir) {\n config.tmpDir = resolveTmpDir(config.tmpDir)\n } else if (process.env.TSR_TMP_DIR) {\n config.tmpDir = resolveTmpDir(process.env.TSR_TMP_DIR)\n } else {\n config.tmpDir = resolveTmpDir(['.tanstack', 'tmp'])\n }\n\n validateConfig(config)\n return config\n}\n\nfunction validateConfig(config: Config) {\n if (typeof config.experimental?.enableCodeSplitting !== 'undefined') {\n const message = `\n------\n⚠️ ⚠️ ⚠️\nERROR: The \"experimental.enableCodeSplitting\" flag has been made stable and is now \"autoCodeSplitting\". Please update your configuration file to use \"autoCodeSplitting\" instead of \"experimental.enableCodeSplitting\".\n------\n`\n console.error(message)\n throw new Error(message)\n }\n\n // Check that indexToken and routeToken are not identical\n // Works for strings, RegExp, and JSON regex objects\n if (areTokensEqual(config.indexToken, config.routeToken)) {\n throw new Error(\n `The \"indexToken\" and \"routeToken\" options must be different.`,\n )\n }\n\n if (\n config.routeFileIgnorePrefix &&\n config.routeFileIgnorePrefix.trim() === '_'\n ) {\n throw new Error(\n `The \"routeFileIgnorePrefix\" cannot be an underscore (\"_\"). This is a reserved character used to denote a pathless route. Please use a different prefix.`,\n )\n }\n\n return config\n}\n\n/**\n * Compares two token matchers for equality.\n * Handles strings, RegExp instances, and JSON regex objects.\n */\nfunction areTokensEqual(a: TokenMatcher, b: TokenMatcher): boolean {\n // Both strings\n if (typeof a === 'string' && typeof b === 'string') {\n return a === b\n }\n\n // Both RegExp instances\n if (a instanceof RegExp && b instanceof RegExp) {\n return a.source === b.source && a.flags === b.flags\n }\n\n // Both JSON regex objects\n if (\n typeof a === 'object' &&\n 'regex' in a &&\n typeof b === 'object' &&\n 'regex' in b\n ) {\n return a.regex === b.regex && (a.flags ?? '') === (b.flags ?? '')\n }\n\n // Mixed types - not equal\n return false\n}\n"],"mappings":";;;;;AAMA,IAAM,uBAAuB,EAAE,OAAO;CACpC,OAAO,EAAE,QAAQ;CACjB,OAAO,EAAE,QAAQ,CAAC,UAAU;CAC7B,CAAC;AAEF,IAAM,qBAAqB,EAAE,MAAM;CACjC,EAAE,QAAQ;CACV,EAAE,WAAW,OAAO;CACpB;CACD,CAAC;AAMF,IAAa,mBAAmB,EAAE,OAAO;CACvC,QAAQ,EAAE,KAAK;EAAC;EAAS;EAAS;EAAM,CAAC,CAAC,UAAU,CAAC,QAAQ,QAAQ;CACrE,oBAAoB,uBAAuB,GAAG,EAAE,QAAQ,CAAC,CAAC,UAAU;CACpE,iBAAiB,EAAE,QAAQ,CAAC,UAAU;CACtC,uBAAuB,EAAE,QAAQ,CAAC,UAAU,CAAC,QAAQ,IAAI;CACzD,wBAAwB,EAAE,QAAQ,CAAC,UAAU;CAC7C,iBAAiB,EAAE,QAAQ,CAAC,UAAU,CAAC,QAAQ,eAAe;CAC9D,YAAY,EAAE,KAAK,CAAC,UAAU,SAAS,CAAC,CAAC,UAAU,CAAC,QAAQ,SAAS;CACrE,YAAY,EAAE,SAAS,CAAC,UAAU,CAAC,QAAQ,MAAM;CACjD,gBAAgB,EAAE,SAAS,CAAC,UAAU,CAAC,QAAQ,MAAM;CACrD,qBAAqB,EAClB,MAAM,EAAE,QAAQ,CAAC,CACjB,UAAU,CACV,QAAQ;EACP;EACA;EACA;EACD,CAAC;CACJ,YAAY,mBAAmB,UAAU,CAAC,QAAQ,QAAQ;CAC1D,YAAY,mBAAmB,UAAU,CAAC,QAAQ,QAAQ;CAC1D,6BAA6B,EAC1B,MAAM,EAAE,KAAK;EAAC;EAAK;EAAK;EAAK;EAAK;EAAK;EAAK;EAAK;EAAI,CAAC,CAAC,CACvD,UAAU;CACd,CAAC;AAIF,IAAa,eAAe,iBAAiB,OAAO;CAClD,oBAAoB,EAAE,QAAQ,CAAC,UAAU,CAAC,QAAQ,yBAAyB;CAC3E,cAAc,EAAE,SAAS,CAAC,UAAU,CAAC,QAAQ,MAAM;CACnD,mBAAmB,EAAE,SAAS,CAAC,UAAU;CACzC,eAAe,EACZ,MAAM,CAAC,EAAE,SAAS,EAAE,EAAE,QAAQ,CAAC,CAAC,CAChC,UAAU,CACV,QAAQ,MAAM,CACd,WAAW,MACV,OAAO,MAAM,WAAY,EAAE,WAAW,IAAI,GAAG,IAAI,IAAI,MAAO,EAC7D;CACH,2BAA2B,EAAE,SAAS,CAAC,UAAU,CAAC,QAAQ,KAAK;CAC/D,qBAAqB,EAClB,MAAM,CACL,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC,EAC1C,EAAE,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAC1C,CAAC,CACD,UAAU;CACb,mBAAmB,EAAE,SAAS,CAAC,UAAU;CACzC,mBAAmB,EAChB,OAAO;EACN,eAAe,EAAE,QAAQ,CAAC,UAAU;EACpC,mBAAmB,EAAE,QAAQ,CAAC,UAAU;EACzC,CAAC,CACD,UAAU;CACb,cAAc,EACX,OAAO,EAEN,qBAAqB,EAAE,SAAS,CAAC,UAAU,EAC5C,CAAC,CACD,UAAU;CACb,SAAS,EAAE,MAAM,EAAE,QAAyB,CAAC,CAAC,UAAU;CACxD,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,QAAQ,GAAG;CACzC,gCAAgC,EAAE,SAAS,CAAC,UAAU,CAAC,QAAQ,MAAM;CACtE,CAAC;AAQF,SAAgB,kBAAkB,EAAE,mBAAkC;AACpE,QAAO,KAAK,QAAQ,iBAAiB,kBAAkB;;AAGzD,SAAgB,UACd,eAAgC,EAAE,EAClC,iBACQ;AACR,KAAI,oBAAoB,KAAA,EACtB,mBAAkB,QAAQ,KAAK;CAEjC,MAAM,qBAAqB,kBAAkB,EAAE,iBAAiB,CAAC;CACjE,MAAM,SAAS,WAAW,mBAAmB;CAE7C,IAAI;AAEJ,KAAI,QAAQ;EAMV,MAAM,SAAS;GACb,GALoB,KAAK,MAAM,aAAa,oBAAoB,QAAQ,CAAC;GAMzE,GAAG;GACJ;AACD,WAAS,aAAa,MAAM,OAAO;OAEnC,UAAS,aAAa,MAAM,aAAa;AAI3C,KAAI,OAAO,aACT,QAAO,qBAAqB,OAAO,mBAAmB,QACpD,eACA,MACD;AAIH,KAAI,gBAEF,KAAI,KAAK,WAAW,gBAAgB,EAAE;AACpC,SAAO,kBAAkB,KAAK,QAC5B,iBACA,OAAO,gBACR;AACD,SAAO,qBAAqB,KAAK,QAC/B,iBACA,OAAO,mBACR;QACI;AACL,SAAO,kBAAkB,KAAK,QAC5B,QAAQ,KAAK,EACb,iBACA,OAAO,gBACR;AACD,SAAO,qBAAqB,KAAK,QAC/B,QAAQ,KAAK,EACb,iBACA,OAAO,mBACR;;CAIL,MAAM,iBAAiB,QAAgC;AACrD,MAAI,MAAM,QAAQ,IAAI,CACpB,OAAM,KAAK,KAAK,GAAG,IAAI;AAEzB,MAAI,CAAC,KAAK,WAAW,IAAI,CACvB,OAAM,KAAK,QAAQ,QAAQ,KAAK,EAAE,IAAI;AAExC,SAAO;;AAGT,KAAI,OAAO,OACT,QAAO,SAAS,cAAc,OAAO,OAAO;UACnC,QAAQ,IAAI,YACrB,QAAO,SAAS,cAAc,QAAQ,IAAI,YAAY;KAEtD,QAAO,SAAS,cAAc,CAAC,aAAa,MAAM,CAAC;AAGrD,gBAAe,OAAO;AACtB,QAAO;;AAGT,SAAS,eAAe,QAAgB;AACtC,KAAI,OAAO,OAAO,cAAc,wBAAwB,aAAa;EACnE,MAAM,UAAU;;;;;;AAMhB,UAAQ,MAAM,QAAQ;AACtB,QAAM,IAAI,MAAM,QAAQ;;AAK1B,KAAI,eAAe,OAAO,YAAY,OAAO,WAAW,CACtD,OAAM,IAAI,MACR,+DACD;AAGH,KACE,OAAO,yBACP,OAAO,sBAAsB,MAAM,KAAK,IAExC,OAAM,IAAI,MACR,0JACD;AAGH,QAAO;;;;;;AAOT,SAAS,eAAe,GAAiB,GAA0B;AAEjE,KAAI,OAAO,MAAM,YAAY,OAAO,MAAM,SACxC,QAAO,MAAM;AAIf,KAAI,aAAa,UAAU,aAAa,OACtC,QAAO,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE;AAIhD,KACE,OAAO,MAAM,YACb,WAAW,KACX,OAAO,MAAM,YACb,WAAW,EAEX,QAAO,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,SAAS,EAAE,SAAS;AAIhE,QAAO"} | ||
| {"version":3,"file":"config.js","names":[],"sources":["../../src/config.ts"],"sourcesContent":["import path from 'node:path'\nimport { existsSync, readFileSync } from 'node:fs'\nimport { z } from 'zod'\nimport { virtualRootRouteSchema } from './filesystem/virtual/config'\nimport type { GeneratorPlugin } from './plugin/types'\n\nconst tokenJsonRegexSchema = z.object({\n regex: z.string(),\n flags: z.string().optional(),\n})\n\nconst tokenMatcherSchema = z.union([\n z.string(),\n z.instanceof(RegExp),\n tokenJsonRegexSchema,\n])\n\nexport type TokenMatcherJson = string | z.infer<typeof tokenJsonRegexSchema>\n\nexport type TokenMatcher = z.infer<typeof tokenMatcherSchema>\n\nexport const baseConfigSchema = z.object({\n target: z.enum(['react', 'solid', 'vue']).optional().default('react'),\n virtualRouteConfig: virtualRootRouteSchema.or(z.string()).optional(),\n routeFilePrefix: z.string().optional(),\n routeFileIgnorePrefix: z.string().optional().default('-'),\n routeFileIgnorePattern: z.string().optional(),\n routesDirectory: z.string().optional().default('./src/routes'),\n quoteStyle: z.enum(['single', 'double']).optional().default('single'),\n semicolons: z.boolean().optional().default(false),\n disableLogging: z.boolean().optional().default(false),\n routeTreeFileHeader: z\n .array(z.string())\n .optional()\n .default([\n '/* eslint-disable */',\n '// @ts-nocheck',\n '// noinspection JSUnusedGlobalSymbols',\n ]),\n indexToken: tokenMatcherSchema.optional().default('index'),\n routeToken: tokenMatcherSchema.optional().default('route'),\n pathParamsAllowedCharacters: z\n .array(z.enum([';', ':', '@', '&', '=', '+', '$', ',']))\n .optional(),\n})\n\nexport type BaseConfig = z.infer<typeof baseConfigSchema>\n\nexport const configSchema = baseConfigSchema.extend({\n generatedRouteTree: z.string().optional().default('./src/routeTree.gen.ts'),\n disableTypes: z.boolean().optional().default(false),\n addExtensions: z\n .union([z.boolean(), z.string()])\n .optional()\n .default(false)\n .transform((v) =>\n typeof v === 'string' ? (v.startsWith('.') ? v : `.${v}`) : v,\n ),\n enableRouteTreeFormatting: z.boolean().optional().default(true),\n routeTreeFileFooter: z\n .union([\n z.array(z.string()).optional().default([]),\n z.function().returns(z.array(z.string())),\n ])\n .optional(),\n autoCodeSplitting: z.boolean().optional(),\n customScaffolding: z\n .object({\n routeTemplate: z.string().optional(),\n lazyRouteTemplate: z.string().optional(),\n })\n .optional(),\n experimental: z\n .object({\n // TODO: This has been made stable and is now \"autoCodeSplitting\". Remove in next major version.\n enableCodeSplitting: z.boolean().optional(),\n })\n .optional(),\n plugins: z.array(z.custom<GeneratorPlugin>()).optional(),\n tmpDir: z.string().optional().default(''),\n importRoutesUsingAbsolutePaths: z.boolean().optional().default(false),\n})\n\nexport type Config = z.infer<typeof configSchema>\n\ntype ResolveParams = {\n configDirectory: string\n}\n\nexport function resolveConfigPath({ configDirectory }: ResolveParams) {\n return path.resolve(configDirectory, 'tsr.config.json')\n}\n\nexport function getConfig(\n inlineConfig: Partial<Config> = {},\n configDirectory?: string,\n): Config {\n if (configDirectory === undefined) {\n configDirectory = process.cwd()\n }\n const configFilePathJson = resolveConfigPath({ configDirectory })\n const exists = existsSync(configFilePathJson)\n\n let config: Config\n\n if (exists) {\n // Parse file config (allows JSON regex-object form)\n const fileConfigRaw = JSON.parse(readFileSync(configFilePathJson, 'utf-8'))\n\n // Merge raw configs (inline overrides file), then parse once to apply defaults\n // This ensures file config values aren't overwritten by inline defaults\n const merged = {\n ...fileConfigRaw,\n ...inlineConfig,\n }\n config = configSchema.parse(merged)\n } else {\n config = configSchema.parse(inlineConfig)\n }\n\n // If typescript is disabled, make sure the generated route tree is a .js file\n if (config.disableTypes) {\n config.generatedRouteTree = config.generatedRouteTree.replace(\n /\\.(ts|tsx)$/,\n '.js',\n )\n }\n\n // if a configDirectory is used, paths should be relative to that directory\n if (configDirectory) {\n // if absolute configDirectory is provided, use it as the root\n if (path.isAbsolute(configDirectory)) {\n config.routesDirectory = path.resolve(\n configDirectory,\n config.routesDirectory,\n )\n config.generatedRouteTree = path.resolve(\n configDirectory,\n config.generatedRouteTree,\n )\n } else {\n config.routesDirectory = path.resolve(\n process.cwd(),\n configDirectory,\n config.routesDirectory,\n )\n config.generatedRouteTree = path.resolve(\n process.cwd(),\n configDirectory,\n config.generatedRouteTree,\n )\n }\n }\n\n const resolveTmpDir = (dir: string | Array<string>) => {\n if (Array.isArray(dir)) {\n dir = path.join(...dir)\n }\n if (!path.isAbsolute(dir)) {\n dir = path.resolve(process.cwd(), dir)\n }\n return dir\n }\n\n if (config.tmpDir) {\n config.tmpDir = resolveTmpDir(config.tmpDir)\n } else if (process.env.TSR_TMP_DIR) {\n config.tmpDir = resolveTmpDir(process.env.TSR_TMP_DIR)\n } else {\n config.tmpDir = resolveTmpDir(['.tanstack', 'tmp'])\n }\n\n validateConfig(config)\n return config\n}\n\nfunction validateConfig(config: Config) {\n if (typeof config.experimental?.enableCodeSplitting !== 'undefined') {\n const message = `\n------\n⚠️ ⚠️ ⚠️\nERROR: The \"experimental.enableCodeSplitting\" flag has been made stable and is now \"autoCodeSplitting\". Please update your configuration file to use \"autoCodeSplitting\" instead of \"experimental.enableCodeSplitting\".\n------\n`\n console.error(message)\n throw new Error(message)\n }\n\n // Check that indexToken and routeToken are not identical\n // Works for strings, RegExp, and JSON regex objects\n if (areTokensEqual(config.indexToken, config.routeToken)) {\n throw new Error(\n `The \"indexToken\" and \"routeToken\" options must be different.`,\n )\n }\n\n if (\n config.routeFileIgnorePrefix &&\n config.routeFileIgnorePrefix.trim() === '_'\n ) {\n throw new Error(\n `The \"routeFileIgnorePrefix\" cannot be an underscore (\"_\"). This is a reserved character used to denote a pathless route. Please use a different prefix.`,\n )\n }\n\n return config\n}\n\n/**\n * Compares two token matchers for equality.\n * Handles strings, RegExp instances, and JSON regex objects.\n */\nfunction areTokensEqual(a: TokenMatcher, b: TokenMatcher): boolean {\n // Both strings\n if (typeof a === 'string' && typeof b === 'string') {\n return a === b\n }\n\n // Both RegExp instances\n if (a instanceof RegExp && b instanceof RegExp) {\n return a.source === b.source && a.flags === b.flags\n }\n\n // Both JSON regex objects\n if (\n typeof a === 'object' &&\n 'regex' in a &&\n typeof b === 'object' &&\n 'regex' in b\n ) {\n return a.regex === b.regex && (a.flags ?? '') === (b.flags ?? '')\n }\n\n // Mixed types - not equal\n return false\n}\n"],"mappings":"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"} |
| import { logging } from "./logger.js"; | ||
| import { rootPathId } from "./filesystem/physical/rootPathId.js"; | ||
| import { RoutePrefixMap, buildFileRoutesByPathInterface, buildImportString, buildRouteTreeConfig, checkFileExists, checkRouteFullPathUniqueness, createRouteNodesByFullPath, createRouteNodesById, createRouteNodesByTo, createTokenRegex, determineNodePath, findParent, format, getImportForRouteNode, getImportPath, getResolvedRouteNodeVariableName, hasParentRoute, isRouteNodeValidForAugmentation, isSegmentPathless, mergeImportDeclarations, multiSortBy, removeExt, removeGroups, removeLastSegmentFromPath, removeLayoutSegmentsWithEscape, removeTrailingSlash, removeUnderscoresWithEscape, replaceBackslash, trimPathLeft } from "./utils.js"; | ||
| import { RoutePrefixMap, buildFileRoutesByPathInterface, buildImportString, buildRouteTreeConfig, checkFileExists, checkRouteFullPathUniqueness, createRouteNodesByFullPath, createRouteNodesById, createRouteNodesByTo, createTokenRegex, determineNodePath, findParent, format, getImportForRouteNode, getResolvedRouteNodeVariableName, hasParentRoute, isSegmentPathless, mergeImportDeclarations, multiSortBy, removeExt, removeGroups, removeLastSegmentFromPath, removeLayoutSegmentsWithEscape, removeTrailingSlash, removeUnderscoresWithEscape, replaceBackslash, trimPathLeft } from "./utils.js"; | ||
| import { getRouteNodes } from "./filesystem/virtual/getRouteNodes.js"; | ||
@@ -312,24 +312,2 @@ import { getRouteNodes as getRouteNodes$1, isVirtualConfigFile } from "./filesystem/physical/getRouteNodes.js"; | ||
| } | ||
| if (config.verboseFileRoutes === false) { | ||
| const typeImport = { | ||
| specifiers: [], | ||
| source: this.targetTemplate.fullPkg, | ||
| importKind: "type" | ||
| }; | ||
| let needsCreateFileRoute = false; | ||
| let needsCreateLazyFileRoute = false; | ||
| for (const node of sortedRouteNodes) { | ||
| if (isRouteNodeValidForAugmentation(node)) { | ||
| if (node._fsRouteType !== "lazy") needsCreateFileRoute = true; | ||
| if (acc.routePiecesByPath[node.routePath]?.lazy) needsCreateLazyFileRoute = true; | ||
| } | ||
| if (needsCreateFileRoute && needsCreateLazyFileRoute) break; | ||
| } | ||
| if (needsCreateFileRoute) typeImport.specifiers.push({ imported: "CreateFileRoute" }); | ||
| if (needsCreateLazyFileRoute) typeImport.specifiers.push({ imported: "CreateLazyFileRoute" }); | ||
| if (typeImport.specifiers.length > 0) { | ||
| typeImport.specifiers.push({ imported: "FileRoutesByPath" }); | ||
| imports.push(typeImport); | ||
| } | ||
| } | ||
| const routeTreeConfig = buildRouteTreeConfig(acc.routeTree, config.disableTypes); | ||
@@ -442,21 +420,2 @@ const createUpdateRoutes = sortedRouteNodes.map((node) => { | ||
| const importStatements = mergedImports.map(buildImportString); | ||
| let moduleAugmentation = ""; | ||
| if (config.verboseFileRoutes === false && !config.disableTypes) moduleAugmentation = opts.routeFileResult.map((node) => { | ||
| const getModuleDeclaration = (routeNode) => { | ||
| if (!isRouteNodeValidForAugmentation(routeNode)) return ""; | ||
| let moduleAugmentation = ""; | ||
| if (routeNode._fsRouteType === "lazy") moduleAugmentation = `const createLazyFileRoute: CreateLazyFileRoute<FileRoutesByPath['${routeNode.routePath}']['preLoaderRoute']>`; | ||
| else moduleAugmentation = `const createFileRoute: CreateFileRoute<'${routeNode.routePath}', | ||
| FileRoutesByPath['${routeNode.routePath}']['parentRoute'], | ||
| FileRoutesByPath['${routeNode.routePath}']['id'], | ||
| FileRoutesByPath['${routeNode.routePath}']['path'], | ||
| FileRoutesByPath['${routeNode.routePath}']['fullPath'] | ||
| > | ||
| `; | ||
| return `declare module './${getImportPath(routeNode, config, this.generatedRouteTreePath)}' { | ||
| ${moduleAugmentation} | ||
| }`; | ||
| }; | ||
| return getModuleDeclaration(node); | ||
| }).join("\n"); | ||
| const rootRouteImport = getImportForRouteNode(rootRouteNode, config, this.generatedRouteTreePath, this.root); | ||
@@ -479,3 +438,2 @@ routeImports.unshift(rootRouteImport); | ||
| fileRoutesByPathInterface, | ||
| moduleAugmentation, | ||
| routeTreeConfig.join("\n"), | ||
@@ -542,4 +500,3 @@ routeTree, | ||
| routeId: escapedRoutePath, | ||
| lazy: node._fsRouteType === "lazy", | ||
| verboseFileRoutes: !(this.config.verboseFileRoutes === false) | ||
| lazy: node._fsRouteType === "lazy" | ||
| }, | ||
@@ -546,0 +503,0 @@ node |
@@ -11,3 +11,2 @@ export { configSchema, getConfig, resolveConfigPath, baseConfigSchema, } from './config.js'; | ||
| export { rootPathId } from './filesystem/physical/rootPathId.js'; | ||
| export { ensureStringArgument } from './transform/utils.js'; | ||
| export type { TransformImportsConfig, TransformContext, TransformOptions, } from './transform/types.js'; | ||
| export type { TransformContext, TransformOptions } from './transform/types.js'; |
@@ -6,4 +6,3 @@ import { baseConfigSchema, configSchema, getConfig, resolveConfigPath } from "./config.js"; | ||
| import { getRouteNodes } from "./filesystem/physical/getRouteNodes.js"; | ||
| import { ensureStringArgument } from "./transform/utils.js"; | ||
| import { Generator } from "./generator.js"; | ||
| export { Generator, baseConfigSchema, capitalize, checkRouteFullPathUniqueness, cleanPath, configSchema, determineInitialRoutePath, ensureStringArgument, format, getConfig, inferFullPath, multiSortBy, getRouteNodes as physicalGetRouteNodes, removeExt, removeLeadingSlash, removeTrailingSlash, removeUnderscores, replaceBackslash, resetRegex, resolveConfigPath, rootPathId, routePathToVariable, trimPathLeft, getRouteNodes$1 as virtualGetRouteNodes, writeIfDifferent }; | ||
| export { Generator, baseConfigSchema, capitalize, checkRouteFullPathUniqueness, cleanPath, configSchema, determineInitialRoutePath, format, getConfig, inferFullPath, multiSortBy, getRouteNodes as physicalGetRouteNodes, removeExt, removeLeadingSlash, removeTrailingSlash, removeUnderscores, replaceBackslash, resetRegex, resolveConfigPath, rootPathId, routePathToVariable, trimPathLeft, getRouteNodes$1 as virtualGetRouteNodes, writeIfDifferent }; |
+15
-12
@@ -6,2 +6,5 @@ import { format } from "./utils.js"; | ||
| } | ||
| function serializeRoutePath(routePath) { | ||
| return JSON.stringify(routePath); | ||
| } | ||
| function getTargetTemplate(config) { | ||
@@ -35,4 +38,4 @@ const target = config.target; | ||
| imports: { | ||
| tsrImports: () => config.verboseFileRoutes === false ? "" : "import { createFileRoute } from '@tanstack/react-router';", | ||
| tsrExportStart: (routePath) => config.verboseFileRoutes === false ? "export const Route = createFileRoute(" : `export const Route = createFileRoute('${routePath}')(`, | ||
| tsrImports: () => "import { createFileRoute } from '@tanstack/react-router';", | ||
| tsrExportStart: (routePath) => `export const Route = createFileRoute(${serializeRoutePath(routePath)})(`, | ||
| tsrExportEnd: () => ");" | ||
@@ -49,4 +52,4 @@ } | ||
| imports: { | ||
| tsrImports: () => config.verboseFileRoutes === false ? "" : "import { createLazyFileRoute } from '@tanstack/react-router';", | ||
| tsrExportStart: (routePath) => config.verboseFileRoutes === false ? "export const Route = createLazyFileRoute(" : `export const Route = createLazyFileRoute('${routePath}')(`, | ||
| tsrImports: () => "import { createLazyFileRoute } from '@tanstack/react-router';", | ||
| tsrExportStart: (routePath) => `export const Route = createLazyFileRoute(${serializeRoutePath(routePath)})(`, | ||
| tsrExportEnd: () => ");" | ||
@@ -81,4 +84,4 @@ } | ||
| imports: { | ||
| tsrImports: () => config.verboseFileRoutes === false ? "" : "import { createFileRoute } from '@tanstack/solid-router';", | ||
| tsrExportStart: (routePath) => config.verboseFileRoutes === false ? "export const Route = createFileRoute(" : `export const Route = createFileRoute('${routePath}')(`, | ||
| tsrImports: () => "import { createFileRoute } from '@tanstack/solid-router';", | ||
| tsrExportStart: (routePath) => `export const Route = createFileRoute(${serializeRoutePath(routePath)})(`, | ||
| tsrExportEnd: () => ");" | ||
@@ -95,4 +98,4 @@ } | ||
| imports: { | ||
| tsrImports: () => config.verboseFileRoutes === false ? "" : "import { createLazyFileRoute } from '@tanstack/solid-router';", | ||
| tsrExportStart: (routePath) => config.verboseFileRoutes === false ? "export const Route = createLazyFileRoute(" : `export const Route = createLazyFileRoute('${routePath}')(`, | ||
| tsrImports: () => "import { createLazyFileRoute } from '@tanstack/solid-router';", | ||
| tsrExportStart: (routePath) => `export const Route = createLazyFileRoute(${serializeRoutePath(routePath)})(`, | ||
| tsrExportEnd: () => ");" | ||
@@ -128,4 +131,4 @@ } | ||
| imports: { | ||
| tsrImports: () => config.verboseFileRoutes === false ? "" : "import { createFileRoute } from '@tanstack/vue-router';", | ||
| tsrExportStart: (routePath) => config.verboseFileRoutes === false ? "export const Route = createFileRoute(" : `export const Route = createFileRoute('${routePath}')(`, | ||
| tsrImports: () => "import { createFileRoute } from '@tanstack/vue-router';", | ||
| tsrExportStart: (routePath) => `export const Route = createFileRoute(${serializeRoutePath(routePath)})(`, | ||
| tsrExportEnd: () => ");" | ||
@@ -143,4 +146,4 @@ } | ||
| imports: { | ||
| tsrImports: () => config.verboseFileRoutes === false ? "" : "import { createLazyFileRoute } from '@tanstack/vue-router';", | ||
| tsrExportStart: (routePath) => config.verboseFileRoutes === false ? "export const Route = createLazyFileRoute(" : `export const Route = createLazyFileRoute('${routePath}')(`, | ||
| tsrImports: () => "import { createLazyFileRoute } from '@tanstack/vue-router';", | ||
| tsrExportStart: (routePath) => `export const Route = createLazyFileRoute(${serializeRoutePath(routePath)})(`, | ||
| tsrExportEnd: () => ");" | ||
@@ -147,0 +150,0 @@ } |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"template.js","names":[],"sources":["../../src/template.ts"],"sourcesContent":["import { format } from './utils'\nimport type { Config } from './config'\n\ntype TemplateTag = 'tsrImports' | 'tsrPath' | 'tsrExportStart' | 'tsrExportEnd'\n\nexport function fillTemplate(\n config: Config,\n template: string,\n values: Record<TemplateTag, string>,\n) {\n const replaced = template.replace(\n /%%(\\w+)%%/g,\n (_, key) => values[key as TemplateTag] || '',\n )\n return format(replaced, config)\n}\n\nexport type TargetTemplate = {\n fullPkg: string\n subPkg: string\n rootRoute: {\n template: () => string\n imports: {\n tsrImports: () => string\n tsrExportStart: () => string\n tsrExportEnd: () => string\n }\n }\n route: {\n template: () => string\n imports: {\n tsrImports: () => string\n tsrExportStart: (routePath: string) => string\n tsrExportEnd: () => string\n }\n }\n lazyRoute: {\n template: () => string\n imports: {\n tsrImports: () => string\n tsrExportStart: (routePath: string) => string\n tsrExportEnd: () => string\n }\n }\n}\n\nexport function getTargetTemplate(config: Config): TargetTemplate {\n const target = config.target\n switch (target) {\n case 'react':\n return {\n fullPkg: '@tanstack/react-router',\n subPkg: 'react-router',\n rootRoute: {\n template: () =>\n [\n 'import * as React from \"react\"\\n',\n '%%tsrImports%%',\n '\\n\\n',\n '%%tsrExportStart%%{\\n component: RootComponent\\n }%%tsrExportEnd%%\\n\\n',\n 'function RootComponent() { return (<React.Fragment><div>Hello \"%%tsrPath%%\"!</div><Outlet /></React.Fragment>) };\\n',\n ].join(''),\n imports: {\n tsrImports: () =>\n \"import { Outlet, createRootRoute } from '@tanstack/react-router';\",\n tsrExportStart: () => 'export const Route = createRootRoute(',\n tsrExportEnd: () => ');',\n },\n },\n route: {\n template: () =>\n [\n '%%tsrImports%%',\n '\\n\\n',\n '%%tsrExportStart%%{\\n component: RouteComponent\\n }%%tsrExportEnd%%\\n\\n',\n 'function RouteComponent() { return <div>Hello \"%%tsrPath%%\"!</div> };\\n',\n ].join(''),\n imports: {\n tsrImports: () =>\n config.verboseFileRoutes === false\n ? ''\n : \"import { createFileRoute } from '@tanstack/react-router';\",\n tsrExportStart: (routePath) =>\n config.verboseFileRoutes === false\n ? 'export const Route = createFileRoute('\n : `export const Route = createFileRoute('${routePath}')(`,\n tsrExportEnd: () => ');',\n },\n },\n lazyRoute: {\n template: () =>\n [\n '%%tsrImports%%',\n '\\n\\n',\n '%%tsrExportStart%%{\\n component: RouteComponent\\n }%%tsrExportEnd%%\\n\\n',\n 'function RouteComponent() { return <div>Hello \"%%tsrPath%%\"!</div> };\\n',\n ].join(''),\n imports: {\n tsrImports: () =>\n config.verboseFileRoutes === false\n ? ''\n : \"import { createLazyFileRoute } from '@tanstack/react-router';\",\n tsrExportStart: (routePath) =>\n config.verboseFileRoutes === false\n ? 'export const Route = createLazyFileRoute('\n : `export const Route = createLazyFileRoute('${routePath}')(`,\n tsrExportEnd: () => ');',\n },\n },\n }\n case 'solid':\n return {\n fullPkg: '@tanstack/solid-router',\n subPkg: 'solid-router',\n rootRoute: {\n template: () =>\n [\n 'import * as Solid from \"solid-js\"\\n',\n '%%tsrImports%%',\n '\\n\\n',\n '%%tsrExportStart%%{\\n component: RootComponent\\n }%%tsrExportEnd%%\\n\\n',\n 'function RootComponent() { return (<><div>Hello \"%%tsrPath%%\"!</div><Outlet /></>) };\\n',\n ].join(''),\n imports: {\n tsrImports: () =>\n \"import { Outlet, createRootRoute } from '@tanstack/solid-router';\",\n tsrExportStart: () => 'export const Route = createRootRoute(',\n tsrExportEnd: () => ');',\n },\n },\n route: {\n template: () =>\n [\n '%%tsrImports%%',\n '\\n\\n',\n '%%tsrExportStart%%{\\n component: RouteComponent\\n }%%tsrExportEnd%%\\n\\n',\n 'function RouteComponent() { return <div>Hello \"%%tsrPath%%\"!</div> };\\n',\n ].join(''),\n imports: {\n tsrImports: () =>\n config.verboseFileRoutes === false\n ? ''\n : \"import { createFileRoute } from '@tanstack/solid-router';\",\n tsrExportStart: (routePath) =>\n config.verboseFileRoutes === false\n ? 'export const Route = createFileRoute('\n : `export const Route = createFileRoute('${routePath}')(`,\n tsrExportEnd: () => ');',\n },\n },\n lazyRoute: {\n template: () =>\n [\n '%%tsrImports%%',\n '\\n\\n',\n '%%tsrExportStart%%{\\n component: RouteComponent\\n }%%tsrExportEnd%%\\n\\n',\n 'function RouteComponent() { return <div>Hello \"%%tsrPath%%\"!</div> };\\n',\n ].join(''),\n imports: {\n tsrImports: () =>\n config.verboseFileRoutes === false\n ? ''\n : \"import { createLazyFileRoute } from '@tanstack/solid-router';\",\n\n tsrExportStart: (routePath) =>\n config.verboseFileRoutes === false\n ? 'export const Route = createLazyFileRoute('\n : `export const Route = createLazyFileRoute('${routePath}')(`,\n\n tsrExportEnd: () => ');',\n },\n },\n }\n case 'vue':\n return {\n fullPkg: '@tanstack/vue-router',\n subPkg: 'vue-router',\n rootRoute: {\n template: () =>\n [\n 'import { h } from \"vue\"\\n',\n '%%tsrImports%%',\n '\\n\\n',\n '%%tsrExportStart%%{\\n component: RootComponent\\n }%%tsrExportEnd%%\\n\\n',\n 'function RootComponent() { return h(\"div\", {}, [\"Hello \\\\\"%%tsrPath%%\\\\\"!\", h(Outlet)]) };\\n',\n ].join(''),\n imports: {\n tsrImports: () =>\n \"import { Outlet, createRootRoute } from '@tanstack/vue-router';\",\n tsrExportStart: () => 'export const Route = createRootRoute(',\n tsrExportEnd: () => ');',\n },\n },\n route: {\n template: () =>\n [\n 'import { h } from \"vue\"\\n',\n '%%tsrImports%%',\n '\\n\\n',\n '%%tsrExportStart%%{\\n component: RouteComponent\\n }%%tsrExportEnd%%\\n\\n',\n 'function RouteComponent() { return h(\"div\", {}, \"Hello \\\\\"%%tsrPath%%\\\\\"!\") };\\n',\n ].join(''),\n imports: {\n tsrImports: () =>\n config.verboseFileRoutes === false\n ? ''\n : \"import { createFileRoute } from '@tanstack/vue-router';\",\n tsrExportStart: (routePath) =>\n config.verboseFileRoutes === false\n ? 'export const Route = createFileRoute('\n : `export const Route = createFileRoute('${routePath}')(`,\n tsrExportEnd: () => ');',\n },\n },\n lazyRoute: {\n template: () =>\n [\n 'import { h } from \"vue\"\\n',\n '%%tsrImports%%',\n '\\n\\n',\n '%%tsrExportStart%%{\\n component: RouteComponent\\n }%%tsrExportEnd%%\\n\\n',\n 'function RouteComponent() { return h(\"div\", {}, \"Hello \\\\\"%%tsrPath%%\\\\\"!\") };\\n',\n ].join(''),\n imports: {\n tsrImports: () =>\n config.verboseFileRoutes === false\n ? ''\n : \"import { createLazyFileRoute } from '@tanstack/vue-router';\",\n\n tsrExportStart: (routePath) =>\n config.verboseFileRoutes === false\n ? 'export const Route = createLazyFileRoute('\n : `export const Route = createLazyFileRoute('${routePath}')(`,\n\n tsrExportEnd: () => ');',\n },\n },\n }\n default:\n throw new Error(`router-generator: Unknown target type: ${target}`)\n }\n}\n"],"mappings":";;AAKA,SAAgB,aACd,QACA,UACA,QACA;AAKA,QAAO,OAJU,SAAS,QACxB,eACC,GAAG,QAAQ,OAAO,QAAuB,GAC3C,EACuB,OAAO;;AAgCjC,SAAgB,kBAAkB,QAAgC;CAChE,MAAM,SAAS,OAAO;AACtB,SAAQ,QAAR;EACE,KAAK,QACH,QAAO;GACL,SAAS;GACT,QAAQ;GACR,WAAW;IACT,gBACE;KACE;KACA;KACA;KACA;KACA;KACD,CAAC,KAAK,GAAG;IACZ,SAAS;KACP,kBACE;KACF,sBAAsB;KACtB,oBAAoB;KACrB;IACF;GACD,OAAO;IACL,gBACE;KACE;KACA;KACA;KACA;KACD,CAAC,KAAK,GAAG;IACZ,SAAS;KACP,kBACE,OAAO,sBAAsB,QACzB,KACA;KACN,iBAAiB,cACf,OAAO,sBAAsB,QACzB,0CACA,yCAAyC,UAAU;KACzD,oBAAoB;KACrB;IACF;GACD,WAAW;IACT,gBACE;KACE;KACA;KACA;KACA;KACD,CAAC,KAAK,GAAG;IACZ,SAAS;KACP,kBACE,OAAO,sBAAsB,QACzB,KACA;KACN,iBAAiB,cACf,OAAO,sBAAsB,QACzB,8CACA,6CAA6C,UAAU;KAC7D,oBAAoB;KACrB;IACF;GACF;EACH,KAAK,QACH,QAAO;GACL,SAAS;GACT,QAAQ;GACR,WAAW;IACT,gBACE;KACE;KACA;KACA;KACA;KACA;KACD,CAAC,KAAK,GAAG;IACZ,SAAS;KACP,kBACE;KACF,sBAAsB;KACtB,oBAAoB;KACrB;IACF;GACD,OAAO;IACL,gBACE;KACE;KACA;KACA;KACA;KACD,CAAC,KAAK,GAAG;IACZ,SAAS;KACP,kBACE,OAAO,sBAAsB,QACzB,KACA;KACN,iBAAiB,cACf,OAAO,sBAAsB,QACzB,0CACA,yCAAyC,UAAU;KACzD,oBAAoB;KACrB;IACF;GACD,WAAW;IACT,gBACE;KACE;KACA;KACA;KACA;KACD,CAAC,KAAK,GAAG;IACZ,SAAS;KACP,kBACE,OAAO,sBAAsB,QACzB,KACA;KAEN,iBAAiB,cACf,OAAO,sBAAsB,QACzB,8CACA,6CAA6C,UAAU;KAE7D,oBAAoB;KACrB;IACF;GACF;EACH,KAAK,MACH,QAAO;GACL,SAAS;GACT,QAAQ;GACR,WAAW;IACT,gBACE;KACE;KACA;KACA;KACA;KACA;KACD,CAAC,KAAK,GAAG;IACZ,SAAS;KACP,kBACE;KACF,sBAAsB;KACtB,oBAAoB;KACrB;IACF;GACD,OAAO;IACL,gBACE;KACE;KACA;KACA;KACA;KACA;KACD,CAAC,KAAK,GAAG;IACZ,SAAS;KACP,kBACE,OAAO,sBAAsB,QACzB,KACA;KACN,iBAAiB,cACf,OAAO,sBAAsB,QACzB,0CACA,yCAAyC,UAAU;KACzD,oBAAoB;KACrB;IACF;GACD,WAAW;IACT,gBACE;KACE;KACA;KACA;KACA;KACA;KACD,CAAC,KAAK,GAAG;IACZ,SAAS;KACP,kBACE,OAAO,sBAAsB,QACzB,KACA;KAEN,iBAAiB,cACf,OAAO,sBAAsB,QACzB,8CACA,6CAA6C,UAAU;KAE7D,oBAAoB;KACrB;IACF;GACF;EACH,QACE,OAAM,IAAI,MAAM,0CAA0C,SAAS"} | ||
| {"version":3,"file":"template.js","names":[],"sources":["../../src/template.ts"],"sourcesContent":["import { format } from './utils'\nimport type { Config } from './config'\n\ntype TemplateTag = 'tsrImports' | 'tsrPath' | 'tsrExportStart' | 'tsrExportEnd'\n\nexport function fillTemplate(\n config: Config,\n template: string,\n values: Record<TemplateTag, string>,\n) {\n const replaced = template.replace(\n /%%(\\w+)%%/g,\n (_, key) => values[key as TemplateTag] || '',\n )\n return format(replaced, config)\n}\n\nexport type TargetTemplate = {\n fullPkg: string\n subPkg: string\n rootRoute: {\n template: () => string\n imports: {\n tsrImports: () => string\n tsrExportStart: () => string\n tsrExportEnd: () => string\n }\n }\n route: {\n template: () => string\n imports: {\n tsrImports: () => string\n tsrExportStart: (routePath: string) => string\n tsrExportEnd: () => string\n }\n }\n lazyRoute: {\n template: () => string\n imports: {\n tsrImports: () => string\n tsrExportStart: (routePath: string) => string\n tsrExportEnd: () => string\n }\n }\n}\n\nfunction serializeRoutePath(routePath: string) {\n return JSON.stringify(routePath)\n}\n\nexport function getTargetTemplate(config: Config): TargetTemplate {\n const target = config.target\n switch (target) {\n case 'react':\n return {\n fullPkg: '@tanstack/react-router',\n subPkg: 'react-router',\n rootRoute: {\n template: () =>\n [\n 'import * as React from \"react\"\\n',\n '%%tsrImports%%',\n '\\n\\n',\n '%%tsrExportStart%%{\\n component: RootComponent\\n }%%tsrExportEnd%%\\n\\n',\n 'function RootComponent() { return (<React.Fragment><div>Hello \"%%tsrPath%%\"!</div><Outlet /></React.Fragment>) };\\n',\n ].join(''),\n imports: {\n tsrImports: () =>\n \"import { Outlet, createRootRoute } from '@tanstack/react-router';\",\n tsrExportStart: () => 'export const Route = createRootRoute(',\n tsrExportEnd: () => ');',\n },\n },\n route: {\n template: () =>\n [\n '%%tsrImports%%',\n '\\n\\n',\n '%%tsrExportStart%%{\\n component: RouteComponent\\n }%%tsrExportEnd%%\\n\\n',\n 'function RouteComponent() { return <div>Hello \"%%tsrPath%%\"!</div> };\\n',\n ].join(''),\n imports: {\n tsrImports: () =>\n \"import { createFileRoute } from '@tanstack/react-router';\",\n tsrExportStart: (routePath) =>\n `export const Route = createFileRoute(${serializeRoutePath(routePath)})(`,\n tsrExportEnd: () => ');',\n },\n },\n lazyRoute: {\n template: () =>\n [\n '%%tsrImports%%',\n '\\n\\n',\n '%%tsrExportStart%%{\\n component: RouteComponent\\n }%%tsrExportEnd%%\\n\\n',\n 'function RouteComponent() { return <div>Hello \"%%tsrPath%%\"!</div> };\\n',\n ].join(''),\n imports: {\n tsrImports: () =>\n \"import { createLazyFileRoute } from '@tanstack/react-router';\",\n tsrExportStart: (routePath) =>\n `export const Route = createLazyFileRoute(${serializeRoutePath(routePath)})(`,\n tsrExportEnd: () => ');',\n },\n },\n }\n case 'solid':\n return {\n fullPkg: '@tanstack/solid-router',\n subPkg: 'solid-router',\n rootRoute: {\n template: () =>\n [\n 'import * as Solid from \"solid-js\"\\n',\n '%%tsrImports%%',\n '\\n\\n',\n '%%tsrExportStart%%{\\n component: RootComponent\\n }%%tsrExportEnd%%\\n\\n',\n 'function RootComponent() { return (<><div>Hello \"%%tsrPath%%\"!</div><Outlet /></>) };\\n',\n ].join(''),\n imports: {\n tsrImports: () =>\n \"import { Outlet, createRootRoute } from '@tanstack/solid-router';\",\n tsrExportStart: () => 'export const Route = createRootRoute(',\n tsrExportEnd: () => ');',\n },\n },\n route: {\n template: () =>\n [\n '%%tsrImports%%',\n '\\n\\n',\n '%%tsrExportStart%%{\\n component: RouteComponent\\n }%%tsrExportEnd%%\\n\\n',\n 'function RouteComponent() { return <div>Hello \"%%tsrPath%%\"!</div> };\\n',\n ].join(''),\n imports: {\n tsrImports: () =>\n \"import { createFileRoute } from '@tanstack/solid-router';\",\n tsrExportStart: (routePath) =>\n `export const Route = createFileRoute(${serializeRoutePath(routePath)})(`,\n tsrExportEnd: () => ');',\n },\n },\n lazyRoute: {\n template: () =>\n [\n '%%tsrImports%%',\n '\\n\\n',\n '%%tsrExportStart%%{\\n component: RouteComponent\\n }%%tsrExportEnd%%\\n\\n',\n 'function RouteComponent() { return <div>Hello \"%%tsrPath%%\"!</div> };\\n',\n ].join(''),\n imports: {\n tsrImports: () =>\n \"import { createLazyFileRoute } from '@tanstack/solid-router';\",\n\n tsrExportStart: (routePath) =>\n `export const Route = createLazyFileRoute(${serializeRoutePath(routePath)})(`,\n\n tsrExportEnd: () => ');',\n },\n },\n }\n case 'vue':\n return {\n fullPkg: '@tanstack/vue-router',\n subPkg: 'vue-router',\n rootRoute: {\n template: () =>\n [\n 'import { h } from \"vue\"\\n',\n '%%tsrImports%%',\n '\\n\\n',\n '%%tsrExportStart%%{\\n component: RootComponent\\n }%%tsrExportEnd%%\\n\\n',\n 'function RootComponent() { return h(\"div\", {}, [\"Hello \\\\\"%%tsrPath%%\\\\\"!\", h(Outlet)]) };\\n',\n ].join(''),\n imports: {\n tsrImports: () =>\n \"import { Outlet, createRootRoute } from '@tanstack/vue-router';\",\n tsrExportStart: () => 'export const Route = createRootRoute(',\n tsrExportEnd: () => ');',\n },\n },\n route: {\n template: () =>\n [\n 'import { h } from \"vue\"\\n',\n '%%tsrImports%%',\n '\\n\\n',\n '%%tsrExportStart%%{\\n component: RouteComponent\\n }%%tsrExportEnd%%\\n\\n',\n 'function RouteComponent() { return h(\"div\", {}, \"Hello \\\\\"%%tsrPath%%\\\\\"!\") };\\n',\n ].join(''),\n imports: {\n tsrImports: () =>\n \"import { createFileRoute } from '@tanstack/vue-router';\",\n tsrExportStart: (routePath) =>\n `export const Route = createFileRoute(${serializeRoutePath(routePath)})(`,\n tsrExportEnd: () => ');',\n },\n },\n lazyRoute: {\n template: () =>\n [\n 'import { h } from \"vue\"\\n',\n '%%tsrImports%%',\n '\\n\\n',\n '%%tsrExportStart%%{\\n component: RouteComponent\\n }%%tsrExportEnd%%\\n\\n',\n 'function RouteComponent() { return h(\"div\", {}, \"Hello \\\\\"%%tsrPath%%\\\\\"!\") };\\n',\n ].join(''),\n imports: {\n tsrImports: () =>\n \"import { createLazyFileRoute } from '@tanstack/vue-router';\",\n\n tsrExportStart: (routePath) =>\n `export const Route = createLazyFileRoute(${serializeRoutePath(routePath)})(`,\n\n tsrExportEnd: () => ');',\n },\n },\n }\n default:\n throw new Error(`router-generator: Unknown target type: ${target}`)\n }\n}\n"],"mappings":";;AAKA,SAAgB,aACd,QACA,UACA,QACA;AAKA,QAAO,OAJU,SAAS,QACxB,eACC,GAAG,QAAQ,OAAO,QAAuB,GAC3C,EACuB,OAAO;;AAgCjC,SAAS,mBAAmB,WAAmB;AAC7C,QAAO,KAAK,UAAU,UAAU;;AAGlC,SAAgB,kBAAkB,QAAgC;CAChE,MAAM,SAAS,OAAO;AACtB,SAAQ,QAAR;EACE,KAAK,QACH,QAAO;GACL,SAAS;GACT,QAAQ;GACR,WAAW;IACT,gBACE;KACE;KACA;KACA;KACA;KACA;KACD,CAAC,KAAK,GAAG;IACZ,SAAS;KACP,kBACE;KACF,sBAAsB;KACtB,oBAAoB;KACrB;IACF;GACD,OAAO;IACL,gBACE;KACE;KACA;KACA;KACA;KACD,CAAC,KAAK,GAAG;IACZ,SAAS;KACP,kBACE;KACF,iBAAiB,cACf,wCAAwC,mBAAmB,UAAU,CAAC;KACxE,oBAAoB;KACrB;IACF;GACD,WAAW;IACT,gBACE;KACE;KACA;KACA;KACA;KACD,CAAC,KAAK,GAAG;IACZ,SAAS;KACP,kBACE;KACF,iBAAiB,cACf,4CAA4C,mBAAmB,UAAU,CAAC;KAC5E,oBAAoB;KACrB;IACF;GACF;EACH,KAAK,QACH,QAAO;GACL,SAAS;GACT,QAAQ;GACR,WAAW;IACT,gBACE;KACE;KACA;KACA;KACA;KACA;KACD,CAAC,KAAK,GAAG;IACZ,SAAS;KACP,kBACE;KACF,sBAAsB;KACtB,oBAAoB;KACrB;IACF;GACD,OAAO;IACL,gBACE;KACE;KACA;KACA;KACA;KACD,CAAC,KAAK,GAAG;IACZ,SAAS;KACP,kBACE;KACF,iBAAiB,cACf,wCAAwC,mBAAmB,UAAU,CAAC;KACxE,oBAAoB;KACrB;IACF;GACD,WAAW;IACT,gBACE;KACE;KACA;KACA;KACA;KACD,CAAC,KAAK,GAAG;IACZ,SAAS;KACP,kBACE;KAEF,iBAAiB,cACf,4CAA4C,mBAAmB,UAAU,CAAC;KAE5E,oBAAoB;KACrB;IACF;GACF;EACH,KAAK,MACH,QAAO;GACL,SAAS;GACT,QAAQ;GACR,WAAW;IACT,gBACE;KACE;KACA;KACA;KACA;KACA;KACD,CAAC,KAAK,GAAG;IACZ,SAAS;KACP,kBACE;KACF,sBAAsB;KACtB,oBAAoB;KACrB;IACF;GACD,OAAO;IACL,gBACE;KACE;KACA;KACA;KACA;KACA;KACD,CAAC,KAAK,GAAG;IACZ,SAAS;KACP,kBACE;KACF,iBAAiB,cACf,wCAAwC,mBAAmB,UAAU,CAAC;KACxE,oBAAoB;KACrB;IACF;GACD,WAAW;IACT,gBACE;KACE;KACA;KACA;KACA;KACA;KACD,CAAC,KAAK,GAAG;IACZ,SAAS;KACP,kBACE;KAEF,iBAAiB,cACf,4CAA4C,mBAAmB,UAAU,CAAC;KAE5E,oBAAoB;KACrB;IACF;GACF;EACH,QACE,OAAM,IAAI,MAAM,0CAA0C,SAAS"} |
@@ -1,4 +0,2 @@ | ||
| import { types } from 'recast'; | ||
| import { TransformOptions, TransformResult } from './types.js'; | ||
| export declare function transform({ ctx, source, node, }: TransformOptions): Promise<TransformResult>; | ||
| export declare function detectPreferredQuoteStyle(ast: types.ASTNode): "'" | '"'; | ||
| export declare function transform({ ctx, source, node, }: TransformOptions): TransformResult; |
+280
-265
@@ -1,297 +0,312 @@ | ||
| import { mergeImportDeclarations } from "../utils.js"; | ||
| import { ensureStringArgument } from "./utils.js"; | ||
| import MagicString from "magic-string"; | ||
| import * as t from "@babel/types"; | ||
| import { parseAst } from "@tanstack/router-utils"; | ||
| import { parse, print, types, visit } from "recast"; | ||
| import { SourceMapConsumer } from "source-map"; | ||
| //#region src/transform/transform.ts | ||
| var b = types.builders; | ||
| async function transform({ ctx, source, node }) { | ||
| let appliedChanges = false; | ||
| var routeConstructors = ["createFileRoute", "createLazyFileRoute"]; | ||
| function transform({ ctx, source, node }) { | ||
| let ast; | ||
| try { | ||
| ast = parse(source, { | ||
| sourceFileName: "output.ts", | ||
| parser: { parse(code) { | ||
| return parseAst({ | ||
| code, | ||
| tokens: true | ||
| }); | ||
| } } | ||
| }); | ||
| } catch (e) { | ||
| console.error("Error parsing code", ctx.routeId, source, e); | ||
| ast = parseAst({ code: source }); | ||
| } catch (error) { | ||
| return { | ||
| result: "error", | ||
| error: e | ||
| error | ||
| }; | ||
| } | ||
| const preferredQuote = detectPreferredQuoteStyle(ast); | ||
| let routeExportHandled = false; | ||
| function onExportFound(decl) { | ||
| if (decl.init?.type === "CallExpression") { | ||
| const callExpression = decl.init; | ||
| const firstArgument = callExpression.arguments[0]; | ||
| if (firstArgument) { | ||
| if (firstArgument.type === "ObjectExpression") { | ||
| const staticProperties = firstArgument.properties.flatMap((p) => { | ||
| if (p.type === "ObjectProperty" && p.key.type === "Identifier") return p.key.name; | ||
| return []; | ||
| }); | ||
| node.createFileRouteProps = new Set(staticProperties); | ||
| } | ||
| } | ||
| let identifier; | ||
| if (callExpression.callee.type === "Identifier") { | ||
| identifier = callExpression.callee; | ||
| if (ctx.verboseFileRoutes) { | ||
| callExpression.callee = b.callExpression(identifier, [b.stringLiteral(ctx.routeId)]); | ||
| appliedChanges = true; | ||
| } | ||
| } else if (callExpression.callee.type === "CallExpression" && callExpression.callee.callee.type === "Identifier") { | ||
| identifier = callExpression.callee.callee; | ||
| if (!ctx.verboseFileRoutes) { | ||
| callExpression.callee = identifier; | ||
| appliedChanges = true; | ||
| } else appliedChanges = ensureStringArgument(callExpression.callee, ctx.routeId, ctx.preferredQuote); | ||
| } | ||
| if (identifier === void 0) throw new Error(`expected identifier to be present in ${ctx.routeId} for export "Route"`); | ||
| if (identifier.name === "createFileRoute" && ctx.lazy) { | ||
| identifier.name = "createLazyFileRoute"; | ||
| appliedChanges = true; | ||
| } else if (identifier.name === "createLazyFileRoute" && !ctx.lazy) { | ||
| identifier.name = "createFileRoute"; | ||
| appliedChanges = true; | ||
| } | ||
| } else throw new Error(`expected "Route" export to be initialized by a CallExpression`); | ||
| routeExportHandled = true; | ||
| const exportedRouteNames = getExportedRouteNames(ast.program.body); | ||
| if (exportedRouteNames.size === 0) return { result: "no-route-export" }; | ||
| const { calls: routeCalls, hasUnsupportedRouteId, hasMalformedRouteCall } = findExportedRouteCalls(ast.program.body, exportedRouteNames); | ||
| if (routeCalls.length === 0 && hasMalformedRouteCall) return { | ||
| result: "error", | ||
| error: /* @__PURE__ */ new Error(`expected Route export in ${ctx.routeId} to use createFileRoute('/path')({...}) or createLazyFileRoute('/path')({...})`) | ||
| }; | ||
| if (routeCalls.length === 0 && hasUnsupportedRouteId) return { | ||
| result: "error", | ||
| error: /* @__PURE__ */ new Error(`expected route id to be a string literal or plain template literal in ${ctx.routeId}`) | ||
| }; | ||
| if (routeCalls.length === 0) return { result: "not-modified" }; | ||
| if (routeCalls.length > 1) return { | ||
| result: "error", | ||
| error: /* @__PURE__ */ new Error(`expected exactly one createFileRoute/createLazyFileRoute call in ${ctx.routeId}`) | ||
| }; | ||
| const routeCall = routeCalls[0]; | ||
| const routeIdQuote = getRouteIdQuote(source, routeCall.routeIdArg); | ||
| const createFileRouteProps = getCreateFileRouteProps(routeCall.optionsArg); | ||
| if (createFileRouteProps) node.createFileRouteProps = createFileRouteProps; | ||
| const expectedCallee = getExpectedRouteConstructor(ctx.lazy); | ||
| const expectedRouteId = `${routeIdQuote}${ctx.routeId}${routeIdQuote}`; | ||
| const currentRouteId = source.slice(routeCall.routeIdArg.start, routeCall.routeIdArg.end); | ||
| const targetModule = `@tanstack/${ctx.target}-router`; | ||
| const imports = parseTargetImports(ast.program.body, source, targetModule); | ||
| const s = new MagicString(source); | ||
| let modified = false; | ||
| if (routeCall.callee.name !== expectedCallee) { | ||
| s.update(routeCall.callee.start, routeCall.callee.end, expectedCallee); | ||
| modified = true; | ||
| } | ||
| const program = ast.program; | ||
| for (const n of program.body) { | ||
| if (n.type === "ExportNamedDeclaration") { | ||
| if (n.declaration?.type === "VariableDeclaration") { | ||
| const decl = n.declaration.declarations[0]; | ||
| if (decl && decl.type === "VariableDeclarator" && decl.id.type === "Identifier") { | ||
| if (decl.id.name === "Route") onExportFound(decl); | ||
| } | ||
| } else if (n.declaration === null && n.specifiers) { | ||
| for (const spec of n.specifiers) if (typeof spec.exported.name === "string") { | ||
| if (spec.exported.name === "Route") { | ||
| const variableName = spec.local?.name || spec.exported.name; | ||
| for (const decl of program.body) if (decl.type === "VariableDeclaration" && decl.declarations[0]) { | ||
| const variable = decl.declarations[0]; | ||
| if (variable.type === "VariableDeclarator" && variable.id.type === "Identifier" && variable.id.name === variableName) { | ||
| onExportFound(variable); | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| if (routeExportHandled) break; | ||
| if (currentRouteId !== expectedRouteId) { | ||
| s.update(routeCall.routeIdArg.start, routeCall.routeIdArg.end, expectedRouteId); | ||
| modified = true; | ||
| } | ||
| if (!routeExportHandled) return { result: "no-route-export" }; | ||
| const imports = { | ||
| required: [], | ||
| banned: [] | ||
| if (updateRouteImports({ | ||
| imports, | ||
| source, | ||
| s, | ||
| targetModule, | ||
| required: expectedCallee, | ||
| lineEnding: getLineEnding(source) | ||
| })) modified = true; | ||
| if (!modified) return { result: "not-modified" }; | ||
| return { | ||
| result: "modified", | ||
| output: s.toString() | ||
| }; | ||
| const targetModule = `@tanstack/${ctx.target}-router`; | ||
| if (ctx.verboseFileRoutes === false) imports.banned = [{ | ||
| source: targetModule, | ||
| specifiers: [{ imported: "createLazyFileRoute" }, { imported: "createFileRoute" }] | ||
| }]; | ||
| else if (ctx.lazy) { | ||
| imports.required = [{ | ||
| source: targetModule, | ||
| specifiers: [{ imported: "createLazyFileRoute" }] | ||
| }]; | ||
| imports.banned = [{ | ||
| source: targetModule, | ||
| specifiers: [{ imported: "createFileRoute" }] | ||
| }]; | ||
| } else { | ||
| imports.required = [{ | ||
| source: targetModule, | ||
| specifiers: [{ imported: "createFileRoute" }] | ||
| }]; | ||
| imports.banned = [{ | ||
| source: targetModule, | ||
| specifiers: [{ imported: "createLazyFileRoute" }] | ||
| }]; | ||
| } | ||
| function getExportedRouteNames(body) { | ||
| const exportedRouteNames = /* @__PURE__ */ new Set(); | ||
| for (const statement of body) { | ||
| if (!t.isExportNamedDeclaration(statement) || statement.source) continue; | ||
| if (t.isVariableDeclaration(statement.declaration)) { | ||
| for (const declarator of statement.declaration.declarations) if (t.isIdentifier(declarator.id) && declarator.id.name === "Route") exportedRouteNames.add("Route"); | ||
| } | ||
| for (const specifier of statement.specifiers) { | ||
| if (!t.isExportSpecifier(specifier) || getExportedName(specifier.exported) !== "Route") continue; | ||
| const localName = getLocalBindingName(specifier.local); | ||
| if (localName) exportedRouteNames.add(localName); | ||
| } | ||
| } | ||
| imports.required = mergeImportDeclarations(imports.required); | ||
| imports.banned = mergeImportDeclarations(imports.banned); | ||
| const importStatementCandidates = []; | ||
| const importDeclarationsToRemove = []; | ||
| for (const n of program.body) { | ||
| const findImport = (opts) => (i) => { | ||
| if (i.source === opts.source) { | ||
| const importKind = i.importKind || "value"; | ||
| return (opts.importKind || "value") === importKind; | ||
| return exportedRouteNames; | ||
| } | ||
| function findExportedRouteCalls(body, exportedRouteNames) { | ||
| const calls = []; | ||
| let hasUnsupportedRouteId = false; | ||
| let hasMalformedRouteCall = false; | ||
| for (const statement of body) { | ||
| const declaration = getVariableDeclaration(statement); | ||
| if (!declaration) continue; | ||
| for (const declarator of declaration.declarations) { | ||
| if (!t.isIdentifier(declarator.id) || !exportedRouteNames.has(declarator.id.name)) continue; | ||
| const init = getRouteConstructorInit(declarator.init); | ||
| if (!init) { | ||
| if (isDirectRouteConstructorCall(declarator.init)) hasMalformedRouteCall = true; | ||
| continue; | ||
| } | ||
| return false; | ||
| }; | ||
| if (n.type === "ImportDeclaration" && typeof n.source.value === "string") { | ||
| const filterImport = findImport({ | ||
| source: n.source.value, | ||
| importKind: n.importKind | ||
| const routeIdArg = init.innerCall.arguments[0]; | ||
| if (isSupportedRouteId(routeIdArg)) calls.push({ | ||
| callee: init.callee, | ||
| routeIdArg, | ||
| optionsArg: init.outerCall.arguments[0] | ||
| }); | ||
| let requiredImports = imports.required.filter(filterImport)[0]; | ||
| const bannedImports = imports.banned.filter(filterImport)[0]; | ||
| if (!requiredImports && !bannedImports) continue; | ||
| const importSpecifiersToRemove = []; | ||
| if (n.specifiers) { | ||
| for (const spec of n.specifiers) { | ||
| if (!requiredImports && !bannedImports) break; | ||
| if (spec.type === "ImportSpecifier" && typeof spec.imported.name === "string") { | ||
| if (requiredImports) { | ||
| const requiredImportIndex = requiredImports.specifiers.findIndex((imp) => imp.imported === spec.imported.name); | ||
| if (requiredImportIndex !== -1) { | ||
| requiredImports.specifiers.splice(requiredImportIndex, 1); | ||
| if (requiredImports.specifiers.length === 0) { | ||
| imports.required = imports.required.splice(imports.required.indexOf(requiredImports), 1); | ||
| requiredImports = void 0; | ||
| } | ||
| } else importStatementCandidates.push(n); | ||
| } | ||
| if (bannedImports) { | ||
| if (bannedImports.specifiers.findIndex((imp) => imp.imported === spec.imported.name) !== -1) importSpecifiersToRemove.push(spec); | ||
| } | ||
| } | ||
| } | ||
| if (importSpecifiersToRemove.length > 0) { | ||
| appliedChanges = true; | ||
| n.specifiers = n.specifiers.filter((spec) => !importSpecifiersToRemove.includes(spec)); | ||
| if (n.specifiers.length === 0) importDeclarationsToRemove.push(n); | ||
| } | ||
| } | ||
| else hasUnsupportedRouteId = true; | ||
| } | ||
| } | ||
| imports.required.forEach((requiredImport) => { | ||
| if (requiredImport.specifiers.length > 0) { | ||
| appliedChanges = true; | ||
| if (importStatementCandidates.length > 0) { | ||
| const importStatement = importStatementCandidates.find((importStatement) => { | ||
| if (importStatement.source.value === requiredImport.source) return (importStatement.importKind || "value") === (requiredImport.importKind || "value"); | ||
| return false; | ||
| }); | ||
| if (importStatement) { | ||
| if (importStatement.specifiers === void 0) importStatement.specifiers = []; | ||
| const importSpecifiersToAdd = requiredImport.specifiers.map((spec) => b.importSpecifier(b.identifier(spec.imported), b.identifier(spec.imported))); | ||
| importStatement.specifiers = [...importStatement.specifiers, ...importSpecifiersToAdd]; | ||
| return; | ||
| } | ||
| } | ||
| const importStatement = b.importDeclaration(requiredImport.specifiers.map((spec) => b.importSpecifier(b.identifier(spec.imported), spec.local ? b.identifier(spec.local) : null)), b.stringLiteral(requiredImport.source)); | ||
| program.body.unshift(importStatement); | ||
| return { | ||
| calls, | ||
| hasUnsupportedRouteId, | ||
| hasMalformedRouteCall | ||
| }; | ||
| } | ||
| function getVariableDeclaration(statement) { | ||
| const declaration = t.isExportNamedDeclaration(statement) ? statement.declaration : statement; | ||
| return t.isVariableDeclaration(declaration) ? declaration : null; | ||
| } | ||
| function getExportedName(node) { | ||
| return t.isIdentifier(node) ? node.name : node.value; | ||
| } | ||
| function getLocalBindingName(node) { | ||
| return t.isIdentifier(node) ? node.name : null; | ||
| } | ||
| function getRouteConstructorInit(expression) { | ||
| if (!expression || !t.isCallExpression(expression)) return null; | ||
| if (!t.isCallExpression(expression.callee)) return null; | ||
| const innerCall = expression.callee; | ||
| if (!t.isIdentifier(innerCall.callee) || !isRouteConstructor(innerCall.callee)) return null; | ||
| return { | ||
| callee: innerCall.callee, | ||
| outerCall: expression, | ||
| innerCall | ||
| }; | ||
| } | ||
| function isDirectRouteConstructorCall(expression) { | ||
| return !!expression && t.isCallExpression(expression) && t.isIdentifier(expression.callee) && isRouteConstructor(expression.callee); | ||
| } | ||
| function isRouteConstructor(callee) { | ||
| return routeConstructors.includes(callee.name); | ||
| } | ||
| function isSupportedRouteId(arg) { | ||
| return !!arg && (t.isStringLiteral(arg) || t.isTemplateLiteral(arg) && arg.expressions.length === 0); | ||
| } | ||
| function getRouteIdQuote(source, arg) { | ||
| const raw = source.slice(arg.start, arg.end); | ||
| if (raw.startsWith("'")) return "'"; | ||
| if (raw.startsWith("\"")) return "\""; | ||
| return "`"; | ||
| } | ||
| function getCreateFileRouteProps(arg) { | ||
| if (!arg || !t.isObjectExpression(arg)) return; | ||
| const props = /* @__PURE__ */ new Set(); | ||
| for (const property of arg.properties) { | ||
| if (!t.isObjectProperty(property) || property.computed) continue; | ||
| if (t.isIdentifier(property.key)) { | ||
| props.add(property.key.name); | ||
| continue; | ||
| } | ||
| if (t.isStringLiteral(property.key)) props.add(property.key.value); | ||
| } | ||
| return props; | ||
| } | ||
| function parseTargetImports(body, source, targetModule) { | ||
| const imports = []; | ||
| for (const statement of body) { | ||
| if (!t.isImportDeclaration(statement) || statement.importKind === "type" || statement.source.value !== targetModule) continue; | ||
| const rawSource = source.slice(statement.source.start, statement.source.end); | ||
| const importStatement = source.slice(statement.start, statement.end); | ||
| imports.push({ | ||
| declaration: statement, | ||
| defaultImport: statement.specifiers.find((specifier) => t.isImportDefaultSpecifier(specifier))?.local.name, | ||
| namespace: statement.specifiers.find((specifier) => t.isImportNamespaceSpecifier(specifier))?.local.name, | ||
| named: statement.specifiers.filter((specifier) => t.isImportSpecifier(specifier)).map((specifier) => ({ | ||
| imported: t.isIdentifier(specifier.imported) ? specifier.imported.name : specifier.imported.value, | ||
| local: specifier.local.name, | ||
| importKind: specifier.importKind ?? void 0 | ||
| })), | ||
| moduleName: statement.source.value, | ||
| quote: rawSource[0], | ||
| semicolon: importStatement.trimEnd().endsWith(";") | ||
| }); | ||
| } | ||
| return imports; | ||
| } | ||
| function updateRouteImports({ imports, source, s, targetModule, required, lineEnding }) { | ||
| const analysis = analyzeRouteImports(imports, required); | ||
| if (analysis.kind === "ok") return false; | ||
| if (analysis.kind === "rename") { | ||
| s.update(analysis.imported.start, analysis.imported.end, analysis.next); | ||
| s.update(analysis.local.start, analysis.local.end, analysis.next); | ||
| return true; | ||
| } | ||
| return normalizeRouteImports({ | ||
| imports, | ||
| source, | ||
| s, | ||
| targetModule, | ||
| required, | ||
| lineEnding | ||
| }); | ||
| if (importDeclarationsToRemove.length > 0) { | ||
| appliedChanges = true; | ||
| for (const importDeclaration of importDeclarationsToRemove) if (importDeclaration.specifiers?.length === 0) { | ||
| const index = program.body.indexOf(importDeclaration); | ||
| if (index !== -1) program.body.splice(index, 1); | ||
| } | ||
| function analyzeRouteImports(imports, required) { | ||
| const opposite = getOtherRouteConstructor(required); | ||
| let requiredCount = 0; | ||
| let oppositeCount = 0; | ||
| let renameCandidate; | ||
| for (const declaration of imports) for (const specifier of declaration.declaration.specifiers) { | ||
| if (!t.isImportSpecifier(specifier)) continue; | ||
| const imported = specifier.imported; | ||
| if (!t.isIdentifier(imported)) return { kind: "normalize" }; | ||
| if (!isRouteConstructorName(imported.name)) continue; | ||
| if (specifier.local.name !== imported.name) return { kind: "normalize" }; | ||
| if (imported.name === required) { | ||
| requiredCount++; | ||
| continue; | ||
| } | ||
| if (imported.name === opposite) { | ||
| oppositeCount++; | ||
| renameCandidate = { | ||
| imported, | ||
| local: specifier.local, | ||
| next: required | ||
| }; | ||
| } | ||
| } | ||
| if (!appliedChanges) return { result: "not-modified" }; | ||
| const printResult = print(ast, { | ||
| reuseWhitespace: true, | ||
| sourceMapName: "output.map" | ||
| }); | ||
| let transformedCode = printResult.code; | ||
| if (printResult.map) transformedCode = await fixTransformedOutputText({ | ||
| originalCode: source, | ||
| transformedCode, | ||
| sourceMap: printResult.map, | ||
| preferredQuote | ||
| }); | ||
| return { | ||
| result: "modified", | ||
| output: transformedCode | ||
| if (requiredCount === 1 && oppositeCount === 0) return { kind: "ok" }; | ||
| if (requiredCount === 0 && oppositeCount === 1 && renameCandidate) return { | ||
| kind: "rename", | ||
| ...renameCandidate | ||
| }; | ||
| return { kind: "normalize" }; | ||
| } | ||
| async function fixTransformedOutputText({ originalCode, transformedCode, sourceMap, preferredQuote }) { | ||
| const originalLines = originalCode.split("\n"); | ||
| const transformedLines = transformedCode.split("\n"); | ||
| const defaultUsesSemicolons = detectSemicolonUsage(originalCode); | ||
| const consumer = await new SourceMapConsumer(sourceMap); | ||
| return transformedLines.map((line, i) => { | ||
| const transformedLineNum = i + 1; | ||
| let origLineText = void 0; | ||
| for (let col = 0; col < line.length; col++) { | ||
| const mapped = consumer.originalPositionFor({ | ||
| line: transformedLineNum, | ||
| column: col | ||
| }); | ||
| if (mapped.line != null && mapped.line > 0) { | ||
| origLineText = originalLines[mapped.line - 1]; | ||
| break; | ||
| } | ||
| function normalizeRouteImports({ imports, source, s, targetModule, required, lineEnding }) { | ||
| const owner = imports.find((declaration) => hasNamedImport(declaration.named, required)) ?? imports.find((declaration) => !declaration.namespace); | ||
| let modified = false; | ||
| for (const declaration of imports) { | ||
| const named = normalizeNamedImports({ | ||
| named: declaration.named, | ||
| required, | ||
| isOwner: declaration === owner | ||
| }); | ||
| if (sameNamedImports(declaration.named, named)) continue; | ||
| const replacement = renderImportDeclaration({ | ||
| ...declaration, | ||
| named | ||
| }); | ||
| if (replacement === null) { | ||
| s.remove(declaration.declaration.start, getRemovalEnd(source, declaration.declaration.end)); | ||
| modified = true; | ||
| continue; | ||
| } | ||
| if (origLineText !== void 0) { | ||
| if (origLineText === line) return origLineText; | ||
| return fixLine(line, { | ||
| originalLine: origLineText, | ||
| useOriginalSemicolon: true, | ||
| useOriginalQuotes: true, | ||
| fallbackQuote: preferredQuote | ||
| }); | ||
| } else return fixLine(line, { | ||
| originalLine: null, | ||
| useOriginalSemicolon: false, | ||
| useOriginalQuotes: false, | ||
| fallbackQuote: preferredQuote, | ||
| fallbackSemicolon: defaultUsesSemicolons | ||
| }); | ||
| }).join("\n"); | ||
| s.update(declaration.declaration.start, declaration.declaration.end, replacement); | ||
| modified = true; | ||
| } | ||
| if (!owner) { | ||
| const quote = imports[0]?.quote ?? "'"; | ||
| const semicolon = imports[0]?.semicolon ?? false; | ||
| s.prepend(`import { ${required} } from ${quote}${targetModule}${quote}${semicolon ? ";" : ""}${lineEnding}`); | ||
| modified = true; | ||
| } | ||
| return modified; | ||
| } | ||
| function fixLine(line, { originalLine, useOriginalSemicolon, useOriginalQuotes, fallbackQuote, fallbackSemicolon = true }) { | ||
| let result = line; | ||
| if (useOriginalQuotes && originalLine) result = fixQuotes(result, originalLine, fallbackQuote); | ||
| else if (!useOriginalQuotes && fallbackQuote) result = fixQuotesToPreferred(result, fallbackQuote); | ||
| if (useOriginalSemicolon && originalLine) { | ||
| const hadSemicolon = originalLine.trimEnd().endsWith(";"); | ||
| const hasSemicolon = result.trimEnd().endsWith(";"); | ||
| if (hadSemicolon && !hasSemicolon) result += ";"; | ||
| if (!hadSemicolon && hasSemicolon) result = result.replace(/;\s*$/, ""); | ||
| } else if (!useOriginalSemicolon) { | ||
| const hasSemicolon = result.trimEnd().endsWith(";"); | ||
| if (!fallbackSemicolon && hasSemicolon) result = result.replace(/;\s*$/, ""); | ||
| if (fallbackSemicolon && !hasSemicolon && result.trim()) result += ";"; | ||
| function normalizeNamedImports({ named, required, isOwner }) { | ||
| const banned = getOtherRouteConstructor(required); | ||
| const nextNamed = []; | ||
| const seen = /* @__PURE__ */ new Set(); | ||
| for (const specifier of named) { | ||
| if (specifier.imported === banned) continue; | ||
| if (specifier.local === required && (specifier.imported !== required || !isOwner)) continue; | ||
| const key = `${specifier.importKind ?? "value"}:${specifier.imported}:${specifier.local}`; | ||
| if (seen.has(key)) continue; | ||
| seen.add(key); | ||
| nextNamed.push(specifier); | ||
| } | ||
| return result; | ||
| if (isOwner && !hasNamedImport(nextNamed, required)) nextNamed.push({ | ||
| imported: required, | ||
| local: required | ||
| }); | ||
| return nextNamed; | ||
| } | ||
| function fixQuotes(line, originalLine, fallbackQuote) { | ||
| let originalQuote = detectQuoteFromLine(originalLine); | ||
| if (!originalQuote) originalQuote = fallbackQuote; | ||
| return fixQuotesToPreferred(line, originalQuote); | ||
| function getExpectedRouteConstructor(lazy) { | ||
| return lazy ? "createLazyFileRoute" : "createFileRoute"; | ||
| } | ||
| function fixQuotesToPreferred(line, quote) { | ||
| return line.replace(/(['"`])([^'"`\\]*(?:\\.[^'"`\\]*)*)\1/g, (_, q, content) => { | ||
| return `${quote}${content.replaceAll(quote, `\\${quote}`)}${quote}`; | ||
| }); | ||
| function getOtherRouteConstructor(constructor) { | ||
| return constructor === "createFileRoute" ? "createLazyFileRoute" : "createFileRoute"; | ||
| } | ||
| function detectQuoteFromLine(line) { | ||
| const match = line.match(/(['"`])(?:\\.|[^\\])*?\1/); | ||
| return match ? match[1] : null; | ||
| function hasNamedImport(named, required) { | ||
| return named.some((specifier) => specifier.imported === required && specifier.local === required && specifier.importKind !== "type"); | ||
| } | ||
| function detectSemicolonUsage(code) { | ||
| const lines = code.split("\n").map((l) => l.trim()); | ||
| const total = lines.length; | ||
| return lines.filter((l) => l.endsWith(";")).length > total / 2; | ||
| function sameNamedImports(left, right) { | ||
| return left.length === right.length && left.every((specifier, index) => specifier.imported === right[index].imported && specifier.local === right[index].local && specifier.importKind === right[index].importKind); | ||
| } | ||
| function detectPreferredQuoteStyle(ast) { | ||
| let single = 0; | ||
| let double = 0; | ||
| visit(ast, { visitStringLiteral(path) { | ||
| if (path.parent.node.type !== "JSXAttribute") { | ||
| const raw = path.node.extra?.raw; | ||
| if (raw?.startsWith("'")) single++; | ||
| else if (raw?.startsWith("\"")) double++; | ||
| } | ||
| return false; | ||
| } }); | ||
| if (single >= double) return "'"; | ||
| return "\""; | ||
| function isRouteConstructorName(value) { | ||
| return routeConstructors.includes(value); | ||
| } | ||
| function renderImportDeclaration(importDeclaration) { | ||
| const parts = []; | ||
| if (importDeclaration.defaultImport) parts.push(importDeclaration.defaultImport); | ||
| if (importDeclaration.namespace) parts.push(`* as ${importDeclaration.namespace}`); | ||
| if (importDeclaration.named.length > 0) parts.push(`{ ${importDeclaration.named.map((specifier) => `${specifier.importKind === "type" ? "type " : ""}${specifier.imported === specifier.local ? specifier.imported : `${specifier.imported} as ${specifier.local}`}`).join(", ")} }`); | ||
| if (parts.length === 0) return null; | ||
| return `import ${parts.join(", ")} from ${importDeclaration.quote}${importDeclaration.moduleName}${importDeclaration.quote}${importDeclaration.semicolon ? ";" : ""}`; | ||
| } | ||
| function getLineEnding(source) { | ||
| if (source.includes("\r\n")) return "\r\n"; | ||
| if (source.includes("\n")) return "\n"; | ||
| if (source.includes("\r")) return "\r"; | ||
| return "\n"; | ||
| } | ||
| function getRemovalEnd(source, end) { | ||
| let pos = end; | ||
| while (pos < source.length && (source[pos] === " " || source[pos] === " ")) pos++; | ||
| if (source[pos] === "\r" && source[pos + 1] === "\n") return pos + 2; | ||
| if (source[pos] === "\n" || source[pos] === "\r") return pos + 1; | ||
| return end; | ||
| } | ||
| //#endregion | ||
@@ -298,0 +313,0 @@ export { transform }; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"transform.js","names":[],"sources":["../../../src/transform/transform.ts"],"sourcesContent":["import { parseAst } from '@tanstack/router-utils'\nimport { parse, print, types, visit } from 'recast'\nimport { SourceMapConsumer } from 'source-map'\nimport { mergeImportDeclarations } from '../utils'\nimport { ensureStringArgument } from './utils'\nimport type { ImportDeclaration } from '../types'\nimport type { RawSourceMap } from 'source-map'\nimport type { TransformOptions, TransformResult } from './types'\n\nconst b = types.builders\n\nexport async function transform({\n ctx,\n source,\n node,\n}: TransformOptions): Promise<TransformResult> {\n let appliedChanges = false as boolean\n let ast: types.namedTypes.File\n try {\n ast = parse(source, {\n sourceFileName: 'output.ts',\n parser: {\n parse(code: string) {\n return parseAst({\n code,\n // we need to instruct babel to produce tokens,\n // otherwise recast will try to generate the tokens via its own parser and will fail\n tokens: true,\n })\n },\n },\n })\n } catch (e) {\n console.error('Error parsing code', ctx.routeId, source, e)\n return {\n result: 'error',\n error: e,\n }\n }\n\n const preferredQuote = detectPreferredQuoteStyle(ast)\n\n let routeExportHandled = false as boolean\n function onExportFound(decl: types.namedTypes.VariableDeclarator) {\n if (decl.init?.type === 'CallExpression') {\n const callExpression = decl.init\n const firstArgument = callExpression.arguments[0]\n if (firstArgument) {\n if (firstArgument.type === 'ObjectExpression') {\n const staticProperties = firstArgument.properties.flatMap((p) => {\n if (p.type === 'ObjectProperty' && p.key.type === 'Identifier') {\n return p.key.name\n }\n return []\n })\n node.createFileRouteProps = new Set(staticProperties)\n }\n }\n let identifier: types.namedTypes.Identifier | undefined\n // `const Route = createFileRoute({ ... })`\n if (callExpression.callee.type === 'Identifier') {\n identifier = callExpression.callee\n if (ctx.verboseFileRoutes) {\n // we need to add the string literal via another CallExpression\n callExpression.callee = b.callExpression(identifier, [\n b.stringLiteral(ctx.routeId),\n ])\n appliedChanges = true\n }\n }\n // `const Route = createFileRoute('/path')({ ... })`\n else if (\n callExpression.callee.type === 'CallExpression' &&\n callExpression.callee.callee.type === 'Identifier'\n ) {\n identifier = callExpression.callee.callee\n if (!ctx.verboseFileRoutes) {\n // we need to remove the route id\n callExpression.callee = identifier\n appliedChanges = true\n } else {\n // check if the route id is correct\n appliedChanges = ensureStringArgument(\n callExpression.callee,\n ctx.routeId,\n ctx.preferredQuote,\n )\n }\n }\n if (identifier === undefined) {\n throw new Error(\n `expected identifier to be present in ${ctx.routeId} for export \"Route\"`,\n )\n }\n if (identifier.name === 'createFileRoute' && ctx.lazy) {\n identifier.name = 'createLazyFileRoute'\n appliedChanges = true\n } else if (identifier.name === 'createLazyFileRoute' && !ctx.lazy) {\n identifier.name = 'createFileRoute'\n appliedChanges = true\n }\n } else {\n throw new Error(\n `expected \"Route\" export to be initialized by a CallExpression`,\n )\n }\n routeExportHandled = true\n }\n\n const program: types.namedTypes.Program = ast.program\n // first pass: find Route export\n for (const n of program.body) {\n if (n.type === 'ExportNamedDeclaration') {\n // direct export of a variable declaration, e.g. `export const Route = createFileRoute('/path')`\n if (n.declaration?.type === 'VariableDeclaration') {\n const decl = n.declaration.declarations[0]\n if (\n decl &&\n decl.type === 'VariableDeclarator' &&\n decl.id.type === 'Identifier'\n ) {\n if (decl.id.name === 'Route') {\n onExportFound(decl)\n }\n }\n }\n // this is an export without a declaration, e.g. `export { Route }`\n else if (n.declaration === null && n.specifiers) {\n for (const spec of n.specifiers) {\n if (typeof spec.exported.name === 'string') {\n if (spec.exported.name === 'Route') {\n const variableName = spec.local?.name || spec.exported.name\n // find the matching variable declaration by iterating over the top-level declarations\n for (const decl of program.body) {\n if (\n decl.type === 'VariableDeclaration' &&\n decl.declarations[0]\n ) {\n const variable = decl.declarations[0]\n if (\n variable.type === 'VariableDeclarator' &&\n variable.id.type === 'Identifier' &&\n variable.id.name === variableName\n ) {\n onExportFound(variable)\n break\n }\n }\n }\n }\n }\n }\n }\n }\n if (routeExportHandled) {\n break\n }\n }\n\n if (!routeExportHandled) {\n return {\n result: 'no-route-export',\n }\n }\n\n const imports: {\n required: Array<ImportDeclaration>\n banned: Array<ImportDeclaration>\n } = {\n required: [],\n banned: [],\n }\n\n const targetModule = `@tanstack/${ctx.target}-router`\n if (ctx.verboseFileRoutes === false) {\n imports.banned = [\n {\n source: targetModule,\n specifiers: [\n { imported: 'createLazyFileRoute' },\n { imported: 'createFileRoute' },\n ],\n },\n ]\n } else {\n if (ctx.lazy) {\n imports.required = [\n {\n source: targetModule,\n specifiers: [{ imported: 'createLazyFileRoute' }],\n },\n ]\n imports.banned = [\n {\n source: targetModule,\n specifiers: [{ imported: 'createFileRoute' }],\n },\n ]\n } else {\n imports.required = [\n {\n source: targetModule,\n specifiers: [{ imported: 'createFileRoute' }],\n },\n ]\n imports.banned = [\n {\n source: targetModule,\n specifiers: [{ imported: 'createLazyFileRoute' }],\n },\n ]\n }\n }\n\n imports.required = mergeImportDeclarations(imports.required)\n imports.banned = mergeImportDeclarations(imports.banned)\n\n const importStatementCandidates: Array<types.namedTypes.ImportDeclaration> =\n []\n const importDeclarationsToRemove: Array<types.namedTypes.ImportDeclaration> =\n []\n\n // second pass: apply import rules, but only if a matching export for the plugin was found\n for (const n of program.body) {\n const findImport =\n (opts: { source: string; importKind?: 'type' | 'value' | 'typeof' }) =>\n (i: ImportDeclaration) => {\n if (i.source === opts.source) {\n const importKind = i.importKind || 'value'\n const expectedImportKind = opts.importKind || 'value'\n return expectedImportKind === importKind\n }\n return false\n }\n if (n.type === 'ImportDeclaration' && typeof n.source.value === 'string') {\n const filterImport = findImport({\n source: n.source.value,\n importKind: n.importKind,\n })\n let requiredImports = imports.required.filter(filterImport)[0]\n\n const bannedImports = imports.banned.filter(filterImport)[0]\n if (!requiredImports && !bannedImports) {\n continue\n }\n const importSpecifiersToRemove: types.namedTypes.ImportDeclaration['specifiers'] =\n []\n if (n.specifiers) {\n for (const spec of n.specifiers) {\n if (!requiredImports && !bannedImports) {\n break\n }\n if (\n spec.type === 'ImportSpecifier' &&\n typeof spec.imported.name === 'string'\n ) {\n if (requiredImports) {\n const requiredImportIndex = requiredImports.specifiers.findIndex(\n (imp) => imp.imported === spec.imported.name,\n )\n if (requiredImportIndex !== -1) {\n // import is already present, remove it from requiredImports\n requiredImports.specifiers.splice(requiredImportIndex, 1)\n if (requiredImports.specifiers.length === 0) {\n imports.required = imports.required.splice(\n imports.required.indexOf(requiredImports),\n 1,\n )\n requiredImports = undefined\n }\n } else {\n // add the import statement to the candidates\n importStatementCandidates.push(n)\n }\n }\n if (bannedImports) {\n const bannedImportIndex = bannedImports.specifiers.findIndex(\n (imp) => imp.imported === spec.imported.name,\n )\n if (bannedImportIndex !== -1) {\n importSpecifiersToRemove.push(spec)\n }\n }\n }\n }\n if (importSpecifiersToRemove.length > 0) {\n appliedChanges = true\n n.specifiers = n.specifiers.filter(\n (spec) => !importSpecifiersToRemove.includes(spec),\n )\n\n // mark the import statement as to be deleted if it is now empty\n if (n.specifiers.length === 0) {\n importDeclarationsToRemove.push(n)\n }\n }\n }\n }\n }\n imports.required.forEach((requiredImport) => {\n if (requiredImport.specifiers.length > 0) {\n appliedChanges = true\n if (importStatementCandidates.length > 0) {\n // find the first import statement that matches both the module and the import kind\n const importStatement = importStatementCandidates.find(\n (importStatement) => {\n if (importStatement.source.value === requiredImport.source) {\n const importKind = importStatement.importKind || 'value'\n const requiredImportKind = requiredImport.importKind || 'value'\n return importKind === requiredImportKind\n }\n return false\n },\n )\n if (importStatement) {\n if (importStatement.specifiers === undefined) {\n importStatement.specifiers = []\n }\n const importSpecifiersToAdd = requiredImport.specifiers.map((spec) =>\n b.importSpecifier(\n b.identifier(spec.imported),\n b.identifier(spec.imported),\n ),\n )\n importStatement.specifiers = [\n ...importStatement.specifiers,\n ...importSpecifiersToAdd,\n ]\n return\n }\n }\n const importStatement = b.importDeclaration(\n requiredImport.specifiers.map((spec) =>\n b.importSpecifier(\n b.identifier(spec.imported),\n spec.local ? b.identifier(spec.local) : null,\n ),\n ),\n b.stringLiteral(requiredImport.source),\n )\n program.body.unshift(importStatement)\n }\n })\n if (importDeclarationsToRemove.length > 0) {\n appliedChanges = true\n for (const importDeclaration of importDeclarationsToRemove) {\n // check if the import declaration is still empty\n if (importDeclaration.specifiers?.length === 0) {\n const index = program.body.indexOf(importDeclaration)\n if (index !== -1) {\n program.body.splice(index, 1)\n }\n }\n }\n }\n\n if (!appliedChanges) {\n return {\n result: 'not-modified',\n }\n }\n\n const printResult = print(ast, {\n reuseWhitespace: true,\n sourceMapName: 'output.map',\n })\n let transformedCode = printResult.code\n if (printResult.map) {\n const fixedOutput = await fixTransformedOutputText({\n originalCode: source,\n transformedCode,\n sourceMap: printResult.map as RawSourceMap,\n preferredQuote,\n })\n transformedCode = fixedOutput\n }\n return {\n result: 'modified',\n output: transformedCode,\n }\n}\n\nasync function fixTransformedOutputText({\n originalCode,\n transformedCode,\n sourceMap,\n preferredQuote,\n}: {\n originalCode: string\n transformedCode: string\n sourceMap: RawSourceMap\n preferredQuote: '\"' | \"'\"\n}) {\n const originalLines = originalCode.split('\\n')\n const transformedLines = transformedCode.split('\\n')\n\n const defaultUsesSemicolons = detectSemicolonUsage(originalCode)\n\n const consumer = await new SourceMapConsumer(sourceMap)\n\n const fixedLines = transformedLines.map((line, i) => {\n const transformedLineNum = i + 1\n\n let origLineText: string | undefined = undefined\n\n for (let col = 0; col < line.length; col++) {\n const mapped = consumer.originalPositionFor({\n line: transformedLineNum,\n column: col,\n })\n if (mapped.line != null && mapped.line > 0) {\n origLineText = originalLines[mapped.line - 1]\n break\n }\n }\n\n if (origLineText !== undefined) {\n if (origLineText === line) {\n return origLineText\n }\n return fixLine(line, {\n originalLine: origLineText,\n useOriginalSemicolon: true,\n useOriginalQuotes: true,\n fallbackQuote: preferredQuote,\n })\n } else {\n return fixLine(line, {\n originalLine: null,\n useOriginalSemicolon: false,\n useOriginalQuotes: false,\n fallbackQuote: preferredQuote,\n fallbackSemicolon: defaultUsesSemicolons,\n })\n }\n })\n\n return fixedLines.join('\\n')\n}\n\nfunction fixLine(\n line: string,\n {\n originalLine,\n useOriginalSemicolon,\n useOriginalQuotes,\n fallbackQuote,\n fallbackSemicolon = true,\n }: {\n originalLine: string | null\n useOriginalSemicolon: boolean\n useOriginalQuotes: boolean\n fallbackQuote: string\n fallbackSemicolon?: boolean\n },\n) {\n let result = line\n\n if (useOriginalQuotes && originalLine) {\n result = fixQuotes(result, originalLine, fallbackQuote)\n } else if (!useOriginalQuotes && fallbackQuote) {\n result = fixQuotesToPreferred(result, fallbackQuote)\n }\n\n if (useOriginalSemicolon && originalLine) {\n const hadSemicolon = originalLine.trimEnd().endsWith(';')\n const hasSemicolon = result.trimEnd().endsWith(';')\n if (hadSemicolon && !hasSemicolon) result += ';'\n if (!hadSemicolon && hasSemicolon) result = result.replace(/;\\s*$/, '')\n } else if (!useOriginalSemicolon) {\n const hasSemicolon = result.trimEnd().endsWith(';')\n if (!fallbackSemicolon && hasSemicolon) result = result.replace(/;\\s*$/, '')\n if (fallbackSemicolon && !hasSemicolon && result.trim()) result += ';'\n }\n\n return result\n}\n\nfunction fixQuotes(line: string, originalLine: string, fallbackQuote: string) {\n let originalQuote = detectQuoteFromLine(originalLine)\n if (!originalQuote) {\n originalQuote = fallbackQuote\n }\n return fixQuotesToPreferred(line, originalQuote)\n}\n\nfunction fixQuotesToPreferred(line: string, quote: string) {\n // Replace existing quotes with preferred quote\n return line.replace(\n /(['\"`])([^'\"`\\\\]*(?:\\\\.[^'\"`\\\\]*)*)\\1/g,\n (_, q, content) => {\n const escaped = content.replaceAll(quote, `\\\\${quote}`)\n return `${quote}${escaped}${quote}`\n },\n )\n}\n\nfunction detectQuoteFromLine(line: string) {\n const match = line.match(/(['\"`])(?:\\\\.|[^\\\\])*?\\1/)\n return match ? match[1] : null\n}\n\nfunction detectSemicolonUsage(code: string) {\n const lines = code.split('\\n').map((l) => l.trim())\n const total = lines.length\n const withSemis = lines.filter((l) => l.endsWith(';')).length\n return withSemis > total / 2\n}\n\nexport function detectPreferredQuoteStyle(ast: types.ASTNode): \"'\" | '\"' {\n let single = 0\n let double = 0\n\n visit(ast, {\n visitStringLiteral(path) {\n if (path.parent.node.type !== 'JSXAttribute') {\n const raw = path.node.extra?.raw\n if (raw?.startsWith(\"'\")) single++\n else if (raw?.startsWith('\"')) double++\n }\n return false\n },\n })\n\n if (single >= double) {\n return \"'\"\n }\n return '\"'\n}\n"],"mappings":"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"} | ||
| {"version":3,"file":"transform.js","names":[],"sources":["../../../src/transform/transform.ts"],"sourcesContent":["import MagicString from 'magic-string'\nimport * as t from '@babel/types'\nimport { parseAst } from '@tanstack/router-utils'\nimport type { TransformOptions, TransformResult } from './types'\n\nconst routeConstructors = ['createFileRoute', 'createLazyFileRoute'] as const\n\ntype RouteConstructorName = (typeof routeConstructors)[number]\ntype SupportedRouteId = t.StringLiteral | t.TemplateLiteral\ntype NamedImport = {\n imported: string\n local: string\n importKind?: 'type' | 'typeof' | 'value'\n}\n\ntype ParsedImportDeclaration = {\n declaration: t.ImportDeclaration\n defaultImport?: string\n namespace?: string\n named: Array<NamedImport>\n moduleName: string\n quote: '\"' | \"'\"\n semicolon: boolean\n}\n\ntype RouteImportAnalysis =\n | { kind: 'ok' }\n | {\n kind: 'rename'\n imported: t.Identifier\n local: t.Identifier\n next: RouteConstructorName\n }\n | { kind: 'normalize' }\n\ntype RouteCall = {\n callee: t.Identifier & { name: RouteConstructorName }\n routeIdArg: SupportedRouteId\n optionsArg: t.CallExpression['arguments'][number] | undefined\n}\n\ntype RouteCallAnalysis = {\n calls: Array<RouteCall>\n hasUnsupportedRouteId: boolean\n hasMalformedRouteCall: boolean\n}\n\nexport function transform({\n ctx,\n source,\n node,\n}: TransformOptions): TransformResult {\n let ast: ReturnType<typeof parseAst>\n\n try {\n ast = parseAst({ code: source })\n } catch (error) {\n return {\n result: 'error',\n error,\n }\n }\n\n const exportedRouteNames = getExportedRouteNames(ast.program.body)\n\n if (exportedRouteNames.size === 0) {\n return { result: 'no-route-export' }\n }\n\n const {\n calls: routeCalls,\n hasUnsupportedRouteId,\n hasMalformedRouteCall,\n } = findExportedRouteCalls(ast.program.body, exportedRouteNames)\n\n if (routeCalls.length === 0 && hasMalformedRouteCall) {\n return {\n result: 'error',\n error: new Error(\n `expected Route export in ${ctx.routeId} to use createFileRoute('/path')({...}) or createLazyFileRoute('/path')({...})`,\n ),\n }\n }\n\n if (routeCalls.length === 0 && hasUnsupportedRouteId) {\n return {\n result: 'error',\n error: new Error(\n `expected route id to be a string literal or plain template literal in ${ctx.routeId}`,\n ),\n }\n }\n\n if (routeCalls.length === 0) {\n return { result: 'not-modified' }\n }\n\n if (routeCalls.length > 1) {\n return {\n result: 'error',\n error: new Error(\n `expected exactly one createFileRoute/createLazyFileRoute call in ${ctx.routeId}`,\n ),\n }\n }\n\n const routeCall = routeCalls[0]!\n const routeIdQuote = getRouteIdQuote(source, routeCall.routeIdArg)\n\n const createFileRouteProps = getCreateFileRouteProps(routeCall.optionsArg)\n if (createFileRouteProps) {\n node.createFileRouteProps = createFileRouteProps\n }\n\n const expectedCallee = getExpectedRouteConstructor(ctx.lazy)\n const expectedRouteId = `${routeIdQuote}${ctx.routeId}${routeIdQuote}`\n const currentRouteId = source.slice(\n routeCall.routeIdArg.start!,\n routeCall.routeIdArg.end!,\n )\n const targetModule = `@tanstack/${ctx.target}-router`\n const imports = parseTargetImports(ast.program.body, source, targetModule)\n\n const s = new MagicString(source)\n let modified = false\n\n if (routeCall.callee.name !== expectedCallee) {\n s.update(routeCall.callee.start!, routeCall.callee.end!, expectedCallee)\n modified = true\n }\n\n if (currentRouteId !== expectedRouteId) {\n s.update(\n routeCall.routeIdArg.start!,\n routeCall.routeIdArg.end!,\n expectedRouteId,\n )\n modified = true\n }\n\n if (\n updateRouteImports({\n imports,\n source,\n s,\n targetModule,\n required: expectedCallee,\n lineEnding: getLineEnding(source),\n })\n ) {\n modified = true\n }\n\n if (!modified) {\n return { result: 'not-modified' }\n }\n\n return {\n result: 'modified',\n output: s.toString(),\n }\n}\n\nfunction getExportedRouteNames(body: Array<t.Statement>) {\n const exportedRouteNames = new Set<string>()\n\n for (const statement of body) {\n if (!t.isExportNamedDeclaration(statement) || statement.source) {\n continue\n }\n\n if (t.isVariableDeclaration(statement.declaration)) {\n for (const declarator of statement.declaration.declarations) {\n if (t.isIdentifier(declarator.id) && declarator.id.name === 'Route') {\n exportedRouteNames.add('Route')\n }\n }\n }\n\n for (const specifier of statement.specifiers) {\n if (\n !t.isExportSpecifier(specifier) ||\n getExportedName(specifier.exported) !== 'Route'\n ) {\n continue\n }\n\n const localName = getLocalBindingName(specifier.local)\n if (localName) {\n exportedRouteNames.add(localName)\n }\n }\n }\n\n return exportedRouteNames\n}\n\nfunction findExportedRouteCalls(\n body: Array<t.Statement>,\n exportedRouteNames: Set<string>,\n): RouteCallAnalysis {\n const calls: Array<RouteCall> = []\n let hasUnsupportedRouteId = false\n let hasMalformedRouteCall = false\n\n for (const statement of body) {\n const declaration = getVariableDeclaration(statement)\n if (!declaration) {\n continue\n }\n\n for (const declarator of declaration.declarations) {\n if (\n !t.isIdentifier(declarator.id) ||\n !exportedRouteNames.has(declarator.id.name)\n ) {\n continue\n }\n\n const init = getRouteConstructorInit(declarator.init)\n if (!init) {\n if (isDirectRouteConstructorCall(declarator.init)) {\n hasMalformedRouteCall = true\n }\n continue\n }\n\n const routeIdArg = init.innerCall.arguments[0]\n if (isSupportedRouteId(routeIdArg)) {\n calls.push({\n callee: init.callee,\n routeIdArg,\n optionsArg: init.outerCall.arguments[0],\n })\n } else {\n hasUnsupportedRouteId = true\n }\n }\n }\n\n return { calls, hasUnsupportedRouteId, hasMalformedRouteCall }\n}\n\nfunction getVariableDeclaration(statement: t.Statement) {\n const declaration = t.isExportNamedDeclaration(statement)\n ? statement.declaration\n : statement\n\n return t.isVariableDeclaration(declaration) ? declaration : null\n}\n\nfunction getExportedName(node: t.Identifier | t.StringLiteral) {\n return t.isIdentifier(node) ? node.name : node.value\n}\n\nfunction getLocalBindingName(node: t.Identifier | t.StringLiteral) {\n return t.isIdentifier(node) ? node.name : null\n}\n\nfunction getRouteConstructorInit(expression: t.Expression | null | undefined) {\n if (!expression || !t.isCallExpression(expression)) {\n return null\n }\n\n if (!t.isCallExpression(expression.callee)) {\n return null\n }\n\n const innerCall = expression.callee\n\n if (\n !t.isIdentifier(innerCall.callee) ||\n !isRouteConstructor(innerCall.callee)\n ) {\n return null\n }\n\n return {\n callee: innerCall.callee,\n outerCall: expression,\n innerCall,\n }\n}\n\nfunction isDirectRouteConstructorCall(\n expression: t.Expression | null | undefined,\n) {\n return (\n !!expression &&\n t.isCallExpression(expression) &&\n t.isIdentifier(expression.callee) &&\n isRouteConstructor(expression.callee)\n )\n}\n\nfunction isRouteConstructor(callee: t.Identifier): callee is t.Identifier & {\n name: RouteConstructorName\n} {\n return routeConstructors.includes(callee.name as RouteConstructorName)\n}\n\nfunction isSupportedRouteId(\n arg: t.CallExpression['arguments'][number] | undefined,\n): arg is SupportedRouteId {\n return (\n !!arg &&\n (t.isStringLiteral(arg) ||\n (t.isTemplateLiteral(arg) && arg.expressions.length === 0))\n )\n}\n\nfunction getRouteIdQuote(\n source: string,\n arg: SupportedRouteId,\n): '\"' | \"'\" | '`' {\n const raw = source.slice(arg.start!, arg.end!)\n\n if (raw.startsWith(\"'\")) return \"'\"\n if (raw.startsWith('\"')) return '\"'\n return '`'\n}\n\nfunction getCreateFileRouteProps(\n arg: t.CallExpression['arguments'][number] | undefined,\n) {\n if (!arg || !t.isObjectExpression(arg)) {\n return undefined\n }\n\n const props = new Set<string>()\n\n for (const property of arg.properties) {\n if (!t.isObjectProperty(property) || property.computed) {\n continue\n }\n\n if (t.isIdentifier(property.key)) {\n props.add(property.key.name)\n continue\n }\n\n if (t.isStringLiteral(property.key)) {\n props.add(property.key.value)\n }\n }\n\n return props\n}\n\nfunction parseTargetImports(\n body: Array<t.Statement>,\n source: string,\n targetModule: string,\n) {\n const imports: Array<ParsedImportDeclaration> = []\n\n for (const statement of body) {\n if (\n !t.isImportDeclaration(statement) ||\n statement.importKind === 'type' ||\n statement.source.value !== targetModule\n ) {\n continue\n }\n\n const rawSource = source.slice(\n statement.source.start!,\n statement.source.end!,\n )\n const importStatement = source.slice(statement.start!, statement.end!)\n\n imports.push({\n declaration: statement,\n defaultImport: statement.specifiers.find((specifier) =>\n t.isImportDefaultSpecifier(specifier),\n )?.local.name,\n namespace: statement.specifiers.find((specifier) =>\n t.isImportNamespaceSpecifier(specifier),\n )?.local.name,\n named: statement.specifiers\n .filter((specifier): specifier is t.ImportSpecifier =>\n t.isImportSpecifier(specifier),\n )\n .map((specifier) => ({\n imported: t.isIdentifier(specifier.imported)\n ? specifier.imported.name\n : specifier.imported.value,\n local: specifier.local.name,\n importKind: specifier.importKind ?? undefined,\n })),\n moduleName: statement.source.value,\n quote: rawSource[0] as '\"' | \"'\",\n semicolon: importStatement.trimEnd().endsWith(';'),\n })\n }\n\n return imports\n}\n\nfunction updateRouteImports({\n imports,\n source,\n s,\n targetModule,\n required,\n lineEnding,\n}: {\n imports: Array<ParsedImportDeclaration>\n source: string\n s: MagicString\n targetModule: string\n required: RouteConstructorName\n lineEnding: '\\r\\n' | '\\n' | '\\r'\n}) {\n const analysis = analyzeRouteImports(imports, required)\n\n if (analysis.kind === 'ok') {\n return false\n }\n\n if (analysis.kind === 'rename') {\n s.update(analysis.imported.start!, analysis.imported.end!, analysis.next)\n s.update(analysis.local.start!, analysis.local.end!, analysis.next)\n return true\n }\n\n return normalizeRouteImports({\n imports,\n source,\n s,\n targetModule,\n required,\n lineEnding,\n })\n}\n\nfunction analyzeRouteImports(\n imports: Array<ParsedImportDeclaration>,\n required: RouteConstructorName,\n): RouteImportAnalysis {\n const opposite = getOtherRouteConstructor(required)\n let requiredCount = 0\n let oppositeCount = 0\n let renameCandidate:\n | {\n imported: t.Identifier\n local: t.Identifier\n next: RouteConstructorName\n }\n | undefined\n\n for (const declaration of imports) {\n for (const specifier of declaration.declaration.specifiers) {\n if (!t.isImportSpecifier(specifier)) {\n continue\n }\n\n const imported = specifier.imported\n if (!t.isIdentifier(imported)) {\n return { kind: 'normalize' }\n }\n\n if (!isRouteConstructorName(imported.name)) {\n continue\n }\n\n if (specifier.local.name !== imported.name) {\n return { kind: 'normalize' }\n }\n\n if (imported.name === required) {\n requiredCount++\n continue\n }\n\n if (imported.name === opposite) {\n oppositeCount++\n renameCandidate = {\n imported,\n local: specifier.local,\n next: required,\n }\n }\n }\n }\n\n if (requiredCount === 1 && oppositeCount === 0) {\n return { kind: 'ok' }\n }\n\n if (requiredCount === 0 && oppositeCount === 1 && renameCandidate) {\n return {\n kind: 'rename',\n ...renameCandidate,\n }\n }\n\n return { kind: 'normalize' }\n}\n\nfunction normalizeRouteImports({\n imports,\n source,\n s,\n targetModule,\n required,\n lineEnding,\n}: {\n imports: Array<ParsedImportDeclaration>\n source: string\n s: MagicString\n targetModule: string\n required: RouteConstructorName\n lineEnding: '\\r\\n' | '\\n' | '\\r'\n}) {\n const owner =\n imports.find((declaration) =>\n hasNamedImport(declaration.named, required),\n ) ?? imports.find((declaration) => !declaration.namespace)\n\n let modified = false\n\n for (const declaration of imports) {\n const named = normalizeNamedImports({\n named: declaration.named,\n required,\n isOwner: declaration === owner,\n })\n\n if (sameNamedImports(declaration.named, named)) {\n continue\n }\n\n const replacement = renderImportDeclaration({\n ...declaration,\n named,\n })\n\n if (replacement === null) {\n s.remove(\n declaration.declaration.start!,\n getRemovalEnd(source, declaration.declaration.end!),\n )\n modified = true\n continue\n }\n\n s.update(\n declaration.declaration.start!,\n declaration.declaration.end!,\n replacement,\n )\n modified = true\n }\n\n if (!owner) {\n const quote = imports[0]?.quote ?? \"'\"\n const semicolon = imports[0]?.semicolon ?? false\n s.prepend(\n `import { ${required} } from ${quote}${targetModule}${quote}${semicolon ? ';' : ''}${lineEnding}`,\n )\n modified = true\n }\n\n return modified\n}\n\nfunction normalizeNamedImports({\n named,\n required,\n isOwner,\n}: {\n named: Array<NamedImport>\n required: RouteConstructorName\n isOwner: boolean\n}) {\n const banned = getOtherRouteConstructor(required)\n const nextNamed: Array<NamedImport> = []\n const seen = new Set<string>()\n\n for (const specifier of named) {\n if (specifier.imported === banned) {\n continue\n }\n\n if (\n specifier.local === required &&\n (specifier.imported !== required || !isOwner)\n ) {\n continue\n }\n\n const key = `${specifier.importKind ?? 'value'}:${specifier.imported}:${specifier.local}`\n if (seen.has(key)) {\n continue\n }\n\n seen.add(key)\n nextNamed.push(specifier)\n }\n\n if (isOwner && !hasNamedImport(nextNamed, required)) {\n nextNamed.push({ imported: required, local: required })\n }\n\n return nextNamed\n}\n\nfunction getExpectedRouteConstructor(lazy: boolean): RouteConstructorName {\n return lazy ? 'createLazyFileRoute' : 'createFileRoute'\n}\n\nfunction getOtherRouteConstructor(\n constructor: RouteConstructorName,\n): RouteConstructorName {\n return constructor === 'createFileRoute'\n ? 'createLazyFileRoute'\n : 'createFileRoute'\n}\n\nfunction hasNamedImport(\n named: Array<NamedImport>,\n required: RouteConstructorName,\n) {\n return named.some(\n (specifier) =>\n specifier.imported === required &&\n specifier.local === required &&\n specifier.importKind !== 'type',\n )\n}\n\nfunction sameNamedImports(left: Array<NamedImport>, right: Array<NamedImport>) {\n return (\n left.length === right.length &&\n left.every(\n (specifier, index) =>\n specifier.imported === right[index]!.imported &&\n specifier.local === right[index]!.local &&\n specifier.importKind === right[index]!.importKind,\n )\n )\n}\n\nfunction isRouteConstructorName(value: string): value is RouteConstructorName {\n return routeConstructors.includes(value as RouteConstructorName)\n}\n\nfunction renderImportDeclaration(importDeclaration: {\n defaultImport?: string\n namespace?: string\n named: Array<NamedImport>\n moduleName: string\n quote: '\"' | \"'\"\n semicolon: boolean\n}) {\n const parts: Array<string> = []\n\n if (importDeclaration.defaultImport) {\n parts.push(importDeclaration.defaultImport)\n }\n\n if (importDeclaration.namespace) {\n parts.push(`* as ${importDeclaration.namespace}`)\n }\n\n if (importDeclaration.named.length > 0) {\n parts.push(\n `{ ${importDeclaration.named\n .map(\n (specifier) =>\n `${specifier.importKind === 'type' ? 'type ' : ''}${specifier.imported === specifier.local ? specifier.imported : `${specifier.imported} as ${specifier.local}`}`,\n )\n .join(', ')} }`,\n )\n }\n\n if (parts.length === 0) {\n return null\n }\n\n return `import ${parts.join(', ')} from ${importDeclaration.quote}${importDeclaration.moduleName}${importDeclaration.quote}${importDeclaration.semicolon ? ';' : ''}`\n}\n\nfunction getLineEnding(source: string): '\\r\\n' | '\\n' | '\\r' {\n if (source.includes('\\r\\n')) {\n return '\\r\\n'\n }\n\n if (source.includes('\\n')) {\n return '\\n'\n }\n\n if (source.includes('\\r')) {\n return '\\r'\n }\n\n return '\\n'\n}\n\nfunction getRemovalEnd(source: string, end: number) {\n let pos = end\n while (pos < source.length && (source[pos] === ' ' || source[pos] === '\\t')) {\n pos++\n }\n\n if (source[pos] === '\\r' && source[pos + 1] === '\\n') {\n return pos + 2\n }\n\n if (source[pos] === '\\n' || source[pos] === '\\r') {\n return pos + 1\n }\n\n return end\n}\n"],"mappings":"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"} |
@@ -1,2 +0,2 @@ | ||
| import { ImportDeclaration, RouteNode } from '../types.js'; | ||
| import { RouteNode } from '../types.js'; | ||
| import { Config } from '../config.js'; | ||
@@ -19,6 +19,2 @@ export interface TransformOptions { | ||
| }; | ||
| export interface TransformImportsConfig { | ||
| banned?: Array<ImportDeclaration>; | ||
| required?: Array<ImportDeclaration>; | ||
| } | ||
| export interface TransformContext { | ||
@@ -28,4 +24,2 @@ target: Config['target']; | ||
| lazy: boolean; | ||
| verboseFileRoutes: boolean; | ||
| preferredQuote?: '"' | "'"; | ||
| } |
+0
-24
@@ -10,3 +10,2 @@ import { Config, TokenMatcher } from './config.js'; | ||
| private prefixToRoute; | ||
| private layoutRoutes; | ||
| constructor(routes: Array<RouteNode>); | ||
@@ -84,3 +83,2 @@ /** | ||
| }): RegExp; | ||
| export declare function isBracketWrappedSegment(segment: string): boolean; | ||
| export declare function unwrapBracketWrappedSegment(segment: string): string; | ||
@@ -166,14 +164,2 @@ export declare function removeLeadingUnderscores(s: string, routeToken: string): string; | ||
| /** | ||
| * Checks if a given RouteNode is valid for augmenting it with typing based on conditions. | ||
| * Also asserts that the RouteNode is defined. | ||
| * | ||
| * @param routeNode - The RouteNode to check. | ||
| * @returns A boolean indicating whether the RouteNode is defined. | ||
| */ | ||
| export declare function isRouteNodeValidForAugmentation(routeNode?: RouteNode): routeNode is RouteNode; | ||
| /** | ||
| * Infers the path for use by TS | ||
| */ | ||
| export declare const inferPath: (routeNode: RouteNode) => string; | ||
| /** | ||
| * Infers the full path for use by TS | ||
@@ -194,14 +180,5 @@ */ | ||
| export declare const createRouteNodesById: (routeNodes: Array<RouteNode>) => Map<string, RouteNode>; | ||
| /** | ||
| * Infers to path | ||
| */ | ||
| export declare const inferTo: (routeNode: RouteNode) => string; | ||
| /** | ||
| * Dedupes branches and index routes | ||
| */ | ||
| export declare const dedupeBranchesAndIndexRoutes: (routes: Array<RouteNode>) => Array<RouteNode>; | ||
| export declare function checkRouteFullPathUniqueness(_routes: Array<RouteNode>, config: Config): void; | ||
| export declare function buildRouteTreeConfig(nodes: Array<RouteNode>, disableTypes: boolean, depth?: number): Array<string>; | ||
| export declare function buildImportString(importDeclaration: ImportDeclaration): string; | ||
| export declare function lowerCaseFirstChar(value: string): string; | ||
| export declare function mergeImportDeclarations(imports: Array<ImportDeclaration>): Array<ImportDeclaration>; | ||
@@ -215,3 +192,2 @@ export declare const findParent: (node: RouteNode | undefined) => string; | ||
| }): string; | ||
| export declare function getImportPath(node: RouteNode, config: Config, generatedRouteTreePath: string): string; | ||
| export declare function getImportForRouteNode(node: RouteNode, config: Config, generatedRouteTreePath: string, root: string): ImportDeclaration; |
+1
-15
@@ -14,3 +14,2 @@ import "./filesystem/physical/rootPathId.js"; | ||
| this.prefixToRoute = /* @__PURE__ */ new Map(); | ||
| this.layoutRoutes = []; | ||
| for (const route of routes) { | ||
@@ -20,5 +19,3 @@ if (!route.routePath || route.routePath === `/__root`) continue; | ||
| this.prefixToRoute.set(route.routePath, route); | ||
| if (route._fsRouteType === "pathless_layout" || route._fsRouteType === "layout" || route._fsRouteType === "__root") this.layoutRoutes.push(route); | ||
| } | ||
| this.layoutRoutes.sort((a, b) => (b.routePath?.length ?? 0) - (a.routePath?.length ?? 0)); | ||
| } | ||
@@ -405,13 +402,2 @@ /** | ||
| /** | ||
| * Checks if a given RouteNode is valid for augmenting it with typing based on conditions. | ||
| * Also asserts that the RouteNode is defined. | ||
| * | ||
| * @param routeNode - The RouteNode to check. | ||
| * @returns A boolean indicating whether the RouteNode is defined. | ||
| */ | ||
| function isRouteNodeValidForAugmentation(routeNode) { | ||
| if (!routeNode || routeNode.isVirtual) return false; | ||
| return true; | ||
| } | ||
| /** | ||
| * Infers the path for use by TS | ||
@@ -610,4 +596,4 @@ */ | ||
| //#endregion | ||
| export { RoutePrefixMap, buildFileRoutesByPathInterface, buildImportString, buildRouteTreeConfig, capitalize, checkFileExists, checkRouteFullPathUniqueness, cleanPath, createRouteNodesByFullPath, createRouteNodesById, createRouteNodesByTo, createTokenRegex, determineInitialRoutePath, determineNodePath, escapeRegExp, findParent, format, getImportForRouteNode, getImportPath, getResolvedRouteNodeVariableName, hasEscapedLeadingUnderscore, hasParentRoute, inferFullPath, isRouteNodeValidForAugmentation, isSegmentPathless, mergeImportDeclarations, multiSortBy, removeExt, removeGroups, removeLastSegmentFromPath, removeLayoutSegmentsWithEscape, removeLeadingSlash, removeTrailingSlash, removeUnderscores, removeUnderscoresWithEscape, replaceBackslash, resetRegex, routePathToVariable, trimPathLeft, unwrapBracketWrappedSegment, writeIfDifferent }; | ||
| export { RoutePrefixMap, buildFileRoutesByPathInterface, buildImportString, buildRouteTreeConfig, capitalize, checkFileExists, checkRouteFullPathUniqueness, cleanPath, createRouteNodesByFullPath, createRouteNodesById, createRouteNodesByTo, createTokenRegex, determineInitialRoutePath, determineNodePath, escapeRegExp, findParent, format, getImportForRouteNode, getResolvedRouteNodeVariableName, hasEscapedLeadingUnderscore, hasParentRoute, inferFullPath, isSegmentPathless, mergeImportDeclarations, multiSortBy, removeExt, removeGroups, removeLastSegmentFromPath, removeLayoutSegmentsWithEscape, removeLeadingSlash, removeTrailingSlash, removeUnderscores, removeUnderscoresWithEscape, replaceBackslash, resetRegex, routePathToVariable, trimPathLeft, unwrapBracketWrappedSegment, writeIfDifferent }; | ||
| //# sourceMappingURL=utils.js.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"utils.js","names":[],"sources":["../../src/utils.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/prefer-for-of */\nimport * as fsp from 'node:fs/promises'\nimport path from 'node:path'\nimport * as prettier from 'prettier'\nimport { rootPathId } from './filesystem/physical/rootPathId'\nimport type { Config, TokenMatcher } from './config'\nimport type { ImportDeclaration, RouteNode } from './types'\n\n/**\n * Prefix map for O(1) parent route lookups.\n * Maps each route path prefix to the route node that owns that prefix.\n * Enables finding longest matching parent without linear search.\n */\nexport class RoutePrefixMap {\n private prefixToRoute: Map<string, RouteNode> = new Map()\n private layoutRoutes: Array<RouteNode> = []\n\n constructor(routes: Array<RouteNode>) {\n for (const route of routes) {\n if (!route.routePath || route.routePath === `/${rootPathId}`) continue\n\n // Skip route pieces (lazy, loader, component, etc.) - they are merged with main routes\n // and should not be valid parent candidates\n if (\n route._fsRouteType === 'lazy' ||\n route._fsRouteType === 'loader' ||\n route._fsRouteType === 'component' ||\n route._fsRouteType === 'pendingComponent' ||\n route._fsRouteType === 'errorComponent' ||\n route._fsRouteType === 'notFoundComponent'\n ) {\n continue\n }\n\n // Index by exact path for direct lookups\n this.prefixToRoute.set(route.routePath, route)\n\n if (\n route._fsRouteType === 'pathless_layout' ||\n route._fsRouteType === 'layout' ||\n route._fsRouteType === '__root'\n ) {\n this.layoutRoutes.push(route)\n }\n }\n\n // Sort by path length descending for longest-match-first\n this.layoutRoutes.sort(\n (a, b) => (b.routePath?.length ?? 0) - (a.routePath?.length ?? 0),\n )\n }\n\n /**\n * Find the longest matching parent route for a given path.\n * O(k) where k is the number of path segments, not O(n) routes.\n */\n findParent(routePath: string): RouteNode | null {\n if (!routePath || routePath === '/') return null\n\n // Walk up the path segments\n let searchPath = routePath\n while (searchPath.length > 0) {\n const lastSlash = searchPath.lastIndexOf('/')\n if (lastSlash <= 0) break\n\n searchPath = searchPath.substring(0, lastSlash)\n const parent = this.prefixToRoute.get(searchPath)\n if (parent && parent.routePath !== routePath) {\n return parent\n }\n }\n return null\n }\n\n /**\n * Check if a route exists at the given path.\n */\n has(routePath: string): boolean {\n return this.prefixToRoute.has(routePath)\n }\n\n /**\n * Get a route by exact path.\n */\n get(routePath: string): RouteNode | undefined {\n return this.prefixToRoute.get(routePath)\n }\n}\n\nexport function multiSortBy<T>(\n arr: Array<T>,\n accessors: Array<(item: T) => any> = [(d) => d],\n): Array<T> {\n const len = arr.length\n // Pre-compute all accessor values to avoid repeated function calls during sort\n const indexed: Array<{ item: T; index: number; keys: Array<any> }> =\n new Array(len)\n for (let i = 0; i < len; i++) {\n const item = arr[i]!\n const keys = new Array(accessors.length)\n for (let j = 0; j < accessors.length; j++) {\n keys[j] = accessors[j]!(item)\n }\n indexed[i] = { item, index: i, keys }\n }\n\n indexed.sort((a, b) => {\n for (let j = 0; j < accessors.length; j++) {\n const ao = a.keys[j]\n const bo = b.keys[j]\n\n if (typeof ao === 'undefined') {\n if (typeof bo === 'undefined') {\n continue\n }\n return 1\n }\n\n if (ao === bo) {\n continue\n }\n\n return ao > bo ? 1 : -1\n }\n\n return a.index - b.index\n })\n\n const result: Array<T> = new Array(len)\n for (let i = 0; i < len; i++) {\n result[i] = indexed[i]!.item\n }\n return result\n}\n\nexport function cleanPath(path: string) {\n // remove double slashes\n return path.replace(/\\/{2,}/g, '/')\n}\n\nexport function trimPathLeft(path: string) {\n return path === '/' ? path : path.replace(/^\\/{1,}/, '')\n}\n\nexport function removeLeadingSlash(path: string): string {\n return path.replace(/^\\//, '')\n}\n\nexport function removeTrailingSlash(s: string) {\n return s.replace(/\\/$/, '')\n}\n\nconst BRACKET_CONTENT_RE = /\\[(.*?)\\]/g\nconst SPLIT_REGEX = /(?<!\\[)\\.(?!\\])/g\n\n/**\n * Characters that cannot be escaped in square brackets.\n * These are characters that would cause issues in URLs or file systems.\n */\nconst DISALLOWED_ESCAPE_CHARS = new Set([\n '/',\n '\\\\',\n '?',\n '#',\n ':',\n '*',\n '<',\n '>',\n '|',\n '!',\n '$',\n '%',\n])\n\nexport function determineInitialRoutePath(routePath: string) {\n const originalRoutePath =\n cleanPath(\n `/${(cleanPath(routePath) || '').split(SPLIT_REGEX).join('/')}`,\n ) || ''\n\n const parts = routePath.split(SPLIT_REGEX)\n\n // Escape any characters that in square brackets\n // we keep the original path untouched\n const escapedParts = parts.map((part) => {\n // Check if any disallowed characters are used in brackets\n\n let match\n while ((match = BRACKET_CONTENT_RE.exec(part)) !== null) {\n const character = match[1]\n if (character === undefined) continue\n if (DISALLOWED_ESCAPE_CHARS.has(character)) {\n console.error(\n `Error: Disallowed character \"${character}\" found in square brackets in route path \"${routePath}\".\\nYou cannot use any of the following characters in square brackets: ${Array.from(\n DISALLOWED_ESCAPE_CHARS,\n ).join(', ')}\\nPlease remove and/or replace them.`,\n )\n process.exit(1)\n }\n }\n\n // Since this split segment is safe at this point, we can\n // remove the brackets and replace them with the content inside\n return part.replace(BRACKET_CONTENT_RE, '$1')\n })\n\n // If the syntax for prefix/suffix is different, from the path\n // matching internals of router-core, we'd perform those changes here\n // on the `escapedParts` array before it is joined back together in\n // `final`\n\n const final = cleanPath(`/${escapedParts.join('/')}`) || ''\n\n return {\n routePath: final,\n originalRoutePath,\n }\n}\n\n/**\n * Checks if a segment is fully escaped (entirely wrapped in brackets with no nested brackets).\n * E.g., \"[index]\" -> true, \"[_layout]\" -> true, \"foo[.]bar\" -> false, \"index\" -> false\n */\nfunction isFullyEscapedSegment(originalSegment: string): boolean {\n return (\n originalSegment.startsWith('[') &&\n originalSegment.endsWith(']') &&\n !originalSegment.slice(1, -1).includes('[') &&\n !originalSegment.slice(1, -1).includes(']')\n )\n}\n\n/**\n * Checks if the leading underscore in a segment is escaped.\n * Returns true if:\n * - Segment starts with [_] pattern: \"[_]layout\" -> \"_layout\"\n * - Segment is fully escaped and content starts with _: \"[_1nd3x]\" -> \"_1nd3x\"\n */\nexport function hasEscapedLeadingUnderscore(originalSegment: string): boolean {\n // Pattern: [_]something or [_something]\n return (\n originalSegment.startsWith('[_]') ||\n (originalSegment.startsWith('[_') && isFullyEscapedSegment(originalSegment))\n )\n}\n\n/**\n * Checks if the trailing underscore in a segment is escaped.\n * Returns true if:\n * - Segment ends with [_] pattern: \"blog[_]\" -> \"blog_\"\n * - Segment is fully escaped and content ends with _: \"[_r0ut3_]\" -> \"_r0ut3_\"\n */\nexport function hasEscapedTrailingUnderscore(originalSegment: string): boolean {\n // Pattern: something[_] or [something_]\n return (\n originalSegment.endsWith('[_]') ||\n (originalSegment.endsWith('_]') && isFullyEscapedSegment(originalSegment))\n )\n}\n\nconst backslashRegex = /\\\\/g\n\nexport function replaceBackslash(s: string) {\n return s.replace(backslashRegex, '/')\n}\n\nconst alphanumericRegex = /[a-zA-Z0-9_]/\nconst splatSlashRegex = /\\/\\$\\//g\nconst trailingSplatRegex = /\\$$/g\nconst bracketSplatRegex = /\\$\\{\\$\\}/g\nconst dollarSignRegex = /\\$/g\nconst splitPathRegex = /[/-]/g\nconst leadingDigitRegex = /^(\\d)/g\n\nconst toVariableSafeChar = (char: string): string => {\n if (alphanumericRegex.test(char)) {\n return char // Keep alphanumeric characters and underscores as is\n }\n\n // Replace special characters with meaningful text equivalents\n switch (char) {\n case '.':\n return 'Dot'\n case '-':\n return 'Dash'\n case '@':\n return 'At'\n case '(':\n return '' // Removed since route groups use parentheses\n case ')':\n return '' // Removed since route groups use parentheses\n case ' ':\n return '' // Remove spaces\n default:\n return `Char${char.charCodeAt(0)}` // For any other characters\n }\n}\n\nexport function routePathToVariable(routePath: string): string {\n const cleaned = removeUnderscores(routePath)\n if (!cleaned) return ''\n\n const parts = cleaned\n .replace(splatSlashRegex, '/splat/')\n .replace(trailingSplatRegex, 'splat')\n .replace(bracketSplatRegex, 'splat')\n .replace(dollarSignRegex, '')\n .split(splitPathRegex)\n\n let result = ''\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i]!\n const segment = i > 0 ? capitalize(part) : part\n for (let j = 0; j < segment.length; j++) {\n result += toVariableSafeChar(segment[j]!)\n }\n }\n\n return result.replace(leadingDigitRegex, 'R$1')\n}\n\nconst underscoreStartEndRegex = /(^_|_$)/gi\nconst underscoreSlashRegex = /(\\/_|_\\/)/gi\n\nexport function removeUnderscores(s?: string) {\n return s\n ?.replace(underscoreStartEndRegex, '')\n .replace(underscoreSlashRegex, '/')\n}\n\n/**\n * Removes underscores from a path, but preserves underscores that were escaped\n * in the original path (indicated by [_] syntax).\n *\n * @param routePath - The path with brackets removed\n * @param originalPath - The original path that may contain [_] escape sequences\n * @returns The path with non-escaped underscores removed\n */\nexport function removeUnderscoresWithEscape(\n routePath?: string,\n originalPath?: string,\n): string {\n if (!routePath) return ''\n if (!originalPath) return removeUnderscores(routePath) ?? ''\n\n const routeSegments = routePath.split('/')\n const originalSegments = originalPath.split('/')\n\n const newSegments = routeSegments.map((segment, i) => {\n const originalSegment = originalSegments[i] || ''\n\n // Check if leading underscore is escaped\n const leadingEscaped = hasEscapedLeadingUnderscore(originalSegment)\n // Check if trailing underscore is escaped\n const trailingEscaped = hasEscapedTrailingUnderscore(originalSegment)\n\n let result = segment\n\n // Remove leading underscore only if not escaped\n if (result.startsWith('_') && !leadingEscaped) {\n result = result.slice(1)\n }\n\n // Remove trailing underscore only if not escaped\n if (result.endsWith('_') && !trailingEscaped) {\n result = result.slice(0, -1)\n }\n\n return result\n })\n\n return newSegments.join('/')\n}\n\n/**\n * Removes layout segments (segments starting with underscore) from a path,\n * but preserves segments where the underscore was escaped.\n *\n * @param routePath - The path with brackets removed\n * @param originalPath - The original path that may contain [_] escape sequences\n * @returns The path with non-escaped layout segments removed\n */\nexport function removeLayoutSegmentsWithEscape(\n routePath: string = '/',\n originalPath?: string,\n): string {\n if (!originalPath) return removeLayoutSegments(routePath)\n\n const routeSegments = routePath.split('/')\n const originalSegments = originalPath.split('/')\n\n // Keep segments that are NOT pathless (i.e., don't start with unescaped underscore)\n const newSegments = routeSegments.filter((segment, i) => {\n const originalSegment = originalSegments[i] || ''\n return !isSegmentPathless(segment, originalSegment)\n })\n\n return newSegments.join('/')\n}\n\n/**\n * Checks if a segment should be treated as a pathless/layout segment.\n * A segment is pathless if it starts with underscore and the underscore is not escaped.\n *\n * @param segment - The segment from routePath (brackets removed)\n * @param originalSegment - The segment from originalRoutePath (may contain brackets)\n * @returns true if the segment is pathless (has non-escaped leading underscore)\n */\nexport function isSegmentPathless(\n segment: string,\n originalSegment: string,\n): boolean {\n if (!segment.startsWith('_')) return false\n return !hasEscapedLeadingUnderscore(originalSegment)\n}\n\nexport function escapeRegExp(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\nfunction sanitizeTokenFlags(flags?: string): string | undefined {\n if (!flags) return flags\n\n // Prevent stateful behavior with RegExp.prototype.test/exec\n // g = global, y = sticky\n return flags.replace(/[gy]/g, '')\n}\n\nexport function createTokenRegex(\n token: TokenMatcher,\n opts: {\n type: 'segment' | 'filename'\n },\n): RegExp {\n // Defensive check: if token is undefined/null, throw a clear error\n // (runtime safety for config loading edge cases)\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (token === undefined || token === null) {\n throw new Error(\n `createTokenRegex: token is ${token}. This usually means the config was not properly parsed with defaults.`,\n )\n }\n\n try {\n if (typeof token === 'string') {\n return opts.type === 'segment'\n ? new RegExp(`^${escapeRegExp(token)}$`)\n : new RegExp(`[./]${escapeRegExp(token)}[.]`)\n }\n\n if (token instanceof RegExp) {\n const flags = sanitizeTokenFlags(token.flags)\n return opts.type === 'segment'\n ? new RegExp(`^(?:${token.source})$`, flags)\n : new RegExp(`[./](?:${token.source})[.]`, flags)\n }\n\n // Handle JSON regex object form: { regex: string, flags?: string }\n if (typeof token === 'object' && 'regex' in token) {\n const flags = sanitizeTokenFlags(token.flags)\n return opts.type === 'segment'\n ? new RegExp(`^(?:${token.regex})$`, flags)\n : new RegExp(`[./](?:${token.regex})[.]`, flags)\n }\n\n throw new Error(\n `createTokenRegex: invalid token type. Expected string, RegExp, or { regex, flags } object, got: ${typeof token}`,\n )\n } catch (e) {\n if (e instanceof SyntaxError) {\n const pattern =\n typeof token === 'string'\n ? token\n : token instanceof RegExp\n ? token.source\n : token.regex\n throw new Error(\n `Invalid regex pattern in token config: \"${pattern}\". ${e.message}`,\n )\n }\n throw e\n }\n}\n\nexport function isBracketWrappedSegment(segment: string): boolean {\n return segment.startsWith('[') && segment.endsWith(']')\n}\n\nexport function unwrapBracketWrappedSegment(segment: string): string {\n return isBracketWrappedSegment(segment) ? segment.slice(1, -1) : segment\n}\n\nexport function removeLeadingUnderscores(s: string, routeToken: string) {\n if (!s) return s\n\n const hasLeadingUnderscore = routeToken[0] === '_'\n\n const routeTokenToExclude = hasLeadingUnderscore\n ? routeToken.slice(1)\n : routeToken\n\n const escapedRouteToken = escapeRegExp(routeTokenToExclude)\n\n const leadingUnderscoreRegex = hasLeadingUnderscore\n ? new RegExp(`(?<=^|\\\\/)_(?!${escapedRouteToken})`, 'g')\n : new RegExp(`(?<=^|\\\\/)_`, 'g')\n\n return s.replaceAll(leadingUnderscoreRegex, '')\n}\n\nexport function removeTrailingUnderscores(s: string, routeToken: string) {\n if (!s) return s\n\n const hasTrailingUnderscore = routeToken.slice(-1) === '_'\n\n const routeTokenToExclude = hasTrailingUnderscore\n ? routeToken.slice(0, -1)\n : routeToken\n\n const escapedRouteToken = escapeRegExp(routeTokenToExclude)\n\n const trailingUnderscoreRegex = hasTrailingUnderscore\n ? new RegExp(`(?<!${escapedRouteToken})_(?=\\\\/|$)`, 'g')\n : new RegExp(`_(?=\\\\/)|_$`, 'g')\n\n return s.replaceAll(trailingUnderscoreRegex, '')\n}\n\nexport function capitalize(s: string) {\n if (typeof s !== 'string') return ''\n return s.charAt(0).toUpperCase() + s.slice(1)\n}\n\nexport function removeExt(d: string, addExtensions: boolean | string = false) {\n if (typeof addExtensions === 'string') {\n const dotIndex = d.lastIndexOf('.')\n if (dotIndex === -1) return d\n return d.substring(0, dotIndex) + addExtensions\n }\n return addExtensions ? d : d.substring(0, d.lastIndexOf('.')) || d\n}\n\n/**\n * This function writes to a file if the content is different.\n *\n * @param filepath The path to the file\n * @param content Original content\n * @param incomingContent New content\n * @param callbacks Callbacks to run before and after writing\n * @returns Whether the file was written\n */\nexport async function writeIfDifferent(\n filepath: string,\n content: string,\n incomingContent: string,\n callbacks?: { beforeWrite?: () => void; afterWrite?: () => void },\n): Promise<boolean> {\n if (content !== incomingContent) {\n callbacks?.beforeWrite?.()\n await fsp.writeFile(filepath, incomingContent)\n callbacks?.afterWrite?.()\n return true\n }\n return false\n}\n\n/**\n * This function formats the source code using the default formatter (Prettier).\n *\n * @param source The content to format\n * @param config The configuration object\n * @returns The formatted content\n */\nexport async function format(\n source: string,\n config: {\n quoteStyle: 'single' | 'double'\n semicolons: boolean\n },\n): Promise<string> {\n const prettierOptions: prettier.Config = {\n semi: config.semicolons,\n singleQuote: config.quoteStyle === 'single',\n parser: 'typescript',\n }\n return prettier.format(source, prettierOptions)\n}\n\n/**\n * This function resets the regex index to 0 so that it can be reused\n * without having to create a new regex object or worry about the last\n * state when using the global flag.\n *\n * @param regex The regex object to reset\n * @returns\n */\nexport function resetRegex(regex: RegExp) {\n regex.lastIndex = 0\n return\n}\n\n/**\n * This function checks if a file exists.\n *\n * @param file The path to the file\n * @returns Whether the file exists\n */\nexport async function checkFileExists(file: string) {\n try {\n await fsp.access(file, fsp.constants.F_OK)\n return true\n } catch {\n return false\n }\n}\n\nconst possiblyNestedRouteGroupPatternRegex = /\\([^/]+\\)\\/?/g\nexport function removeGroups(s: string) {\n return s.replace(possiblyNestedRouteGroupPatternRegex, '')\n}\n\n/**\n * Removes all segments from a given path that start with an underscore ('_').\n *\n * @param {string} routePath - The path from which to remove segments. Defaults to '/'.\n * @returns {string} The path with all underscore-prefixed segments removed.\n * @example\n * removeLayoutSegments('/workspace/_auth/foo') // '/workspace/foo'\n */\nexport function removeLayoutSegments(routePath: string = '/'): string {\n const segments = routePath.split('/')\n const newSegments = segments.filter((segment) => !segment.startsWith('_'))\n return newSegments.join('/')\n}\n\n/**\n * The `node.path` is used as the `id` in the route definition.\n * This function checks if the given node has a parent and if so, it determines the correct path for the given node.\n * @param node - The node to determine the path for.\n * @returns The correct path for the given node.\n */\nexport function determineNodePath(node: RouteNode) {\n return (node.path = node.parent\n ? node.routePath?.replace(node.parent.routePath ?? '', '') || '/'\n : node.routePath)\n}\n\n/**\n * Removes the last segment from a given path. Segments are considered to be separated by a '/'.\n *\n * @param {string} routePath - The path from which to remove the last segment. Defaults to '/'.\n * @returns {string} The path with the last segment removed.\n * @example\n * removeLastSegmentFromPath('/workspace/_auth/foo') // '/workspace/_auth'\n */\nexport function removeLastSegmentFromPath(routePath: string = '/'): string {\n const segments = routePath.split('/')\n segments.pop() // Remove the last segment\n return segments.join('/')\n}\n\n/**\n * Find parent route using RoutePrefixMap for O(k) lookups instead of O(n).\n */\nexport function hasParentRoute(\n prefixMap: RoutePrefixMap,\n node: RouteNode,\n routePathToCheck: string | undefined,\n): RouteNode | null {\n if (!routePathToCheck || routePathToCheck === '/') {\n return null\n }\n\n return prefixMap.findParent(routePathToCheck)\n}\n\n/**\n * Gets the final variable name for a route\n */\nexport const getResolvedRouteNodeVariableName = (\n routeNode: RouteNode,\n): string => {\n return routeNode.children?.length\n ? `${routeNode.variableName}RouteWithChildren`\n : `${routeNode.variableName}Route`\n}\n\n/**\n * Checks if a given RouteNode is valid for augmenting it with typing based on conditions.\n * Also asserts that the RouteNode is defined.\n *\n * @param routeNode - The RouteNode to check.\n * @returns A boolean indicating whether the RouteNode is defined.\n */\nexport function isRouteNodeValidForAugmentation(\n routeNode?: RouteNode,\n): routeNode is RouteNode {\n if (!routeNode || routeNode.isVirtual) {\n return false\n }\n return true\n}\n\n/**\n * Infers the path for use by TS\n */\nexport const inferPath = (routeNode: RouteNode): string => {\n if (routeNode.cleanedPath === '/') {\n return routeNode.cleanedPath ?? ''\n }\n return routeNode.cleanedPath?.replace(/\\/$/, '') ?? ''\n}\n\n/**\n * Infers the full path for use by TS\n */\nexport const inferFullPath = (routeNode: RouteNode): string => {\n const fullPath = removeGroups(\n removeUnderscoresWithEscape(\n removeLayoutSegmentsWithEscape(\n routeNode.routePath,\n routeNode.originalRoutePath,\n ),\n routeNode.originalRoutePath,\n ),\n )\n\n if (fullPath === '') {\n return '/'\n }\n\n // Preserve trailing slash for index routes (routePath ends with '/')\n // This ensures types match runtime behavior\n const isIndexRoute = routeNode.routePath?.endsWith('/')\n if (isIndexRoute) {\n return fullPath\n }\n\n return fullPath.replace(/\\/$/, '')\n}\n\nconst shouldPreferIndexRoute = (\n current: RouteNode,\n existing: RouteNode,\n): boolean => {\n return existing.cleanedPath === '/' && current.cleanedPath !== '/'\n}\n\n/**\n * Creates a map from fullPath to routeNode\n */\nexport const createRouteNodesByFullPath = (\n routeNodes: Array<RouteNode>,\n): Map<string, RouteNode> => {\n const map = new Map<string, RouteNode>()\n\n for (const routeNode of routeNodes) {\n const fullPath = inferFullPath(routeNode)\n\n if (fullPath === '/' && map.has('/')) {\n const existing = map.get('/')!\n if (shouldPreferIndexRoute(routeNode, existing)) {\n continue\n }\n }\n\n map.set(fullPath, routeNode)\n }\n\n return map\n}\n\n/**\n * Create a map from 'to' to a routeNode\n */\nexport const createRouteNodesByTo = (\n routeNodes: Array<RouteNode>,\n): Map<string, RouteNode> => {\n const map = new Map<string, RouteNode>()\n\n for (const routeNode of dedupeBranchesAndIndexRoutes(routeNodes)) {\n const to = inferTo(routeNode)\n\n if (to === '/' && map.has('/')) {\n const existing = map.get('/')!\n if (shouldPreferIndexRoute(routeNode, existing)) {\n continue\n }\n }\n\n map.set(to, routeNode)\n }\n\n return map\n}\n\n/**\n * Create a map from 'id' to a routeNode\n */\nexport const createRouteNodesById = (\n routeNodes: Array<RouteNode>,\n): Map<string, RouteNode> => {\n return new Map(\n routeNodes.map((routeNode) => {\n const id = routeNode.routePath ?? ''\n return [id, routeNode]\n }),\n )\n}\n\n/**\n * Infers to path\n */\nexport const inferTo = (routeNode: RouteNode): string => {\n const fullPath = inferFullPath(routeNode)\n\n if (fullPath === '/') return fullPath\n\n return fullPath.replace(/\\/$/, '')\n}\n\n/**\n * Dedupes branches and index routes\n */\nexport const dedupeBranchesAndIndexRoutes = (\n routes: Array<RouteNode>,\n): Array<RouteNode> => {\n return routes.filter((route) => {\n if (route.children?.find((child) => child.cleanedPath === '/')) return false\n return true\n })\n}\n\nfunction checkUnique<TElement>(routes: Array<TElement>, key: keyof TElement) {\n // Check no two routes have the same `key`\n // if they do, throw an error with the conflicting filePaths\n const keys = routes.map((d) => d[key])\n const uniqueKeys = new Set(keys)\n if (keys.length !== uniqueKeys.size) {\n const duplicateKeys = keys.filter((d, i) => keys.indexOf(d) !== i)\n const conflictingFiles = routes.filter((d) =>\n duplicateKeys.includes(d[key]),\n )\n return conflictingFiles\n }\n return undefined\n}\n\nexport function checkRouteFullPathUniqueness(\n _routes: Array<RouteNode>,\n config: Config,\n) {\n const emptyPathRoutes = _routes.filter((d) => d.routePath === '')\n if (emptyPathRoutes.length) {\n const errorMessage = `Invalid route path \"\" was found. Root routes must be defined via __root.tsx (createRootRoute), not createFileRoute('') or a route file that resolves to an empty path.\nConflicting files: \\n ${emptyPathRoutes\n .map((d) => path.resolve(config.routesDirectory, d.filePath))\n .join('\\n ')}\\n`\n throw new Error(errorMessage)\n }\n\n const routes = _routes.map((d) => {\n const inferredFullPath = inferFullPath(d)\n return { ...d, inferredFullPath }\n })\n\n const conflictingFiles = checkUnique(routes, 'inferredFullPath')\n\n if (conflictingFiles !== undefined) {\n const errorMessage = `Conflicting configuration paths were found for the following route${conflictingFiles.length > 1 ? 's' : ''}: ${conflictingFiles\n .map((p) => `\"${p.inferredFullPath}\"`)\n .join(', ')}.\nPlease ensure each Route has a unique full path.\nConflicting files: \\n ${conflictingFiles.map((d) => path.resolve(config.routesDirectory, d.filePath)).join('\\n ')}\\n`\n throw new Error(errorMessage)\n }\n}\n\nexport function buildRouteTreeConfig(\n nodes: Array<RouteNode>,\n disableTypes: boolean,\n depth = 1,\n): Array<string> {\n const children = nodes.map((node) => {\n if (node._fsRouteType === '__root') {\n return\n }\n\n if (node._fsRouteType === 'pathless_layout' && !node.children?.length) {\n return\n }\n\n const route = `${node.variableName}`\n\n if (node.children?.length) {\n const childConfigs = buildRouteTreeConfig(\n node.children,\n disableTypes,\n depth + 1,\n )\n\n const childrenDeclaration = disableTypes\n ? ''\n : `interface ${route}RouteChildren {\n ${node.children\n .map(\n (child) =>\n `${child.variableName}Route: typeof ${getResolvedRouteNodeVariableName(child)}`,\n )\n .join(',')}\n}`\n\n const children = `const ${route}RouteChildren${disableTypes ? '' : `: ${route}RouteChildren`} = {\n ${node.children\n .map(\n (child) =>\n `${child.variableName}Route: ${getResolvedRouteNodeVariableName(child)}`,\n )\n .join(',')}\n}`\n\n const routeWithChildren = `const ${route}RouteWithChildren = ${route}Route._addFileChildren(${route}RouteChildren)`\n\n return [\n childConfigs.join('\\n'),\n childrenDeclaration,\n children,\n routeWithChildren,\n ].join('\\n\\n')\n }\n\n return undefined\n })\n\n return children.filter((x) => x !== undefined)\n}\n\nexport function buildImportString(\n importDeclaration: ImportDeclaration,\n): string {\n const { source, specifiers, importKind } = importDeclaration\n return specifiers.length\n ? `import ${importKind === 'type' ? 'type ' : ''}{ ${specifiers.map((s) => (s.local ? `${s.imported} as ${s.local}` : s.imported)).join(', ')} } from '${source}'`\n : ''\n}\n\nexport function lowerCaseFirstChar(value: string) {\n if (!value[0]) {\n return value\n }\n\n return value[0].toLowerCase() + value.slice(1)\n}\n\nexport function mergeImportDeclarations(\n imports: Array<ImportDeclaration>,\n): Array<ImportDeclaration> {\n const merged = new Map<string, ImportDeclaration>()\n\n for (const imp of imports) {\n const key = `${imp.source}-${imp.importKind ?? ''}`\n let existing = merged.get(key)\n if (!existing) {\n existing = { ...imp, specifiers: [] }\n merged.set(key, existing)\n }\n\n const existingSpecs = existing.specifiers\n for (const specifier of imp.specifiers) {\n let found = false\n for (let i = 0; i < existingSpecs.length; i++) {\n const e = existingSpecs[i]!\n if (e.imported === specifier.imported && e.local === specifier.local) {\n found = true\n break\n }\n }\n if (!found) {\n existingSpecs.push(specifier)\n }\n }\n }\n\n return [...merged.values()]\n}\n\nexport const findParent = (node: RouteNode | undefined): string => {\n if (!node) {\n return `rootRouteImport`\n }\n if (node.parent) {\n return `${node.parent.variableName}Route`\n }\n return findParent(node.parent)\n}\n\nexport function buildFileRoutesByPathInterface(opts: {\n routeNodes: Array<RouteNode>\n module: string\n interfaceName: string\n config?: Pick<Config, 'routeToken'>\n}): string {\n return `declare module '${opts.module}' {\n interface ${opts.interfaceName} {\n ${opts.routeNodes\n .map((routeNode) => {\n const filePathId = routeNode.routePath\n const preloaderRoute = `typeof ${routeNode.variableName}RouteImport`\n\n const parent = findParent(routeNode)\n\n return `'${filePathId}': {\n id: '${filePathId}'\n path: '${inferPath(routeNode)}'\n fullPath: '${inferFullPath(routeNode)}'\n preLoaderRoute: ${preloaderRoute}\n parentRoute: typeof ${parent}\n }`\n })\n .join('\\n')}\n }\n}`\n}\n\nexport function getImportPath(\n node: RouteNode,\n config: Config,\n generatedRouteTreePath: string,\n): string {\n return replaceBackslash(\n removeExt(\n path.relative(\n path.dirname(generatedRouteTreePath),\n path.resolve(config.routesDirectory, node.filePath),\n ),\n config.addExtensions,\n ),\n )\n}\n\nexport function getImportForRouteNode(\n node: RouteNode,\n config: Config,\n generatedRouteTreePath: string,\n root: string,\n): ImportDeclaration {\n let source = ''\n if (config.importRoutesUsingAbsolutePaths) {\n source = replaceBackslash(\n removeExt(\n path.resolve(root, config.routesDirectory, node.filePath),\n config.addExtensions,\n ),\n )\n } else {\n source = `./${getImportPath(node, config, generatedRouteTreePath)}`\n }\n return {\n source,\n specifiers: [\n {\n imported: 'Route',\n local: `${node.variableName}RouteImport`,\n },\n ],\n } satisfies ImportDeclaration\n}\n"],"mappings":"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"} | ||
| {"version":3,"file":"utils.js","names":[],"sources":["../../src/utils.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/prefer-for-of */\nimport * as fsp from 'node:fs/promises'\nimport path from 'node:path'\nimport * as prettier from 'prettier'\nimport { rootPathId } from './filesystem/physical/rootPathId'\nimport type { Config, TokenMatcher } from './config'\nimport type { ImportDeclaration, RouteNode } from './types'\n\n/**\n * Prefix map for O(1) parent route lookups.\n * Maps each route path prefix to the route node that owns that prefix.\n * Enables finding longest matching parent without linear search.\n */\nexport class RoutePrefixMap {\n private prefixToRoute: Map<string, RouteNode> = new Map()\n\n constructor(routes: Array<RouteNode>) {\n for (const route of routes) {\n if (!route.routePath || route.routePath === `/${rootPathId}`) continue\n\n // Skip route pieces (lazy, loader, component, etc.) - they are merged with main routes\n // and should not be valid parent candidates\n if (\n route._fsRouteType === 'lazy' ||\n route._fsRouteType === 'loader' ||\n route._fsRouteType === 'component' ||\n route._fsRouteType === 'pendingComponent' ||\n route._fsRouteType === 'errorComponent' ||\n route._fsRouteType === 'notFoundComponent'\n ) {\n continue\n }\n\n // Index by exact path for direct lookups\n this.prefixToRoute.set(route.routePath, route)\n }\n }\n\n /**\n * Find the longest matching parent route for a given path.\n * O(k) where k is the number of path segments, not O(n) routes.\n */\n findParent(routePath: string): RouteNode | null {\n if (!routePath || routePath === '/') return null\n\n // Walk up the path segments\n let searchPath = routePath\n while (searchPath.length > 0) {\n const lastSlash = searchPath.lastIndexOf('/')\n if (lastSlash <= 0) break\n\n searchPath = searchPath.substring(0, lastSlash)\n const parent = this.prefixToRoute.get(searchPath)\n if (parent && parent.routePath !== routePath) {\n return parent\n }\n }\n return null\n }\n\n /**\n * Check if a route exists at the given path.\n */\n has(routePath: string): boolean {\n return this.prefixToRoute.has(routePath)\n }\n\n /**\n * Get a route by exact path.\n */\n get(routePath: string): RouteNode | undefined {\n return this.prefixToRoute.get(routePath)\n }\n}\n\nexport function multiSortBy<T>(\n arr: Array<T>,\n accessors: Array<(item: T) => any> = [(d) => d],\n): Array<T> {\n const len = arr.length\n // Pre-compute all accessor values to avoid repeated function calls during sort\n const indexed: Array<{ item: T; index: number; keys: Array<any> }> =\n new Array(len)\n for (let i = 0; i < len; i++) {\n const item = arr[i]!\n const keys = new Array(accessors.length)\n for (let j = 0; j < accessors.length; j++) {\n keys[j] = accessors[j]!(item)\n }\n indexed[i] = { item, index: i, keys }\n }\n\n indexed.sort((a, b) => {\n for (let j = 0; j < accessors.length; j++) {\n const ao = a.keys[j]\n const bo = b.keys[j]\n\n if (typeof ao === 'undefined') {\n if (typeof bo === 'undefined') {\n continue\n }\n return 1\n }\n\n if (ao === bo) {\n continue\n }\n\n return ao > bo ? 1 : -1\n }\n\n return a.index - b.index\n })\n\n const result: Array<T> = new Array(len)\n for (let i = 0; i < len; i++) {\n result[i] = indexed[i]!.item\n }\n return result\n}\n\nexport function cleanPath(path: string) {\n // remove double slashes\n return path.replace(/\\/{2,}/g, '/')\n}\n\nexport function trimPathLeft(path: string) {\n return path === '/' ? path : path.replace(/^\\/{1,}/, '')\n}\n\nexport function removeLeadingSlash(path: string): string {\n return path.replace(/^\\//, '')\n}\n\nexport function removeTrailingSlash(s: string) {\n return s.replace(/\\/$/, '')\n}\n\nconst BRACKET_CONTENT_RE = /\\[(.*?)\\]/g\nconst SPLIT_REGEX = /(?<!\\[)\\.(?!\\])/g\n\n/**\n * Characters that cannot be escaped in square brackets.\n * These are characters that would cause issues in URLs or file systems.\n */\nconst DISALLOWED_ESCAPE_CHARS = new Set([\n '/',\n '\\\\',\n '?',\n '#',\n ':',\n '*',\n '<',\n '>',\n '|',\n '!',\n '$',\n '%',\n])\n\nexport function determineInitialRoutePath(routePath: string) {\n const originalRoutePath =\n cleanPath(\n `/${(cleanPath(routePath) || '').split(SPLIT_REGEX).join('/')}`,\n ) || ''\n\n const parts = routePath.split(SPLIT_REGEX)\n\n // Escape any characters that in square brackets\n // we keep the original path untouched\n const escapedParts = parts.map((part) => {\n // Check if any disallowed characters are used in brackets\n\n let match\n while ((match = BRACKET_CONTENT_RE.exec(part)) !== null) {\n const character = match[1]\n if (character === undefined) continue\n if (DISALLOWED_ESCAPE_CHARS.has(character)) {\n console.error(\n `Error: Disallowed character \"${character}\" found in square brackets in route path \"${routePath}\".\\nYou cannot use any of the following characters in square brackets: ${Array.from(\n DISALLOWED_ESCAPE_CHARS,\n ).join(', ')}\\nPlease remove and/or replace them.`,\n )\n process.exit(1)\n }\n }\n\n // Since this split segment is safe at this point, we can\n // remove the brackets and replace them with the content inside\n return part.replace(BRACKET_CONTENT_RE, '$1')\n })\n\n // If the syntax for prefix/suffix is different, from the path\n // matching internals of router-core, we'd perform those changes here\n // on the `escapedParts` array before it is joined back together in\n // `final`\n\n const final = cleanPath(`/${escapedParts.join('/')}`) || ''\n\n return {\n routePath: final,\n originalRoutePath,\n }\n}\n\n/**\n * Checks if a segment is fully escaped (entirely wrapped in brackets with no nested brackets).\n * E.g., \"[index]\" -> true, \"[_layout]\" -> true, \"foo[.]bar\" -> false, \"index\" -> false\n */\nfunction isFullyEscapedSegment(originalSegment: string): boolean {\n return (\n originalSegment.startsWith('[') &&\n originalSegment.endsWith(']') &&\n !originalSegment.slice(1, -1).includes('[') &&\n !originalSegment.slice(1, -1).includes(']')\n )\n}\n\n/**\n * Checks if the leading underscore in a segment is escaped.\n * Returns true if:\n * - Segment starts with [_] pattern: \"[_]layout\" -> \"_layout\"\n * - Segment is fully escaped and content starts with _: \"[_1nd3x]\" -> \"_1nd3x\"\n */\nexport function hasEscapedLeadingUnderscore(originalSegment: string): boolean {\n // Pattern: [_]something or [_something]\n return (\n originalSegment.startsWith('[_]') ||\n (originalSegment.startsWith('[_') && isFullyEscapedSegment(originalSegment))\n )\n}\n\n/**\n * Checks if the trailing underscore in a segment is escaped.\n * Returns true if:\n * - Segment ends with [_] pattern: \"blog[_]\" -> \"blog_\"\n * - Segment is fully escaped and content ends with _: \"[_r0ut3_]\" -> \"_r0ut3_\"\n */\nexport function hasEscapedTrailingUnderscore(originalSegment: string): boolean {\n // Pattern: something[_] or [something_]\n return (\n originalSegment.endsWith('[_]') ||\n (originalSegment.endsWith('_]') && isFullyEscapedSegment(originalSegment))\n )\n}\n\nconst backslashRegex = /\\\\/g\n\nexport function replaceBackslash(s: string) {\n return s.replace(backslashRegex, '/')\n}\n\nconst alphanumericRegex = /[a-zA-Z0-9_]/\nconst splatSlashRegex = /\\/\\$\\//g\nconst trailingSplatRegex = /\\$$/g\nconst bracketSplatRegex = /\\$\\{\\$\\}/g\nconst dollarSignRegex = /\\$/g\nconst splitPathRegex = /[/-]/g\nconst leadingDigitRegex = /^(\\d)/g\n\nconst toVariableSafeChar = (char: string): string => {\n if (alphanumericRegex.test(char)) {\n return char // Keep alphanumeric characters and underscores as is\n }\n\n // Replace special characters with meaningful text equivalents\n switch (char) {\n case '.':\n return 'Dot'\n case '-':\n return 'Dash'\n case '@':\n return 'At'\n case '(':\n return '' // Removed since route groups use parentheses\n case ')':\n return '' // Removed since route groups use parentheses\n case ' ':\n return '' // Remove spaces\n default:\n return `Char${char.charCodeAt(0)}` // For any other characters\n }\n}\n\nexport function routePathToVariable(routePath: string): string {\n const cleaned = removeUnderscores(routePath)\n if (!cleaned) return ''\n\n const parts = cleaned\n .replace(splatSlashRegex, '/splat/')\n .replace(trailingSplatRegex, 'splat')\n .replace(bracketSplatRegex, 'splat')\n .replace(dollarSignRegex, '')\n .split(splitPathRegex)\n\n let result = ''\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i]!\n const segment = i > 0 ? capitalize(part) : part\n for (let j = 0; j < segment.length; j++) {\n result += toVariableSafeChar(segment[j]!)\n }\n }\n\n return result.replace(leadingDigitRegex, 'R$1')\n}\n\nconst underscoreStartEndRegex = /(^_|_$)/gi\nconst underscoreSlashRegex = /(\\/_|_\\/)/gi\n\nexport function removeUnderscores(s?: string) {\n return s\n ?.replace(underscoreStartEndRegex, '')\n .replace(underscoreSlashRegex, '/')\n}\n\n/**\n * Removes underscores from a path, but preserves underscores that were escaped\n * in the original path (indicated by [_] syntax).\n *\n * @param routePath - The path with brackets removed\n * @param originalPath - The original path that may contain [_] escape sequences\n * @returns The path with non-escaped underscores removed\n */\nexport function removeUnderscoresWithEscape(\n routePath?: string,\n originalPath?: string,\n): string {\n if (!routePath) return ''\n if (!originalPath) return removeUnderscores(routePath) ?? ''\n\n const routeSegments = routePath.split('/')\n const originalSegments = originalPath.split('/')\n\n const newSegments = routeSegments.map((segment, i) => {\n const originalSegment = originalSegments[i] || ''\n\n // Check if leading underscore is escaped\n const leadingEscaped = hasEscapedLeadingUnderscore(originalSegment)\n // Check if trailing underscore is escaped\n const trailingEscaped = hasEscapedTrailingUnderscore(originalSegment)\n\n let result = segment\n\n // Remove leading underscore only if not escaped\n if (result.startsWith('_') && !leadingEscaped) {\n result = result.slice(1)\n }\n\n // Remove trailing underscore only if not escaped\n if (result.endsWith('_') && !trailingEscaped) {\n result = result.slice(0, -1)\n }\n\n return result\n })\n\n return newSegments.join('/')\n}\n\n/**\n * Removes layout segments (segments starting with underscore) from a path,\n * but preserves segments where the underscore was escaped.\n *\n * @param routePath - The path with brackets removed\n * @param originalPath - The original path that may contain [_] escape sequences\n * @returns The path with non-escaped layout segments removed\n */\nexport function removeLayoutSegmentsWithEscape(\n routePath: string = '/',\n originalPath?: string,\n): string {\n if (!originalPath) return removeLayoutSegments(routePath)\n\n const routeSegments = routePath.split('/')\n const originalSegments = originalPath.split('/')\n\n // Keep segments that are NOT pathless (i.e., don't start with unescaped underscore)\n const newSegments = routeSegments.filter((segment, i) => {\n const originalSegment = originalSegments[i] || ''\n return !isSegmentPathless(segment, originalSegment)\n })\n\n return newSegments.join('/')\n}\n\n/**\n * Checks if a segment should be treated as a pathless/layout segment.\n * A segment is pathless if it starts with underscore and the underscore is not escaped.\n *\n * @param segment - The segment from routePath (brackets removed)\n * @param originalSegment - The segment from originalRoutePath (may contain brackets)\n * @returns true if the segment is pathless (has non-escaped leading underscore)\n */\nexport function isSegmentPathless(\n segment: string,\n originalSegment: string,\n): boolean {\n if (!segment.startsWith('_')) return false\n return !hasEscapedLeadingUnderscore(originalSegment)\n}\n\nexport function escapeRegExp(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\nfunction sanitizeTokenFlags(flags?: string): string | undefined {\n if (!flags) return flags\n\n // Prevent stateful behavior with RegExp.prototype.test/exec\n // g = global, y = sticky\n return flags.replace(/[gy]/g, '')\n}\n\nexport function createTokenRegex(\n token: TokenMatcher,\n opts: {\n type: 'segment' | 'filename'\n },\n): RegExp {\n // Defensive check: if token is undefined/null, throw a clear error\n // (runtime safety for config loading edge cases)\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (token === undefined || token === null) {\n throw new Error(\n `createTokenRegex: token is ${token}. This usually means the config was not properly parsed with defaults.`,\n )\n }\n\n try {\n if (typeof token === 'string') {\n return opts.type === 'segment'\n ? new RegExp(`^${escapeRegExp(token)}$`)\n : new RegExp(`[./]${escapeRegExp(token)}[.]`)\n }\n\n if (token instanceof RegExp) {\n const flags = sanitizeTokenFlags(token.flags)\n return opts.type === 'segment'\n ? new RegExp(`^(?:${token.source})$`, flags)\n : new RegExp(`[./](?:${token.source})[.]`, flags)\n }\n\n // Handle JSON regex object form: { regex: string, flags?: string }\n if (typeof token === 'object' && 'regex' in token) {\n const flags = sanitizeTokenFlags(token.flags)\n return opts.type === 'segment'\n ? new RegExp(`^(?:${token.regex})$`, flags)\n : new RegExp(`[./](?:${token.regex})[.]`, flags)\n }\n\n throw new Error(\n `createTokenRegex: invalid token type. Expected string, RegExp, or { regex, flags } object, got: ${typeof token}`,\n )\n } catch (e) {\n if (e instanceof SyntaxError) {\n const pattern =\n typeof token === 'string'\n ? token\n : token instanceof RegExp\n ? token.source\n : token.regex\n throw new Error(\n `Invalid regex pattern in token config: \"${pattern}\". ${e.message}`,\n )\n }\n throw e\n }\n}\n\nfunction isBracketWrappedSegment(segment: string): boolean {\n return segment.startsWith('[') && segment.endsWith(']')\n}\n\nexport function unwrapBracketWrappedSegment(segment: string): string {\n return isBracketWrappedSegment(segment) ? segment.slice(1, -1) : segment\n}\n\nexport function removeLeadingUnderscores(s: string, routeToken: string) {\n if (!s) return s\n\n const hasLeadingUnderscore = routeToken[0] === '_'\n\n const routeTokenToExclude = hasLeadingUnderscore\n ? routeToken.slice(1)\n : routeToken\n\n const escapedRouteToken = escapeRegExp(routeTokenToExclude)\n\n const leadingUnderscoreRegex = hasLeadingUnderscore\n ? new RegExp(`(?<=^|\\\\/)_(?!${escapedRouteToken})`, 'g')\n : new RegExp(`(?<=^|\\\\/)_`, 'g')\n\n return s.replaceAll(leadingUnderscoreRegex, '')\n}\n\nexport function removeTrailingUnderscores(s: string, routeToken: string) {\n if (!s) return s\n\n const hasTrailingUnderscore = routeToken.slice(-1) === '_'\n\n const routeTokenToExclude = hasTrailingUnderscore\n ? routeToken.slice(0, -1)\n : routeToken\n\n const escapedRouteToken = escapeRegExp(routeTokenToExclude)\n\n const trailingUnderscoreRegex = hasTrailingUnderscore\n ? new RegExp(`(?<!${escapedRouteToken})_(?=\\\\/|$)`, 'g')\n : new RegExp(`_(?=\\\\/)|_$`, 'g')\n\n return s.replaceAll(trailingUnderscoreRegex, '')\n}\n\nexport function capitalize(s: string) {\n if (typeof s !== 'string') return ''\n return s.charAt(0).toUpperCase() + s.slice(1)\n}\n\nexport function removeExt(d: string, addExtensions: boolean | string = false) {\n if (typeof addExtensions === 'string') {\n const dotIndex = d.lastIndexOf('.')\n if (dotIndex === -1) return d\n return d.substring(0, dotIndex) + addExtensions\n }\n return addExtensions ? d : d.substring(0, d.lastIndexOf('.')) || d\n}\n\n/**\n * This function writes to a file if the content is different.\n *\n * @param filepath The path to the file\n * @param content Original content\n * @param incomingContent New content\n * @param callbacks Callbacks to run before and after writing\n * @returns Whether the file was written\n */\nexport async function writeIfDifferent(\n filepath: string,\n content: string,\n incomingContent: string,\n callbacks?: { beforeWrite?: () => void; afterWrite?: () => void },\n): Promise<boolean> {\n if (content !== incomingContent) {\n callbacks?.beforeWrite?.()\n await fsp.writeFile(filepath, incomingContent)\n callbacks?.afterWrite?.()\n return true\n }\n return false\n}\n\n/**\n * This function formats the source code using the default formatter (Prettier).\n *\n * @param source The content to format\n * @param config The configuration object\n * @returns The formatted content\n */\nexport async function format(\n source: string,\n config: {\n quoteStyle: 'single' | 'double'\n semicolons: boolean\n },\n): Promise<string> {\n const prettierOptions: prettier.Config = {\n semi: config.semicolons,\n singleQuote: config.quoteStyle === 'single',\n parser: 'typescript',\n }\n return prettier.format(source, prettierOptions)\n}\n\n/**\n * This function resets the regex index to 0 so that it can be reused\n * without having to create a new regex object or worry about the last\n * state when using the global flag.\n *\n * @param regex The regex object to reset\n * @returns\n */\nexport function resetRegex(regex: RegExp) {\n regex.lastIndex = 0\n return\n}\n\n/**\n * This function checks if a file exists.\n *\n * @param file The path to the file\n * @returns Whether the file exists\n */\nexport async function checkFileExists(file: string) {\n try {\n await fsp.access(file, fsp.constants.F_OK)\n return true\n } catch {\n return false\n }\n}\n\nconst possiblyNestedRouteGroupPatternRegex = /\\([^/]+\\)\\/?/g\nexport function removeGroups(s: string) {\n return s.replace(possiblyNestedRouteGroupPatternRegex, '')\n}\n\n/**\n * Removes all segments from a given path that start with an underscore ('_').\n *\n * @param {string} routePath - The path from which to remove segments. Defaults to '/'.\n * @returns {string} The path with all underscore-prefixed segments removed.\n * @example\n * removeLayoutSegments('/workspace/_auth/foo') // '/workspace/foo'\n */\nexport function removeLayoutSegments(routePath: string = '/'): string {\n const segments = routePath.split('/')\n const newSegments = segments.filter((segment) => !segment.startsWith('_'))\n return newSegments.join('/')\n}\n\n/**\n * The `node.path` is used as the `id` in the route definition.\n * This function checks if the given node has a parent and if so, it determines the correct path for the given node.\n * @param node - The node to determine the path for.\n * @returns The correct path for the given node.\n */\nexport function determineNodePath(node: RouteNode) {\n return (node.path = node.parent\n ? node.routePath?.replace(node.parent.routePath ?? '', '') || '/'\n : node.routePath)\n}\n\n/**\n * Removes the last segment from a given path. Segments are considered to be separated by a '/'.\n *\n * @param {string} routePath - The path from which to remove the last segment. Defaults to '/'.\n * @returns {string} The path with the last segment removed.\n * @example\n * removeLastSegmentFromPath('/workspace/_auth/foo') // '/workspace/_auth'\n */\nexport function removeLastSegmentFromPath(routePath: string = '/'): string {\n const segments = routePath.split('/')\n segments.pop() // Remove the last segment\n return segments.join('/')\n}\n\n/**\n * Find parent route using RoutePrefixMap for O(k) lookups instead of O(n).\n */\nexport function hasParentRoute(\n prefixMap: RoutePrefixMap,\n node: RouteNode,\n routePathToCheck: string | undefined,\n): RouteNode | null {\n if (!routePathToCheck || routePathToCheck === '/') {\n return null\n }\n\n return prefixMap.findParent(routePathToCheck)\n}\n\n/**\n * Gets the final variable name for a route\n */\nexport const getResolvedRouteNodeVariableName = (\n routeNode: RouteNode,\n): string => {\n return routeNode.children?.length\n ? `${routeNode.variableName}RouteWithChildren`\n : `${routeNode.variableName}Route`\n}\n\n/**\n * Infers the path for use by TS\n */\nconst inferPath = (routeNode: RouteNode): string => {\n if (routeNode.cleanedPath === '/') {\n return routeNode.cleanedPath ?? ''\n }\n return routeNode.cleanedPath?.replace(/\\/$/, '') ?? ''\n}\n\n/**\n * Infers the full path for use by TS\n */\nexport const inferFullPath = (routeNode: RouteNode): string => {\n const fullPath = removeGroups(\n removeUnderscoresWithEscape(\n removeLayoutSegmentsWithEscape(\n routeNode.routePath,\n routeNode.originalRoutePath,\n ),\n routeNode.originalRoutePath,\n ),\n )\n\n if (fullPath === '') {\n return '/'\n }\n\n // Preserve trailing slash for index routes (routePath ends with '/')\n // This ensures types match runtime behavior\n const isIndexRoute = routeNode.routePath?.endsWith('/')\n if (isIndexRoute) {\n return fullPath\n }\n\n return fullPath.replace(/\\/$/, '')\n}\n\nconst shouldPreferIndexRoute = (\n current: RouteNode,\n existing: RouteNode,\n): boolean => {\n return existing.cleanedPath === '/' && current.cleanedPath !== '/'\n}\n\n/**\n * Creates a map from fullPath to routeNode\n */\nexport const createRouteNodesByFullPath = (\n routeNodes: Array<RouteNode>,\n): Map<string, RouteNode> => {\n const map = new Map<string, RouteNode>()\n\n for (const routeNode of routeNodes) {\n const fullPath = inferFullPath(routeNode)\n\n if (fullPath === '/' && map.has('/')) {\n const existing = map.get('/')!\n if (shouldPreferIndexRoute(routeNode, existing)) {\n continue\n }\n }\n\n map.set(fullPath, routeNode)\n }\n\n return map\n}\n\n/**\n * Create a map from 'to' to a routeNode\n */\nexport const createRouteNodesByTo = (\n routeNodes: Array<RouteNode>,\n): Map<string, RouteNode> => {\n const map = new Map<string, RouteNode>()\n\n for (const routeNode of dedupeBranchesAndIndexRoutes(routeNodes)) {\n const to = inferTo(routeNode)\n\n if (to === '/' && map.has('/')) {\n const existing = map.get('/')!\n if (shouldPreferIndexRoute(routeNode, existing)) {\n continue\n }\n }\n\n map.set(to, routeNode)\n }\n\n return map\n}\n\n/**\n * Create a map from 'id' to a routeNode\n */\nexport const createRouteNodesById = (\n routeNodes: Array<RouteNode>,\n): Map<string, RouteNode> => {\n return new Map(\n routeNodes.map((routeNode) => {\n const id = routeNode.routePath ?? ''\n return [id, routeNode]\n }),\n )\n}\n\n/**\n * Infers to path\n */\nconst inferTo = (routeNode: RouteNode): string => {\n const fullPath = inferFullPath(routeNode)\n\n if (fullPath === '/') return fullPath\n\n return fullPath.replace(/\\/$/, '')\n}\n\n/**\n * Dedupes branches and index routes\n */\nconst dedupeBranchesAndIndexRoutes = (\n routes: Array<RouteNode>,\n): Array<RouteNode> => {\n return routes.filter((route) => {\n if (route.children?.find((child) => child.cleanedPath === '/')) return false\n return true\n })\n}\n\nfunction checkUnique<TElement>(routes: Array<TElement>, key: keyof TElement) {\n // Check no two routes have the same `key`\n // if they do, throw an error with the conflicting filePaths\n const keys = routes.map((d) => d[key])\n const uniqueKeys = new Set(keys)\n if (keys.length !== uniqueKeys.size) {\n const duplicateKeys = keys.filter((d, i) => keys.indexOf(d) !== i)\n const conflictingFiles = routes.filter((d) =>\n duplicateKeys.includes(d[key]),\n )\n return conflictingFiles\n }\n return undefined\n}\n\nexport function checkRouteFullPathUniqueness(\n _routes: Array<RouteNode>,\n config: Config,\n) {\n const emptyPathRoutes = _routes.filter((d) => d.routePath === '')\n if (emptyPathRoutes.length) {\n const errorMessage = `Invalid route path \"\" was found. Root routes must be defined via __root.tsx (createRootRoute), not createFileRoute('') or a route file that resolves to an empty path.\nConflicting files: \\n ${emptyPathRoutes\n .map((d) => path.resolve(config.routesDirectory, d.filePath))\n .join('\\n ')}\\n`\n throw new Error(errorMessage)\n }\n\n const routes = _routes.map((d) => {\n const inferredFullPath = inferFullPath(d)\n return { ...d, inferredFullPath }\n })\n\n const conflictingFiles = checkUnique(routes, 'inferredFullPath')\n\n if (conflictingFiles !== undefined) {\n const errorMessage = `Conflicting configuration paths were found for the following route${conflictingFiles.length > 1 ? 's' : ''}: ${conflictingFiles\n .map((p) => `\"${p.inferredFullPath}\"`)\n .join(', ')}.\nPlease ensure each Route has a unique full path.\nConflicting files: \\n ${conflictingFiles.map((d) => path.resolve(config.routesDirectory, d.filePath)).join('\\n ')}\\n`\n throw new Error(errorMessage)\n }\n}\n\nexport function buildRouteTreeConfig(\n nodes: Array<RouteNode>,\n disableTypes: boolean,\n depth = 1,\n): Array<string> {\n const children = nodes.map((node) => {\n if (node._fsRouteType === '__root') {\n return\n }\n\n if (node._fsRouteType === 'pathless_layout' && !node.children?.length) {\n return\n }\n\n const route = `${node.variableName}`\n\n if (node.children?.length) {\n const childConfigs = buildRouteTreeConfig(\n node.children,\n disableTypes,\n depth + 1,\n )\n\n const childrenDeclaration = disableTypes\n ? ''\n : `interface ${route}RouteChildren {\n ${node.children\n .map(\n (child) =>\n `${child.variableName}Route: typeof ${getResolvedRouteNodeVariableName(child)}`,\n )\n .join(',')}\n}`\n\n const children = `const ${route}RouteChildren${disableTypes ? '' : `: ${route}RouteChildren`} = {\n ${node.children\n .map(\n (child) =>\n `${child.variableName}Route: ${getResolvedRouteNodeVariableName(child)}`,\n )\n .join(',')}\n}`\n\n const routeWithChildren = `const ${route}RouteWithChildren = ${route}Route._addFileChildren(${route}RouteChildren)`\n\n return [\n childConfigs.join('\\n'),\n childrenDeclaration,\n children,\n routeWithChildren,\n ].join('\\n\\n')\n }\n\n return undefined\n })\n\n return children.filter((x) => x !== undefined)\n}\n\nexport function buildImportString(\n importDeclaration: ImportDeclaration,\n): string {\n const { source, specifiers, importKind } = importDeclaration\n return specifiers.length\n ? `import ${importKind === 'type' ? 'type ' : ''}{ ${specifiers.map((s) => (s.local ? `${s.imported} as ${s.local}` : s.imported)).join(', ')} } from '${source}'`\n : ''\n}\n\nexport function mergeImportDeclarations(\n imports: Array<ImportDeclaration>,\n): Array<ImportDeclaration> {\n const merged = new Map<string, ImportDeclaration>()\n\n for (const imp of imports) {\n const key = `${imp.source}-${imp.importKind ?? ''}`\n let existing = merged.get(key)\n if (!existing) {\n existing = { ...imp, specifiers: [] }\n merged.set(key, existing)\n }\n\n const existingSpecs = existing.specifiers\n for (const specifier of imp.specifiers) {\n let found = false\n for (let i = 0; i < existingSpecs.length; i++) {\n const e = existingSpecs[i]!\n if (e.imported === specifier.imported && e.local === specifier.local) {\n found = true\n break\n }\n }\n if (!found) {\n existingSpecs.push(specifier)\n }\n }\n }\n\n return [...merged.values()]\n}\n\nexport const findParent = (node: RouteNode | undefined): string => {\n if (!node) {\n return `rootRouteImport`\n }\n if (node.parent) {\n return `${node.parent.variableName}Route`\n }\n return findParent(node.parent)\n}\n\nexport function buildFileRoutesByPathInterface(opts: {\n routeNodes: Array<RouteNode>\n module: string\n interfaceName: string\n config?: Pick<Config, 'routeToken'>\n}): string {\n return `declare module '${opts.module}' {\n interface ${opts.interfaceName} {\n ${opts.routeNodes\n .map((routeNode) => {\n const filePathId = routeNode.routePath\n const preloaderRoute = `typeof ${routeNode.variableName}RouteImport`\n\n const parent = findParent(routeNode)\n\n return `'${filePathId}': {\n id: '${filePathId}'\n path: '${inferPath(routeNode)}'\n fullPath: '${inferFullPath(routeNode)}'\n preLoaderRoute: ${preloaderRoute}\n parentRoute: typeof ${parent}\n }`\n })\n .join('\\n')}\n }\n}`\n}\n\nfunction getImportPath(\n node: RouteNode,\n config: Config,\n generatedRouteTreePath: string,\n): string {\n return replaceBackslash(\n removeExt(\n path.relative(\n path.dirname(generatedRouteTreePath),\n path.resolve(config.routesDirectory, node.filePath),\n ),\n config.addExtensions,\n ),\n )\n}\n\nexport function getImportForRouteNode(\n node: RouteNode,\n config: Config,\n generatedRouteTreePath: string,\n root: string,\n): ImportDeclaration {\n let source = ''\n if (config.importRoutesUsingAbsolutePaths) {\n source = replaceBackslash(\n removeExt(\n path.resolve(root, config.routesDirectory, node.filePath),\n config.addExtensions,\n ),\n )\n } else {\n source = `./${getImportPath(node, config, generatedRouteTreePath)}`\n }\n return {\n source,\n specifiers: [\n {\n imported: 'Route',\n local: `${node.variableName}RouteImport`,\n },\n ],\n } satisfies ImportDeclaration\n}\n"],"mappings":"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"} |
+4
-4
| { | ||
| "name": "@tanstack/router-generator", | ||
| "version": "1.166.29", | ||
| "version": "1.166.30", | ||
| "description": "Modern and scalable routing for React applications", | ||
@@ -52,5 +52,5 @@ "author": "Tanner Linsley", | ||
| "dependencies": { | ||
| "@babel/types": "^7.28.5", | ||
| "magic-string": "^0.30.21", | ||
| "prettier": "^3.5.0", | ||
| "recast": "^0.23.11", | ||
| "source-map": "^0.7.4", | ||
| "tsx": "^4.19.2", | ||
@@ -65,3 +65,3 @@ "zod": "^3.24.2", | ||
| "@types/node": ">=20", | ||
| "@tanstack/react-router": "1.168.18" | ||
| "@tanstack/react-router": "1.168.19" | ||
| }, | ||
@@ -68,0 +68,0 @@ "scripts": { |
+0
-1
@@ -52,3 +52,2 @@ import path from 'node:path' | ||
| disableTypes: z.boolean().optional().default(false), | ||
| verboseFileRoutes: z.boolean().optional(), | ||
| addExtensions: z | ||
@@ -55,0 +54,0 @@ .union([z.boolean(), z.string()]) |
+0
-66
@@ -28,6 +28,4 @@ import path from 'node:path' | ||
| getImportForRouteNode, | ||
| getImportPath, | ||
| getResolvedRouteNodeVariableName, | ||
| hasParentRoute, | ||
| isRouteNodeValidForAugmentation, | ||
| isSegmentPathless, | ||
@@ -662,34 +660,2 @@ mergeImportDeclarations, | ||
| } | ||
| if (config.verboseFileRoutes === false) { | ||
| const typeImport: ImportDeclaration = { | ||
| specifiers: [], | ||
| source: this.targetTemplate.fullPkg, | ||
| importKind: 'type', | ||
| } | ||
| let needsCreateFileRoute = false | ||
| let needsCreateLazyFileRoute = false | ||
| for (const node of sortedRouteNodes) { | ||
| if (isRouteNodeValidForAugmentation(node)) { | ||
| if (node._fsRouteType !== 'lazy') { | ||
| needsCreateFileRoute = true | ||
| } | ||
| if (acc.routePiecesByPath[node.routePath!]?.lazy) { | ||
| needsCreateLazyFileRoute = true | ||
| } | ||
| } | ||
| if (needsCreateFileRoute && needsCreateLazyFileRoute) break | ||
| } | ||
| if (needsCreateFileRoute) { | ||
| typeImport.specifiers.push({ imported: 'CreateFileRoute' }) | ||
| } | ||
| if (needsCreateLazyFileRoute) { | ||
| typeImport.specifiers.push({ imported: 'CreateLazyFileRoute' }) | ||
| } | ||
| if (typeImport.specifiers.length > 0) { | ||
| typeImport.specifiers.push({ imported: 'FileRoutesByPath' }) | ||
| imports.push(typeImport) | ||
| } | ||
| } | ||
| const routeTreeConfig = buildRouteTreeConfig( | ||
@@ -968,32 +934,2 @@ acc.routeTree, | ||
| let moduleAugmentation = '' | ||
| if (config.verboseFileRoutes === false && !config.disableTypes) { | ||
| moduleAugmentation = opts.routeFileResult | ||
| .map((node) => { | ||
| const getModuleDeclaration = (routeNode?: RouteNode) => { | ||
| if (!isRouteNodeValidForAugmentation(routeNode)) { | ||
| return '' | ||
| } | ||
| let moduleAugmentation = '' | ||
| if (routeNode._fsRouteType === 'lazy') { | ||
| moduleAugmentation = `const createLazyFileRoute: CreateLazyFileRoute<FileRoutesByPath['${routeNode.routePath}']['preLoaderRoute']>` | ||
| } else { | ||
| moduleAugmentation = `const createFileRoute: CreateFileRoute<'${routeNode.routePath}', | ||
| FileRoutesByPath['${routeNode.routePath}']['parentRoute'], | ||
| FileRoutesByPath['${routeNode.routePath}']['id'], | ||
| FileRoutesByPath['${routeNode.routePath}']['path'], | ||
| FileRoutesByPath['${routeNode.routePath}']['fullPath'] | ||
| > | ||
| ` | ||
| } | ||
| return `declare module './${getImportPath(routeNode, config, this.generatedRouteTreePath)}' { | ||
| ${moduleAugmentation} | ||
| }` | ||
| } | ||
| return getModuleDeclaration(node) | ||
| }) | ||
| .join('\n') | ||
| } | ||
| const rootRouteImport = getImportForRouteNode( | ||
@@ -1026,3 +962,2 @@ rootRouteNode, | ||
| fileRoutesByPathInterface, | ||
| moduleAugmentation, | ||
| routeTreeConfig.join('\n'), | ||
@@ -1145,3 +1080,2 @@ routeTree, | ||
| lazy: node._fsRouteType === 'lazy', | ||
| verboseFileRoutes: !(this.config.verboseFileRoutes === false), | ||
| }, | ||
@@ -1148,0 +1082,0 @@ node, |
+1
-7
@@ -48,8 +48,2 @@ export { | ||
| export { ensureStringArgument } from './transform/utils' | ||
| export type { | ||
| TransformImportsConfig, | ||
| TransformContext, | ||
| TransformOptions, | ||
| } from './transform/types' | ||
| export type { TransformContext, TransformOptions } from './transform/types' |
+16
-36
@@ -47,2 +47,6 @@ import { format } from './utils' | ||
| function serializeRoutePath(routePath: string) { | ||
| return JSON.stringify(routePath) | ||
| } | ||
| export function getTargetTemplate(config: Config): TargetTemplate { | ||
@@ -81,9 +85,5 @@ const target = config.target | ||
| tsrImports: () => | ||
| config.verboseFileRoutes === false | ||
| ? '' | ||
| : "import { createFileRoute } from '@tanstack/react-router';", | ||
| "import { createFileRoute } from '@tanstack/react-router';", | ||
| tsrExportStart: (routePath) => | ||
| config.verboseFileRoutes === false | ||
| ? 'export const Route = createFileRoute(' | ||
| : `export const Route = createFileRoute('${routePath}')(`, | ||
| `export const Route = createFileRoute(${serializeRoutePath(routePath)})(`, | ||
| tsrExportEnd: () => ');', | ||
@@ -102,9 +102,5 @@ }, | ||
| tsrImports: () => | ||
| config.verboseFileRoutes === false | ||
| ? '' | ||
| : "import { createLazyFileRoute } from '@tanstack/react-router';", | ||
| "import { createLazyFileRoute } from '@tanstack/react-router';", | ||
| tsrExportStart: (routePath) => | ||
| config.verboseFileRoutes === false | ||
| ? 'export const Route = createLazyFileRoute(' | ||
| : `export const Route = createLazyFileRoute('${routePath}')(`, | ||
| `export const Route = createLazyFileRoute(${serializeRoutePath(routePath)})(`, | ||
| tsrExportEnd: () => ');', | ||
@@ -144,9 +140,5 @@ }, | ||
| tsrImports: () => | ||
| config.verboseFileRoutes === false | ||
| ? '' | ||
| : "import { createFileRoute } from '@tanstack/solid-router';", | ||
| "import { createFileRoute } from '@tanstack/solid-router';", | ||
| tsrExportStart: (routePath) => | ||
| config.verboseFileRoutes === false | ||
| ? 'export const Route = createFileRoute(' | ||
| : `export const Route = createFileRoute('${routePath}')(`, | ||
| `export const Route = createFileRoute(${serializeRoutePath(routePath)})(`, | ||
| tsrExportEnd: () => ');', | ||
@@ -165,10 +157,6 @@ }, | ||
| tsrImports: () => | ||
| config.verboseFileRoutes === false | ||
| ? '' | ||
| : "import { createLazyFileRoute } from '@tanstack/solid-router';", | ||
| "import { createLazyFileRoute } from '@tanstack/solid-router';", | ||
| tsrExportStart: (routePath) => | ||
| config.verboseFileRoutes === false | ||
| ? 'export const Route = createLazyFileRoute(' | ||
| : `export const Route = createLazyFileRoute('${routePath}')(`, | ||
| `export const Route = createLazyFileRoute(${serializeRoutePath(routePath)})(`, | ||
@@ -210,9 +198,5 @@ tsrExportEnd: () => ');', | ||
| tsrImports: () => | ||
| config.verboseFileRoutes === false | ||
| ? '' | ||
| : "import { createFileRoute } from '@tanstack/vue-router';", | ||
| "import { createFileRoute } from '@tanstack/vue-router';", | ||
| tsrExportStart: (routePath) => | ||
| config.verboseFileRoutes === false | ||
| ? 'export const Route = createFileRoute(' | ||
| : `export const Route = createFileRoute('${routePath}')(`, | ||
| `export const Route = createFileRoute(${serializeRoutePath(routePath)})(`, | ||
| tsrExportEnd: () => ');', | ||
@@ -232,10 +216,6 @@ }, | ||
| tsrImports: () => | ||
| config.verboseFileRoutes === false | ||
| ? '' | ||
| : "import { createLazyFileRoute } from '@tanstack/vue-router';", | ||
| "import { createLazyFileRoute } from '@tanstack/vue-router';", | ||
| tsrExportStart: (routePath) => | ||
| config.verboseFileRoutes === false | ||
| ? 'export const Route = createLazyFileRoute(' | ||
| : `export const Route = createLazyFileRoute('${routePath}')(`, | ||
| `export const Route = createLazyFileRoute(${serializeRoutePath(routePath)})(`, | ||
@@ -242,0 +222,0 @@ tsrExportEnd: () => ');', |
+628
-441
@@ -0,529 +1,716 @@ | ||
| import MagicString from 'magic-string' | ||
| import * as t from '@babel/types' | ||
| import { parseAst } from '@tanstack/router-utils' | ||
| import { parse, print, types, visit } from 'recast' | ||
| import { SourceMapConsumer } from 'source-map' | ||
| import { mergeImportDeclarations } from '../utils' | ||
| import { ensureStringArgument } from './utils' | ||
| import type { ImportDeclaration } from '../types' | ||
| import type { RawSourceMap } from 'source-map' | ||
| import type { TransformOptions, TransformResult } from './types' | ||
| const b = types.builders | ||
| const routeConstructors = ['createFileRoute', 'createLazyFileRoute'] as const | ||
| export async function transform({ | ||
| type RouteConstructorName = (typeof routeConstructors)[number] | ||
| type SupportedRouteId = t.StringLiteral | t.TemplateLiteral | ||
| type NamedImport = { | ||
| imported: string | ||
| local: string | ||
| importKind?: 'type' | 'typeof' | 'value' | ||
| } | ||
| type ParsedImportDeclaration = { | ||
| declaration: t.ImportDeclaration | ||
| defaultImport?: string | ||
| namespace?: string | ||
| named: Array<NamedImport> | ||
| moduleName: string | ||
| quote: '"' | "'" | ||
| semicolon: boolean | ||
| } | ||
| type RouteImportAnalysis = | ||
| | { kind: 'ok' } | ||
| | { | ||
| kind: 'rename' | ||
| imported: t.Identifier | ||
| local: t.Identifier | ||
| next: RouteConstructorName | ||
| } | ||
| | { kind: 'normalize' } | ||
| type RouteCall = { | ||
| callee: t.Identifier & { name: RouteConstructorName } | ||
| routeIdArg: SupportedRouteId | ||
| optionsArg: t.CallExpression['arguments'][number] | undefined | ||
| } | ||
| type RouteCallAnalysis = { | ||
| calls: Array<RouteCall> | ||
| hasUnsupportedRouteId: boolean | ||
| hasMalformedRouteCall: boolean | ||
| } | ||
| export function transform({ | ||
| ctx, | ||
| source, | ||
| node, | ||
| }: TransformOptions): Promise<TransformResult> { | ||
| let appliedChanges = false as boolean | ||
| let ast: types.namedTypes.File | ||
| }: TransformOptions): TransformResult { | ||
| let ast: ReturnType<typeof parseAst> | ||
| try { | ||
| ast = parse(source, { | ||
| sourceFileName: 'output.ts', | ||
| parser: { | ||
| parse(code: string) { | ||
| return parseAst({ | ||
| code, | ||
| // we need to instruct babel to produce tokens, | ||
| // otherwise recast will try to generate the tokens via its own parser and will fail | ||
| tokens: true, | ||
| }) | ||
| }, | ||
| }, | ||
| }) | ||
| } catch (e) { | ||
| console.error('Error parsing code', ctx.routeId, source, e) | ||
| ast = parseAst({ code: source }) | ||
| } catch (error) { | ||
| return { | ||
| result: 'error', | ||
| error: e, | ||
| error, | ||
| } | ||
| } | ||
| const preferredQuote = detectPreferredQuoteStyle(ast) | ||
| const exportedRouteNames = getExportedRouteNames(ast.program.body) | ||
| let routeExportHandled = false as boolean | ||
| function onExportFound(decl: types.namedTypes.VariableDeclarator) { | ||
| if (decl.init?.type === 'CallExpression') { | ||
| const callExpression = decl.init | ||
| const firstArgument = callExpression.arguments[0] | ||
| if (firstArgument) { | ||
| if (firstArgument.type === 'ObjectExpression') { | ||
| const staticProperties = firstArgument.properties.flatMap((p) => { | ||
| if (p.type === 'ObjectProperty' && p.key.type === 'Identifier') { | ||
| return p.key.name | ||
| } | ||
| return [] | ||
| }) | ||
| node.createFileRouteProps = new Set(staticProperties) | ||
| } | ||
| } | ||
| let identifier: types.namedTypes.Identifier | undefined | ||
| // `const Route = createFileRoute({ ... })` | ||
| if (callExpression.callee.type === 'Identifier') { | ||
| identifier = callExpression.callee | ||
| if (ctx.verboseFileRoutes) { | ||
| // we need to add the string literal via another CallExpression | ||
| callExpression.callee = b.callExpression(identifier, [ | ||
| b.stringLiteral(ctx.routeId), | ||
| ]) | ||
| appliedChanges = true | ||
| } | ||
| } | ||
| // `const Route = createFileRoute('/path')({ ... })` | ||
| else if ( | ||
| callExpression.callee.type === 'CallExpression' && | ||
| callExpression.callee.callee.type === 'Identifier' | ||
| ) { | ||
| identifier = callExpression.callee.callee | ||
| if (!ctx.verboseFileRoutes) { | ||
| // we need to remove the route id | ||
| callExpression.callee = identifier | ||
| appliedChanges = true | ||
| } else { | ||
| // check if the route id is correct | ||
| appliedChanges = ensureStringArgument( | ||
| callExpression.callee, | ||
| ctx.routeId, | ||
| ctx.preferredQuote, | ||
| ) | ||
| } | ||
| } | ||
| if (identifier === undefined) { | ||
| throw new Error( | ||
| `expected identifier to be present in ${ctx.routeId} for export "Route"`, | ||
| ) | ||
| } | ||
| if (identifier.name === 'createFileRoute' && ctx.lazy) { | ||
| identifier.name = 'createLazyFileRoute' | ||
| appliedChanges = true | ||
| } else if (identifier.name === 'createLazyFileRoute' && !ctx.lazy) { | ||
| identifier.name = 'createFileRoute' | ||
| appliedChanges = true | ||
| } | ||
| } else { | ||
| throw new Error( | ||
| `expected "Route" export to be initialized by a CallExpression`, | ||
| ) | ||
| if (exportedRouteNames.size === 0) { | ||
| return { result: 'no-route-export' } | ||
| } | ||
| const { | ||
| calls: routeCalls, | ||
| hasUnsupportedRouteId, | ||
| hasMalformedRouteCall, | ||
| } = findExportedRouteCalls(ast.program.body, exportedRouteNames) | ||
| if (routeCalls.length === 0 && hasMalformedRouteCall) { | ||
| return { | ||
| result: 'error', | ||
| error: new Error( | ||
| `expected Route export in ${ctx.routeId} to use createFileRoute('/path')({...}) or createLazyFileRoute('/path')({...})`, | ||
| ), | ||
| } | ||
| routeExportHandled = true | ||
| } | ||
| const program: types.namedTypes.Program = ast.program | ||
| // first pass: find Route export | ||
| for (const n of program.body) { | ||
| if (n.type === 'ExportNamedDeclaration') { | ||
| // direct export of a variable declaration, e.g. `export const Route = createFileRoute('/path')` | ||
| if (n.declaration?.type === 'VariableDeclaration') { | ||
| const decl = n.declaration.declarations[0] | ||
| if ( | ||
| decl && | ||
| decl.type === 'VariableDeclarator' && | ||
| decl.id.type === 'Identifier' | ||
| ) { | ||
| if (decl.id.name === 'Route') { | ||
| onExportFound(decl) | ||
| } | ||
| } | ||
| } | ||
| // this is an export without a declaration, e.g. `export { Route }` | ||
| else if (n.declaration === null && n.specifiers) { | ||
| for (const spec of n.specifiers) { | ||
| if (typeof spec.exported.name === 'string') { | ||
| if (spec.exported.name === 'Route') { | ||
| const variableName = spec.local?.name || spec.exported.name | ||
| // find the matching variable declaration by iterating over the top-level declarations | ||
| for (const decl of program.body) { | ||
| if ( | ||
| decl.type === 'VariableDeclaration' && | ||
| decl.declarations[0] | ||
| ) { | ||
| const variable = decl.declarations[0] | ||
| if ( | ||
| variable.type === 'VariableDeclarator' && | ||
| variable.id.type === 'Identifier' && | ||
| variable.id.name === variableName | ||
| ) { | ||
| onExportFound(variable) | ||
| break | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| if (routeCalls.length === 0 && hasUnsupportedRouteId) { | ||
| return { | ||
| result: 'error', | ||
| error: new Error( | ||
| `expected route id to be a string literal or plain template literal in ${ctx.routeId}`, | ||
| ), | ||
| } | ||
| if (routeExportHandled) { | ||
| break | ||
| } | ||
| } | ||
| if (!routeExportHandled) { | ||
| if (routeCalls.length === 0) { | ||
| return { result: 'not-modified' } | ||
| } | ||
| if (routeCalls.length > 1) { | ||
| return { | ||
| result: 'no-route-export', | ||
| result: 'error', | ||
| error: new Error( | ||
| `expected exactly one createFileRoute/createLazyFileRoute call in ${ctx.routeId}`, | ||
| ), | ||
| } | ||
| } | ||
| const imports: { | ||
| required: Array<ImportDeclaration> | ||
| banned: Array<ImportDeclaration> | ||
| } = { | ||
| required: [], | ||
| banned: [], | ||
| const routeCall = routeCalls[0]! | ||
| const routeIdQuote = getRouteIdQuote(source, routeCall.routeIdArg) | ||
| const createFileRouteProps = getCreateFileRouteProps(routeCall.optionsArg) | ||
| if (createFileRouteProps) { | ||
| node.createFileRouteProps = createFileRouteProps | ||
| } | ||
| const expectedCallee = getExpectedRouteConstructor(ctx.lazy) | ||
| const expectedRouteId = `${routeIdQuote}${ctx.routeId}${routeIdQuote}` | ||
| const currentRouteId = source.slice( | ||
| routeCall.routeIdArg.start!, | ||
| routeCall.routeIdArg.end!, | ||
| ) | ||
| const targetModule = `@tanstack/${ctx.target}-router` | ||
| if (ctx.verboseFileRoutes === false) { | ||
| imports.banned = [ | ||
| { | ||
| source: targetModule, | ||
| specifiers: [ | ||
| { imported: 'createLazyFileRoute' }, | ||
| { imported: 'createFileRoute' }, | ||
| ], | ||
| }, | ||
| ] | ||
| } else { | ||
| if (ctx.lazy) { | ||
| imports.required = [ | ||
| { | ||
| source: targetModule, | ||
| specifiers: [{ imported: 'createLazyFileRoute' }], | ||
| }, | ||
| ] | ||
| imports.banned = [ | ||
| { | ||
| source: targetModule, | ||
| specifiers: [{ imported: 'createFileRoute' }], | ||
| }, | ||
| ] | ||
| } else { | ||
| imports.required = [ | ||
| { | ||
| source: targetModule, | ||
| specifiers: [{ imported: 'createFileRoute' }], | ||
| }, | ||
| ] | ||
| imports.banned = [ | ||
| { | ||
| source: targetModule, | ||
| specifiers: [{ imported: 'createLazyFileRoute' }], | ||
| }, | ||
| ] | ||
| } | ||
| const imports = parseTargetImports(ast.program.body, source, targetModule) | ||
| const s = new MagicString(source) | ||
| let modified = false | ||
| if (routeCall.callee.name !== expectedCallee) { | ||
| s.update(routeCall.callee.start!, routeCall.callee.end!, expectedCallee) | ||
| modified = true | ||
| } | ||
| imports.required = mergeImportDeclarations(imports.required) | ||
| imports.banned = mergeImportDeclarations(imports.banned) | ||
| if (currentRouteId !== expectedRouteId) { | ||
| s.update( | ||
| routeCall.routeIdArg.start!, | ||
| routeCall.routeIdArg.end!, | ||
| expectedRouteId, | ||
| ) | ||
| modified = true | ||
| } | ||
| const importStatementCandidates: Array<types.namedTypes.ImportDeclaration> = | ||
| [] | ||
| const importDeclarationsToRemove: Array<types.namedTypes.ImportDeclaration> = | ||
| [] | ||
| if ( | ||
| updateRouteImports({ | ||
| imports, | ||
| source, | ||
| s, | ||
| targetModule, | ||
| required: expectedCallee, | ||
| lineEnding: getLineEnding(source), | ||
| }) | ||
| ) { | ||
| modified = true | ||
| } | ||
| // second pass: apply import rules, but only if a matching export for the plugin was found | ||
| for (const n of program.body) { | ||
| const findImport = | ||
| (opts: { source: string; importKind?: 'type' | 'value' | 'typeof' }) => | ||
| (i: ImportDeclaration) => { | ||
| if (i.source === opts.source) { | ||
| const importKind = i.importKind || 'value' | ||
| const expectedImportKind = opts.importKind || 'value' | ||
| return expectedImportKind === importKind | ||
| if (!modified) { | ||
| return { result: 'not-modified' } | ||
| } | ||
| return { | ||
| result: 'modified', | ||
| output: s.toString(), | ||
| } | ||
| } | ||
| function getExportedRouteNames(body: Array<t.Statement>) { | ||
| const exportedRouteNames = new Set<string>() | ||
| for (const statement of body) { | ||
| if (!t.isExportNamedDeclaration(statement) || statement.source) { | ||
| continue | ||
| } | ||
| if (t.isVariableDeclaration(statement.declaration)) { | ||
| for (const declarator of statement.declaration.declarations) { | ||
| if (t.isIdentifier(declarator.id) && declarator.id.name === 'Route') { | ||
| exportedRouteNames.add('Route') | ||
| } | ||
| return false | ||
| } | ||
| if (n.type === 'ImportDeclaration' && typeof n.source.value === 'string') { | ||
| const filterImport = findImport({ | ||
| source: n.source.value, | ||
| importKind: n.importKind, | ||
| }) | ||
| let requiredImports = imports.required.filter(filterImport)[0] | ||
| } | ||
| const bannedImports = imports.banned.filter(filterImport)[0] | ||
| if (!requiredImports && !bannedImports) { | ||
| for (const specifier of statement.specifiers) { | ||
| if ( | ||
| !t.isExportSpecifier(specifier) || | ||
| getExportedName(specifier.exported) !== 'Route' | ||
| ) { | ||
| continue | ||
| } | ||
| const importSpecifiersToRemove: types.namedTypes.ImportDeclaration['specifiers'] = | ||
| [] | ||
| if (n.specifiers) { | ||
| for (const spec of n.specifiers) { | ||
| if (!requiredImports && !bannedImports) { | ||
| break | ||
| } | ||
| if ( | ||
| spec.type === 'ImportSpecifier' && | ||
| typeof spec.imported.name === 'string' | ||
| ) { | ||
| if (requiredImports) { | ||
| const requiredImportIndex = requiredImports.specifiers.findIndex( | ||
| (imp) => imp.imported === spec.imported.name, | ||
| ) | ||
| if (requiredImportIndex !== -1) { | ||
| // import is already present, remove it from requiredImports | ||
| requiredImports.specifiers.splice(requiredImportIndex, 1) | ||
| if (requiredImports.specifiers.length === 0) { | ||
| imports.required = imports.required.splice( | ||
| imports.required.indexOf(requiredImports), | ||
| 1, | ||
| ) | ||
| requiredImports = undefined | ||
| } | ||
| } else { | ||
| // add the import statement to the candidates | ||
| importStatementCandidates.push(n) | ||
| } | ||
| } | ||
| if (bannedImports) { | ||
| const bannedImportIndex = bannedImports.specifiers.findIndex( | ||
| (imp) => imp.imported === spec.imported.name, | ||
| ) | ||
| if (bannedImportIndex !== -1) { | ||
| importSpecifiersToRemove.push(spec) | ||
| } | ||
| } | ||
| } | ||
| } | ||
| if (importSpecifiersToRemove.length > 0) { | ||
| appliedChanges = true | ||
| n.specifiers = n.specifiers.filter( | ||
| (spec) => !importSpecifiersToRemove.includes(spec), | ||
| ) | ||
| // mark the import statement as to be deleted if it is now empty | ||
| if (n.specifiers.length === 0) { | ||
| importDeclarationsToRemove.push(n) | ||
| } | ||
| } | ||
| const localName = getLocalBindingName(specifier.local) | ||
| if (localName) { | ||
| exportedRouteNames.add(localName) | ||
| } | ||
| } | ||
| } | ||
| imports.required.forEach((requiredImport) => { | ||
| if (requiredImport.specifiers.length > 0) { | ||
| appliedChanges = true | ||
| if (importStatementCandidates.length > 0) { | ||
| // find the first import statement that matches both the module and the import kind | ||
| const importStatement = importStatementCandidates.find( | ||
| (importStatement) => { | ||
| if (importStatement.source.value === requiredImport.source) { | ||
| const importKind = importStatement.importKind || 'value' | ||
| const requiredImportKind = requiredImport.importKind || 'value' | ||
| return importKind === requiredImportKind | ||
| } | ||
| return false | ||
| }, | ||
| ) | ||
| if (importStatement) { | ||
| if (importStatement.specifiers === undefined) { | ||
| importStatement.specifiers = [] | ||
| } | ||
| const importSpecifiersToAdd = requiredImport.specifiers.map((spec) => | ||
| b.importSpecifier( | ||
| b.identifier(spec.imported), | ||
| b.identifier(spec.imported), | ||
| ), | ||
| ) | ||
| importStatement.specifiers = [ | ||
| ...importStatement.specifiers, | ||
| ...importSpecifiersToAdd, | ||
| ] | ||
| return | ||
| } | ||
| return exportedRouteNames | ||
| } | ||
| function findExportedRouteCalls( | ||
| body: Array<t.Statement>, | ||
| exportedRouteNames: Set<string>, | ||
| ): RouteCallAnalysis { | ||
| const calls: Array<RouteCall> = [] | ||
| let hasUnsupportedRouteId = false | ||
| let hasMalformedRouteCall = false | ||
| for (const statement of body) { | ||
| const declaration = getVariableDeclaration(statement) | ||
| if (!declaration) { | ||
| continue | ||
| } | ||
| for (const declarator of declaration.declarations) { | ||
| if ( | ||
| !t.isIdentifier(declarator.id) || | ||
| !exportedRouteNames.has(declarator.id.name) | ||
| ) { | ||
| continue | ||
| } | ||
| const importStatement = b.importDeclaration( | ||
| requiredImport.specifiers.map((spec) => | ||
| b.importSpecifier( | ||
| b.identifier(spec.imported), | ||
| spec.local ? b.identifier(spec.local) : null, | ||
| ), | ||
| ), | ||
| b.stringLiteral(requiredImport.source), | ||
| ) | ||
| program.body.unshift(importStatement) | ||
| } | ||
| }) | ||
| if (importDeclarationsToRemove.length > 0) { | ||
| appliedChanges = true | ||
| for (const importDeclaration of importDeclarationsToRemove) { | ||
| // check if the import declaration is still empty | ||
| if (importDeclaration.specifiers?.length === 0) { | ||
| const index = program.body.indexOf(importDeclaration) | ||
| if (index !== -1) { | ||
| program.body.splice(index, 1) | ||
| const init = getRouteConstructorInit(declarator.init) | ||
| if (!init) { | ||
| if (isDirectRouteConstructorCall(declarator.init)) { | ||
| hasMalformedRouteCall = true | ||
| } | ||
| continue | ||
| } | ||
| const routeIdArg = init.innerCall.arguments[0] | ||
| if (isSupportedRouteId(routeIdArg)) { | ||
| calls.push({ | ||
| callee: init.callee, | ||
| routeIdArg, | ||
| optionsArg: init.outerCall.arguments[0], | ||
| }) | ||
| } else { | ||
| hasUnsupportedRouteId = true | ||
| } | ||
| } | ||
| } | ||
| if (!appliedChanges) { | ||
| return { | ||
| result: 'not-modified', | ||
| } | ||
| return { calls, hasUnsupportedRouteId, hasMalformedRouteCall } | ||
| } | ||
| function getVariableDeclaration(statement: t.Statement) { | ||
| const declaration = t.isExportNamedDeclaration(statement) | ||
| ? statement.declaration | ||
| : statement | ||
| return t.isVariableDeclaration(declaration) ? declaration : null | ||
| } | ||
| function getExportedName(node: t.Identifier | t.StringLiteral) { | ||
| return t.isIdentifier(node) ? node.name : node.value | ||
| } | ||
| function getLocalBindingName(node: t.Identifier | t.StringLiteral) { | ||
| return t.isIdentifier(node) ? node.name : null | ||
| } | ||
| function getRouteConstructorInit(expression: t.Expression | null | undefined) { | ||
| if (!expression || !t.isCallExpression(expression)) { | ||
| return null | ||
| } | ||
| const printResult = print(ast, { | ||
| reuseWhitespace: true, | ||
| sourceMapName: 'output.map', | ||
| }) | ||
| let transformedCode = printResult.code | ||
| if (printResult.map) { | ||
| const fixedOutput = await fixTransformedOutputText({ | ||
| originalCode: source, | ||
| transformedCode, | ||
| sourceMap: printResult.map as RawSourceMap, | ||
| preferredQuote, | ||
| }) | ||
| transformedCode = fixedOutput | ||
| if (!t.isCallExpression(expression.callee)) { | ||
| return null | ||
| } | ||
| const innerCall = expression.callee | ||
| if ( | ||
| !t.isIdentifier(innerCall.callee) || | ||
| !isRouteConstructor(innerCall.callee) | ||
| ) { | ||
| return null | ||
| } | ||
| return { | ||
| result: 'modified', | ||
| output: transformedCode, | ||
| callee: innerCall.callee, | ||
| outerCall: expression, | ||
| innerCall, | ||
| } | ||
| } | ||
| async function fixTransformedOutputText({ | ||
| originalCode, | ||
| transformedCode, | ||
| sourceMap, | ||
| preferredQuote, | ||
| function isDirectRouteConstructorCall( | ||
| expression: t.Expression | null | undefined, | ||
| ) { | ||
| return ( | ||
| !!expression && | ||
| t.isCallExpression(expression) && | ||
| t.isIdentifier(expression.callee) && | ||
| isRouteConstructor(expression.callee) | ||
| ) | ||
| } | ||
| function isRouteConstructor(callee: t.Identifier): callee is t.Identifier & { | ||
| name: RouteConstructorName | ||
| } { | ||
| return routeConstructors.includes(callee.name as RouteConstructorName) | ||
| } | ||
| function isSupportedRouteId( | ||
| arg: t.CallExpression['arguments'][number] | undefined, | ||
| ): arg is SupportedRouteId { | ||
| return ( | ||
| !!arg && | ||
| (t.isStringLiteral(arg) || | ||
| (t.isTemplateLiteral(arg) && arg.expressions.length === 0)) | ||
| ) | ||
| } | ||
| function getRouteIdQuote( | ||
| source: string, | ||
| arg: SupportedRouteId, | ||
| ): '"' | "'" | '`' { | ||
| const raw = source.slice(arg.start!, arg.end!) | ||
| if (raw.startsWith("'")) return "'" | ||
| if (raw.startsWith('"')) return '"' | ||
| return '`' | ||
| } | ||
| function getCreateFileRouteProps( | ||
| arg: t.CallExpression['arguments'][number] | undefined, | ||
| ) { | ||
| if (!arg || !t.isObjectExpression(arg)) { | ||
| return undefined | ||
| } | ||
| const props = new Set<string>() | ||
| for (const property of arg.properties) { | ||
| if (!t.isObjectProperty(property) || property.computed) { | ||
| continue | ||
| } | ||
| if (t.isIdentifier(property.key)) { | ||
| props.add(property.key.name) | ||
| continue | ||
| } | ||
| if (t.isStringLiteral(property.key)) { | ||
| props.add(property.key.value) | ||
| } | ||
| } | ||
| return props | ||
| } | ||
| function parseTargetImports( | ||
| body: Array<t.Statement>, | ||
| source: string, | ||
| targetModule: string, | ||
| ) { | ||
| const imports: Array<ParsedImportDeclaration> = [] | ||
| for (const statement of body) { | ||
| if ( | ||
| !t.isImportDeclaration(statement) || | ||
| statement.importKind === 'type' || | ||
| statement.source.value !== targetModule | ||
| ) { | ||
| continue | ||
| } | ||
| const rawSource = source.slice( | ||
| statement.source.start!, | ||
| statement.source.end!, | ||
| ) | ||
| const importStatement = source.slice(statement.start!, statement.end!) | ||
| imports.push({ | ||
| declaration: statement, | ||
| defaultImport: statement.specifiers.find((specifier) => | ||
| t.isImportDefaultSpecifier(specifier), | ||
| )?.local.name, | ||
| namespace: statement.specifiers.find((specifier) => | ||
| t.isImportNamespaceSpecifier(specifier), | ||
| )?.local.name, | ||
| named: statement.specifiers | ||
| .filter((specifier): specifier is t.ImportSpecifier => | ||
| t.isImportSpecifier(specifier), | ||
| ) | ||
| .map((specifier) => ({ | ||
| imported: t.isIdentifier(specifier.imported) | ||
| ? specifier.imported.name | ||
| : specifier.imported.value, | ||
| local: specifier.local.name, | ||
| importKind: specifier.importKind ?? undefined, | ||
| })), | ||
| moduleName: statement.source.value, | ||
| quote: rawSource[0] as '"' | "'", | ||
| semicolon: importStatement.trimEnd().endsWith(';'), | ||
| }) | ||
| } | ||
| return imports | ||
| } | ||
| function updateRouteImports({ | ||
| imports, | ||
| source, | ||
| s, | ||
| targetModule, | ||
| required, | ||
| lineEnding, | ||
| }: { | ||
| originalCode: string | ||
| transformedCode: string | ||
| sourceMap: RawSourceMap | ||
| preferredQuote: '"' | "'" | ||
| imports: Array<ParsedImportDeclaration> | ||
| source: string | ||
| s: MagicString | ||
| targetModule: string | ||
| required: RouteConstructorName | ||
| lineEnding: '\r\n' | '\n' | '\r' | ||
| }) { | ||
| const originalLines = originalCode.split('\n') | ||
| const transformedLines = transformedCode.split('\n') | ||
| const analysis = analyzeRouteImports(imports, required) | ||
| const defaultUsesSemicolons = detectSemicolonUsage(originalCode) | ||
| if (analysis.kind === 'ok') { | ||
| return false | ||
| } | ||
| const consumer = await new SourceMapConsumer(sourceMap) | ||
| if (analysis.kind === 'rename') { | ||
| s.update(analysis.imported.start!, analysis.imported.end!, analysis.next) | ||
| s.update(analysis.local.start!, analysis.local.end!, analysis.next) | ||
| return true | ||
| } | ||
| const fixedLines = transformedLines.map((line, i) => { | ||
| const transformedLineNum = i + 1 | ||
| return normalizeRouteImports({ | ||
| imports, | ||
| source, | ||
| s, | ||
| targetModule, | ||
| required, | ||
| lineEnding, | ||
| }) | ||
| } | ||
| let origLineText: string | undefined = undefined | ||
| function analyzeRouteImports( | ||
| imports: Array<ParsedImportDeclaration>, | ||
| required: RouteConstructorName, | ||
| ): RouteImportAnalysis { | ||
| const opposite = getOtherRouteConstructor(required) | ||
| let requiredCount = 0 | ||
| let oppositeCount = 0 | ||
| let renameCandidate: | ||
| | { | ||
| imported: t.Identifier | ||
| local: t.Identifier | ||
| next: RouteConstructorName | ||
| } | ||
| | undefined | ||
| for (let col = 0; col < line.length; col++) { | ||
| const mapped = consumer.originalPositionFor({ | ||
| line: transformedLineNum, | ||
| column: col, | ||
| }) | ||
| if (mapped.line != null && mapped.line > 0) { | ||
| origLineText = originalLines[mapped.line - 1] | ||
| break | ||
| for (const declaration of imports) { | ||
| for (const specifier of declaration.declaration.specifiers) { | ||
| if (!t.isImportSpecifier(specifier)) { | ||
| continue | ||
| } | ||
| } | ||
| if (origLineText !== undefined) { | ||
| if (origLineText === line) { | ||
| return origLineText | ||
| const imported = specifier.imported | ||
| if (!t.isIdentifier(imported)) { | ||
| return { kind: 'normalize' } | ||
| } | ||
| return fixLine(line, { | ||
| originalLine: origLineText, | ||
| useOriginalSemicolon: true, | ||
| useOriginalQuotes: true, | ||
| fallbackQuote: preferredQuote, | ||
| }) | ||
| } else { | ||
| return fixLine(line, { | ||
| originalLine: null, | ||
| useOriginalSemicolon: false, | ||
| useOriginalQuotes: false, | ||
| fallbackQuote: preferredQuote, | ||
| fallbackSemicolon: defaultUsesSemicolons, | ||
| }) | ||
| if (!isRouteConstructorName(imported.name)) { | ||
| continue | ||
| } | ||
| if (specifier.local.name !== imported.name) { | ||
| return { kind: 'normalize' } | ||
| } | ||
| if (imported.name === required) { | ||
| requiredCount++ | ||
| continue | ||
| } | ||
| if (imported.name === opposite) { | ||
| oppositeCount++ | ||
| renameCandidate = { | ||
| imported, | ||
| local: specifier.local, | ||
| next: required, | ||
| } | ||
| } | ||
| } | ||
| }) | ||
| } | ||
| return fixedLines.join('\n') | ||
| if (requiredCount === 1 && oppositeCount === 0) { | ||
| return { kind: 'ok' } | ||
| } | ||
| if (requiredCount === 0 && oppositeCount === 1 && renameCandidate) { | ||
| return { | ||
| kind: 'rename', | ||
| ...renameCandidate, | ||
| } | ||
| } | ||
| return { kind: 'normalize' } | ||
| } | ||
| function fixLine( | ||
| line: string, | ||
| { | ||
| originalLine, | ||
| useOriginalSemicolon, | ||
| useOriginalQuotes, | ||
| fallbackQuote, | ||
| fallbackSemicolon = true, | ||
| }: { | ||
| originalLine: string | null | ||
| useOriginalSemicolon: boolean | ||
| useOriginalQuotes: boolean | ||
| fallbackQuote: string | ||
| fallbackSemicolon?: boolean | ||
| }, | ||
| ) { | ||
| let result = line | ||
| function normalizeRouteImports({ | ||
| imports, | ||
| source, | ||
| s, | ||
| targetModule, | ||
| required, | ||
| lineEnding, | ||
| }: { | ||
| imports: Array<ParsedImportDeclaration> | ||
| source: string | ||
| s: MagicString | ||
| targetModule: string | ||
| required: RouteConstructorName | ||
| lineEnding: '\r\n' | '\n' | '\r' | ||
| }) { | ||
| const owner = | ||
| imports.find((declaration) => | ||
| hasNamedImport(declaration.named, required), | ||
| ) ?? imports.find((declaration) => !declaration.namespace) | ||
| if (useOriginalQuotes && originalLine) { | ||
| result = fixQuotes(result, originalLine, fallbackQuote) | ||
| } else if (!useOriginalQuotes && fallbackQuote) { | ||
| result = fixQuotesToPreferred(result, fallbackQuote) | ||
| let modified = false | ||
| for (const declaration of imports) { | ||
| const named = normalizeNamedImports({ | ||
| named: declaration.named, | ||
| required, | ||
| isOwner: declaration === owner, | ||
| }) | ||
| if (sameNamedImports(declaration.named, named)) { | ||
| continue | ||
| } | ||
| const replacement = renderImportDeclaration({ | ||
| ...declaration, | ||
| named, | ||
| }) | ||
| if (replacement === null) { | ||
| s.remove( | ||
| declaration.declaration.start!, | ||
| getRemovalEnd(source, declaration.declaration.end!), | ||
| ) | ||
| modified = true | ||
| continue | ||
| } | ||
| s.update( | ||
| declaration.declaration.start!, | ||
| declaration.declaration.end!, | ||
| replacement, | ||
| ) | ||
| modified = true | ||
| } | ||
| if (useOriginalSemicolon && originalLine) { | ||
| const hadSemicolon = originalLine.trimEnd().endsWith(';') | ||
| const hasSemicolon = result.trimEnd().endsWith(';') | ||
| if (hadSemicolon && !hasSemicolon) result += ';' | ||
| if (!hadSemicolon && hasSemicolon) result = result.replace(/;\s*$/, '') | ||
| } else if (!useOriginalSemicolon) { | ||
| const hasSemicolon = result.trimEnd().endsWith(';') | ||
| if (!fallbackSemicolon && hasSemicolon) result = result.replace(/;\s*$/, '') | ||
| if (fallbackSemicolon && !hasSemicolon && result.trim()) result += ';' | ||
| if (!owner) { | ||
| const quote = imports[0]?.quote ?? "'" | ||
| const semicolon = imports[0]?.semicolon ?? false | ||
| s.prepend( | ||
| `import { ${required} } from ${quote}${targetModule}${quote}${semicolon ? ';' : ''}${lineEnding}`, | ||
| ) | ||
| modified = true | ||
| } | ||
| return result | ||
| return modified | ||
| } | ||
| function fixQuotes(line: string, originalLine: string, fallbackQuote: string) { | ||
| let originalQuote = detectQuoteFromLine(originalLine) | ||
| if (!originalQuote) { | ||
| originalQuote = fallbackQuote | ||
| function normalizeNamedImports({ | ||
| named, | ||
| required, | ||
| isOwner, | ||
| }: { | ||
| named: Array<NamedImport> | ||
| required: RouteConstructorName | ||
| isOwner: boolean | ||
| }) { | ||
| const banned = getOtherRouteConstructor(required) | ||
| const nextNamed: Array<NamedImport> = [] | ||
| const seen = new Set<string>() | ||
| for (const specifier of named) { | ||
| if (specifier.imported === banned) { | ||
| continue | ||
| } | ||
| if ( | ||
| specifier.local === required && | ||
| (specifier.imported !== required || !isOwner) | ||
| ) { | ||
| continue | ||
| } | ||
| const key = `${specifier.importKind ?? 'value'}:${specifier.imported}:${specifier.local}` | ||
| if (seen.has(key)) { | ||
| continue | ||
| } | ||
| seen.add(key) | ||
| nextNamed.push(specifier) | ||
| } | ||
| return fixQuotesToPreferred(line, originalQuote) | ||
| if (isOwner && !hasNamedImport(nextNamed, required)) { | ||
| nextNamed.push({ imported: required, local: required }) | ||
| } | ||
| return nextNamed | ||
| } | ||
| function fixQuotesToPreferred(line: string, quote: string) { | ||
| // Replace existing quotes with preferred quote | ||
| return line.replace( | ||
| /(['"`])([^'"`\\]*(?:\\.[^'"`\\]*)*)\1/g, | ||
| (_, q, content) => { | ||
| const escaped = content.replaceAll(quote, `\\${quote}`) | ||
| return `${quote}${escaped}${quote}` | ||
| }, | ||
| function getExpectedRouteConstructor(lazy: boolean): RouteConstructorName { | ||
| return lazy ? 'createLazyFileRoute' : 'createFileRoute' | ||
| } | ||
| function getOtherRouteConstructor( | ||
| constructor: RouteConstructorName, | ||
| ): RouteConstructorName { | ||
| return constructor === 'createFileRoute' | ||
| ? 'createLazyFileRoute' | ||
| : 'createFileRoute' | ||
| } | ||
| function hasNamedImport( | ||
| named: Array<NamedImport>, | ||
| required: RouteConstructorName, | ||
| ) { | ||
| return named.some( | ||
| (specifier) => | ||
| specifier.imported === required && | ||
| specifier.local === required && | ||
| specifier.importKind !== 'type', | ||
| ) | ||
| } | ||
| function detectQuoteFromLine(line: string) { | ||
| const match = line.match(/(['"`])(?:\\.|[^\\])*?\1/) | ||
| return match ? match[1] : null | ||
| function sameNamedImports(left: Array<NamedImport>, right: Array<NamedImport>) { | ||
| return ( | ||
| left.length === right.length && | ||
| left.every( | ||
| (specifier, index) => | ||
| specifier.imported === right[index]!.imported && | ||
| specifier.local === right[index]!.local && | ||
| specifier.importKind === right[index]!.importKind, | ||
| ) | ||
| ) | ||
| } | ||
| function detectSemicolonUsage(code: string) { | ||
| const lines = code.split('\n').map((l) => l.trim()) | ||
| const total = lines.length | ||
| const withSemis = lines.filter((l) => l.endsWith(';')).length | ||
| return withSemis > total / 2 | ||
| function isRouteConstructorName(value: string): value is RouteConstructorName { | ||
| return routeConstructors.includes(value as RouteConstructorName) | ||
| } | ||
| export function detectPreferredQuoteStyle(ast: types.ASTNode): "'" | '"' { | ||
| let single = 0 | ||
| let double = 0 | ||
| function renderImportDeclaration(importDeclaration: { | ||
| defaultImport?: string | ||
| namespace?: string | ||
| named: Array<NamedImport> | ||
| moduleName: string | ||
| quote: '"' | "'" | ||
| semicolon: boolean | ||
| }) { | ||
| const parts: Array<string> = [] | ||
| visit(ast, { | ||
| visitStringLiteral(path) { | ||
| if (path.parent.node.type !== 'JSXAttribute') { | ||
| const raw = path.node.extra?.raw | ||
| if (raw?.startsWith("'")) single++ | ||
| else if (raw?.startsWith('"')) double++ | ||
| } | ||
| return false | ||
| }, | ||
| }) | ||
| if (importDeclaration.defaultImport) { | ||
| parts.push(importDeclaration.defaultImport) | ||
| } | ||
| if (single >= double) { | ||
| return "'" | ||
| if (importDeclaration.namespace) { | ||
| parts.push(`* as ${importDeclaration.namespace}`) | ||
| } | ||
| return '"' | ||
| if (importDeclaration.named.length > 0) { | ||
| parts.push( | ||
| `{ ${importDeclaration.named | ||
| .map( | ||
| (specifier) => | ||
| `${specifier.importKind === 'type' ? 'type ' : ''}${specifier.imported === specifier.local ? specifier.imported : `${specifier.imported} as ${specifier.local}`}`, | ||
| ) | ||
| .join(', ')} }`, | ||
| ) | ||
| } | ||
| if (parts.length === 0) { | ||
| return null | ||
| } | ||
| return `import ${parts.join(', ')} from ${importDeclaration.quote}${importDeclaration.moduleName}${importDeclaration.quote}${importDeclaration.semicolon ? ';' : ''}` | ||
| } | ||
| function getLineEnding(source: string): '\r\n' | '\n' | '\r' { | ||
| if (source.includes('\r\n')) { | ||
| return '\r\n' | ||
| } | ||
| if (source.includes('\n')) { | ||
| return '\n' | ||
| } | ||
| if (source.includes('\r')) { | ||
| return '\r' | ||
| } | ||
| return '\n' | ||
| } | ||
| function getRemovalEnd(source: string, end: number) { | ||
| let pos = end | ||
| while (pos < source.length && (source[pos] === ' ' || source[pos] === '\t')) { | ||
| pos++ | ||
| } | ||
| if (source[pos] === '\r' && source[pos + 1] === '\n') { | ||
| return pos + 2 | ||
| } | ||
| if (source[pos] === '\n' || source[pos] === '\r') { | ||
| return pos + 1 | ||
| } | ||
| return end | ||
| } |
@@ -1,2 +0,2 @@ | ||
| import type { ImportDeclaration, RouteNode } from '../types' | ||
| import type { RouteNode } from '../types' | ||
| import type { Config } from '../config' | ||
@@ -26,7 +26,2 @@ | ||
| export interface TransformImportsConfig { | ||
| banned?: Array<ImportDeclaration> | ||
| required?: Array<ImportDeclaration> | ||
| } | ||
| export interface TransformContext { | ||
@@ -36,4 +31,2 @@ target: Config['target'] | ||
| lazy: boolean | ||
| verboseFileRoutes: boolean | ||
| preferredQuote?: '"' | "'" | ||
| } |
+5
-43
@@ -16,3 +16,2 @@ /* eslint-disable @typescript-eslint/prefer-for-of */ | ||
| private prefixToRoute: Map<string, RouteNode> = new Map() | ||
| private layoutRoutes: Array<RouteNode> = [] | ||
@@ -38,16 +37,3 @@ constructor(routes: Array<RouteNode>) { | ||
| this.prefixToRoute.set(route.routePath, route) | ||
| if ( | ||
| route._fsRouteType === 'pathless_layout' || | ||
| route._fsRouteType === 'layout' || | ||
| route._fsRouteType === '__root' | ||
| ) { | ||
| this.layoutRoutes.push(route) | ||
| } | ||
| } | ||
| // Sort by path length descending for longest-match-first | ||
| this.layoutRoutes.sort( | ||
| (a, b) => (b.routePath?.length ?? 0) - (a.routePath?.length ?? 0), | ||
| ) | ||
| } | ||
@@ -487,3 +473,3 @@ | ||
| export function isBracketWrappedSegment(segment: string): boolean { | ||
| function isBracketWrappedSegment(segment: string): boolean { | ||
| return segment.startsWith('[') && segment.endsWith(']') | ||
@@ -692,21 +678,5 @@ } | ||
| /** | ||
| * Checks if a given RouteNode is valid for augmenting it with typing based on conditions. | ||
| * Also asserts that the RouteNode is defined. | ||
| * | ||
| * @param routeNode - The RouteNode to check. | ||
| * @returns A boolean indicating whether the RouteNode is defined. | ||
| */ | ||
| export function isRouteNodeValidForAugmentation( | ||
| routeNode?: RouteNode, | ||
| ): routeNode is RouteNode { | ||
| if (!routeNode || routeNode.isVirtual) { | ||
| return false | ||
| } | ||
| return true | ||
| } | ||
| /** | ||
| * Infers the path for use by TS | ||
| */ | ||
| export const inferPath = (routeNode: RouteNode): string => { | ||
| const inferPath = (routeNode: RouteNode): string => { | ||
| if (routeNode.cleanedPath === '/') { | ||
@@ -818,3 +788,3 @@ return routeNode.cleanedPath ?? '' | ||
| */ | ||
| export const inferTo = (routeNode: RouteNode): string => { | ||
| const inferTo = (routeNode: RouteNode): string => { | ||
| const fullPath = inferFullPath(routeNode) | ||
@@ -830,3 +800,3 @@ | ||
| */ | ||
| export const dedupeBranchesAndIndexRoutes = ( | ||
| const dedupeBranchesAndIndexRoutes = ( | ||
| routes: Array<RouteNode>, | ||
@@ -953,10 +923,2 @@ ): Array<RouteNode> => { | ||
| export function lowerCaseFirstChar(value: string) { | ||
| if (!value[0]) { | ||
| return value | ||
| } | ||
| return value[0].toLowerCase() + value.slice(1) | ||
| } | ||
| export function mergeImportDeclarations( | ||
@@ -1032,3 +994,3 @@ imports: Array<ImportDeclaration>, | ||
| export function getImportPath( | ||
| function getImportPath( | ||
| node: RouteNode, | ||
@@ -1035,0 +997,0 @@ config: Config, |
| require("../_virtual/_rolldown/runtime.cjs"); | ||
| //#region src/transform/utils.ts | ||
| var b = require("recast").types.builders; | ||
| function ensureStringArgument(callExpression, value, preferredQuote) { | ||
| const argument = callExpression.arguments[0]; | ||
| if (!argument) { | ||
| let stringLiteral; | ||
| if (!preferredQuote) stringLiteral = b.stringLiteral.from({ value }); | ||
| else stringLiteral = b.stringLiteral.from({ | ||
| value, | ||
| extra: { | ||
| rawValue: value, | ||
| raw: `${preferredQuote}${value}${preferredQuote}` | ||
| } | ||
| }); | ||
| callExpression.arguments.push(stringLiteral); | ||
| return true; | ||
| } else if (argument.type === "StringLiteral") { | ||
| if (argument.value !== value) { | ||
| argument.value = value; | ||
| return true; | ||
| } | ||
| } else if (argument.type === "TemplateLiteral") { | ||
| if (argument.quasis.length === 1 && argument.quasis[0] && argument.quasis[0].value.raw !== value) { | ||
| argument.quasis[0].value.raw = value; | ||
| return true; | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
| //#endregion | ||
| exports.ensureStringArgument = ensureStringArgument; | ||
| //# sourceMappingURL=utils.cjs.map |
| {"version":3,"file":"utils.cjs","names":[],"sources":["../../../src/transform/utils.ts"],"sourcesContent":["import { types } from 'recast'\n\nconst b = types.builders\n\nexport function ensureStringArgument(\n callExpression: types.namedTypes.CallExpression,\n value: string,\n preferredQuote?: \"'\" | '\"',\n) {\n const argument = callExpression.arguments[0]\n if (!argument) {\n let stringLiteral: types.namedTypes.StringLiteral\n if (!preferredQuote) {\n stringLiteral = b.stringLiteral.from({ value })\n } else {\n stringLiteral = b.stringLiteral.from({\n value,\n extra: {\n rawValue: value,\n raw: `${preferredQuote}${value}${preferredQuote}`,\n },\n })\n }\n callExpression.arguments.push(stringLiteral)\n return true\n } else if (argument.type === 'StringLiteral') {\n if (argument.value !== value) {\n argument.value = value\n return true\n }\n } else if (argument.type === 'TemplateLiteral') {\n if (\n argument.quasis.length === 1 &&\n argument.quasis[0] &&\n argument.quasis[0].value.raw !== value\n ) {\n argument.quasis[0].value.raw = value\n return true\n }\n }\n return false\n}\n"],"mappings":";;AAEA,IAAM,sBAAI,MAAM;AAEhB,SAAgB,qBACd,gBACA,OACA,gBACA;CACA,MAAM,WAAW,eAAe,UAAU;AAC1C,KAAI,CAAC,UAAU;EACb,IAAI;AACJ,MAAI,CAAC,eACH,iBAAgB,EAAE,cAAc,KAAK,EAAE,OAAO,CAAC;MAE/C,iBAAgB,EAAE,cAAc,KAAK;GACnC;GACA,OAAO;IACL,UAAU;IACV,KAAK,GAAG,iBAAiB,QAAQ;IAClC;GACF,CAAC;AAEJ,iBAAe,UAAU,KAAK,cAAc;AAC5C,SAAO;YACE,SAAS,SAAS;MACvB,SAAS,UAAU,OAAO;AAC5B,YAAS,QAAQ;AACjB,UAAO;;YAEA,SAAS,SAAS;MAEzB,SAAS,OAAO,WAAW,KAC3B,SAAS,OAAO,MAChB,SAAS,OAAO,GAAG,MAAM,QAAQ,OACjC;AACA,YAAS,OAAO,GAAG,MAAM,MAAM;AAC/B,UAAO;;;AAGX,QAAO"} |
| import { types } from 'recast'; | ||
| export declare function ensureStringArgument(callExpression: types.namedTypes.CallExpression, value: string, preferredQuote?: "'" | '"'): boolean; |
| import { types } from 'recast'; | ||
| export declare function ensureStringArgument(callExpression: types.namedTypes.CallExpression, value: string, preferredQuote?: "'" | '"'): boolean; |
| import { types } from "recast"; | ||
| //#region src/transform/utils.ts | ||
| var b = types.builders; | ||
| function ensureStringArgument(callExpression, value, preferredQuote) { | ||
| const argument = callExpression.arguments[0]; | ||
| if (!argument) { | ||
| let stringLiteral; | ||
| if (!preferredQuote) stringLiteral = b.stringLiteral.from({ value }); | ||
| else stringLiteral = b.stringLiteral.from({ | ||
| value, | ||
| extra: { | ||
| rawValue: value, | ||
| raw: `${preferredQuote}${value}${preferredQuote}` | ||
| } | ||
| }); | ||
| callExpression.arguments.push(stringLiteral); | ||
| return true; | ||
| } else if (argument.type === "StringLiteral") { | ||
| if (argument.value !== value) { | ||
| argument.value = value; | ||
| return true; | ||
| } | ||
| } else if (argument.type === "TemplateLiteral") { | ||
| if (argument.quasis.length === 1 && argument.quasis[0] && argument.quasis[0].value.raw !== value) { | ||
| argument.quasis[0].value.raw = value; | ||
| return true; | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
| //#endregion | ||
| export { ensureStringArgument }; | ||
| //# sourceMappingURL=utils.js.map |
| {"version":3,"file":"utils.js","names":[],"sources":["../../../src/transform/utils.ts"],"sourcesContent":["import { types } from 'recast'\n\nconst b = types.builders\n\nexport function ensureStringArgument(\n callExpression: types.namedTypes.CallExpression,\n value: string,\n preferredQuote?: \"'\" | '\"',\n) {\n const argument = callExpression.arguments[0]\n if (!argument) {\n let stringLiteral: types.namedTypes.StringLiteral\n if (!preferredQuote) {\n stringLiteral = b.stringLiteral.from({ value })\n } else {\n stringLiteral = b.stringLiteral.from({\n value,\n extra: {\n rawValue: value,\n raw: `${preferredQuote}${value}${preferredQuote}`,\n },\n })\n }\n callExpression.arguments.push(stringLiteral)\n return true\n } else if (argument.type === 'StringLiteral') {\n if (argument.value !== value) {\n argument.value = value\n return true\n }\n } else if (argument.type === 'TemplateLiteral') {\n if (\n argument.quasis.length === 1 &&\n argument.quasis[0] &&\n argument.quasis[0].value.raw !== value\n ) {\n argument.quasis[0].value.raw = value\n return true\n }\n }\n return false\n}\n"],"mappings":";;AAEA,IAAM,IAAI,MAAM;AAEhB,SAAgB,qBACd,gBACA,OACA,gBACA;CACA,MAAM,WAAW,eAAe,UAAU;AAC1C,KAAI,CAAC,UAAU;EACb,IAAI;AACJ,MAAI,CAAC,eACH,iBAAgB,EAAE,cAAc,KAAK,EAAE,OAAO,CAAC;MAE/C,iBAAgB,EAAE,cAAc,KAAK;GACnC;GACA,OAAO;IACL,UAAU;IACV,KAAK,GAAG,iBAAiB,QAAQ;IAClC;GACF,CAAC;AAEJ,iBAAe,UAAU,KAAK,cAAc;AAC5C,SAAO;YACE,SAAS,SAAS;MACvB,SAAS,UAAU,OAAO;AAC5B,YAAS,QAAQ;AACjB,UAAO;;YAEA,SAAS,SAAS;MAEzB,SAAS,OAAO,WAAW,KAC3B,SAAS,OAAO,MAChB,SAAS,OAAO,GAAG,MAAM,QAAQ,OACjC;AACA,YAAS,OAAO,GAAG,MAAM,MAAM;AAC/B,UAAO;;;AAGX,QAAO"} |
| import { types } from 'recast' | ||
| const b = types.builders | ||
| export function ensureStringArgument( | ||
| callExpression: types.namedTypes.CallExpression, | ||
| value: string, | ||
| preferredQuote?: "'" | '"', | ||
| ) { | ||
| const argument = callExpression.arguments[0] | ||
| if (!argument) { | ||
| let stringLiteral: types.namedTypes.StringLiteral | ||
| if (!preferredQuote) { | ||
| stringLiteral = b.stringLiteral.from({ value }) | ||
| } else { | ||
| stringLiteral = b.stringLiteral.from({ | ||
| value, | ||
| extra: { | ||
| rawValue: value, | ||
| raw: `${preferredQuote}${value}${preferredQuote}`, | ||
| }, | ||
| }) | ||
| } | ||
| callExpression.arguments.push(stringLiteral) | ||
| return true | ||
| } else if (argument.type === 'StringLiteral') { | ||
| if (argument.value !== value) { | ||
| argument.value = value | ||
| return true | ||
| } | ||
| } else if (argument.type === 'TemplateLiteral') { | ||
| if ( | ||
| argument.quasis.length === 1 && | ||
| argument.quasis[0] && | ||
| argument.quasis[0].value.raw !== value | ||
| ) { | ||
| argument.quasis[0].value.raw = value | ||
| return true | ||
| } | ||
| } | ||
| return false | ||
| } |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 2 instances in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 2 instances in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
866196
-3.08%101
-6.48%10323
-2.3%32
14.29%+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed