@tanstack/eslint-plugin-router 1.154.7 → 1.161.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.cjs +4 -2
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/rules/route-param-names/constants.cjs +13 -0
- package/dist/cjs/rules/route-param-names/constants.cjs.map +1 -0
- package/dist/cjs/rules/route-param-names/constants.d.cts +23 -0
- package/dist/cjs/rules/route-param-names/route-param-names.rule.cjs +98 -0
- package/dist/cjs/rules/route-param-names/route-param-names.rule.cjs.map +1 -0
- package/dist/cjs/rules/route-param-names/route-param-names.rule.d.cts +4 -0
- package/dist/cjs/rules/route-param-names/route-param-names.utils.cjs +61 -0
- package/dist/cjs/rules/route-param-names/route-param-names.utils.cjs.map +1 -0
- package/dist/cjs/rules/route-param-names/route-param-names.utils.d.cts +43 -0
- package/dist/cjs/rules.cjs +3 -1
- package/dist/cjs/rules.cjs.map +1 -1
- package/dist/cjs/utils/detect-router-imports.cjs +2 -1
- package/dist/cjs/utils/detect-router-imports.cjs.map +1 -1
- package/dist/esm/index.js +4 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/rules/route-param-names/constants.d.ts +23 -0
- package/dist/esm/rules/route-param-names/constants.js +13 -0
- package/dist/esm/rules/route-param-names/constants.js.map +1 -0
- package/dist/esm/rules/route-param-names/route-param-names.rule.d.ts +4 -0
- package/dist/esm/rules/route-param-names/route-param-names.rule.js +98 -0
- package/dist/esm/rules/route-param-names/route-param-names.rule.js.map +1 -0
- package/dist/esm/rules/route-param-names/route-param-names.utils.d.ts +43 -0
- package/dist/esm/rules/route-param-names/route-param-names.utils.js +61 -0
- package/dist/esm/rules/route-param-names/route-param-names.utils.js.map +1 -0
- package/dist/esm/rules.js +3 -1
- package/dist/esm/rules.js.map +1 -1
- package/dist/esm/utils/detect-router-imports.js +2 -1
- package/dist/esm/utils/detect-router-imports.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/route-param-names.rule.test.ts +271 -0
- package/src/__tests__/route-param-names.utils.test.ts +174 -0
- package/src/index.ts +2 -0
- package/src/rules/route-param-names/constants.ts +36 -0
- package/src/rules/route-param-names/route-param-names.rule.ts +127 -0
- package/src/rules/route-param-names/route-param-names.utils.ts +122 -0
- package/src/rules.ts +2 -0
- package/src/utils/detect-router-imports.ts +2 -1
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { VALID_PARAM_NAME_REGEX } from "./constants.js";
|
|
2
|
+
function extractParamsFromSegment(segment) {
|
|
3
|
+
const params = [];
|
|
4
|
+
if (!segment || !segment.includes("$")) {
|
|
5
|
+
return params;
|
|
6
|
+
}
|
|
7
|
+
if (segment === "$" || segment === "{$}") {
|
|
8
|
+
return params;
|
|
9
|
+
}
|
|
10
|
+
if (segment.startsWith("$") && !segment.includes("{")) {
|
|
11
|
+
const paramName = segment.slice(1);
|
|
12
|
+
if (paramName) {
|
|
13
|
+
params.push({
|
|
14
|
+
fullParam: segment,
|
|
15
|
+
paramName,
|
|
16
|
+
isOptional: false,
|
|
17
|
+
isValid: VALID_PARAM_NAME_REGEX.test(paramName)
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
return params;
|
|
21
|
+
}
|
|
22
|
+
const bracePattern = /\{(-?\$)([^}]*)\}/g;
|
|
23
|
+
let match;
|
|
24
|
+
while ((match = bracePattern.exec(segment)) !== null) {
|
|
25
|
+
const prefix = match[1];
|
|
26
|
+
const paramName = match[2];
|
|
27
|
+
if (!paramName) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
const isOptional = prefix === "-$";
|
|
31
|
+
params.push({
|
|
32
|
+
fullParam: `${prefix}${paramName}`,
|
|
33
|
+
paramName,
|
|
34
|
+
isOptional,
|
|
35
|
+
isValid: VALID_PARAM_NAME_REGEX.test(paramName)
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return params;
|
|
39
|
+
}
|
|
40
|
+
function extractParamsFromPath(path) {
|
|
41
|
+
if (!path || !path.includes("$")) {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
const segments = path.split("/");
|
|
45
|
+
const allParams = [];
|
|
46
|
+
for (const segment of segments) {
|
|
47
|
+
const params = extractParamsFromSegment(segment);
|
|
48
|
+
allParams.push(...params);
|
|
49
|
+
}
|
|
50
|
+
return allParams;
|
|
51
|
+
}
|
|
52
|
+
function getInvalidParams(path) {
|
|
53
|
+
const params = extractParamsFromPath(path);
|
|
54
|
+
return params.filter((p) => !p.isValid);
|
|
55
|
+
}
|
|
56
|
+
export {
|
|
57
|
+
extractParamsFromPath,
|
|
58
|
+
extractParamsFromSegment,
|
|
59
|
+
getInvalidParams
|
|
60
|
+
};
|
|
61
|
+
//# sourceMappingURL=route-param-names.utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-param-names.utils.js","sources":["../../../../src/rules/route-param-names/route-param-names.utils.ts"],"sourcesContent":["import { VALID_PARAM_NAME_REGEX } from './constants'\n\nexport interface ExtractedParam {\n /** The full param string including $ prefix (e.g., \"$userId\", \"-$optional\") */\n fullParam: string\n /** The param name without $ prefix (e.g., \"userId\", \"optional\") */\n paramName: string\n /** Whether this is an optional param (prefixed with -$) */\n isOptional: boolean\n /** Whether this param name is valid */\n isValid: boolean\n}\n\n/**\n * Extracts param names from a route path segment.\n *\n * Handles these patterns:\n * - $paramName -> extract \"paramName\"\n * - {$paramName} -> extract \"paramName\"\n * - prefix{$paramName}suffix -> extract \"paramName\"\n * - {-$paramName} -> extract \"paramName\" (optional)\n * - prefix{-$paramName}suffix -> extract \"paramName\" (optional)\n * - $ or {$} -> wildcard, skip validation\n */\nexport function extractParamsFromSegment(\n segment: string,\n): Array<ExtractedParam> {\n const params: Array<ExtractedParam> = []\n\n // Skip empty segments\n if (!segment || !segment.includes('$')) {\n return params\n }\n\n // Check for wildcard ($ alone or {$})\n if (segment === '$' || segment === '{$}') {\n return params // Wildcard, no param name to validate\n }\n\n // Pattern 1: Simple $paramName (entire segment starts with $)\n if (segment.startsWith('$') && !segment.includes('{')) {\n const paramName = segment.slice(1)\n if (paramName) {\n params.push({\n fullParam: segment,\n paramName,\n isOptional: false,\n isValid: VALID_PARAM_NAME_REGEX.test(paramName),\n })\n }\n return params\n }\n\n // Pattern 2: Braces pattern {$paramName} or {-$paramName} with optional prefix/suffix\n // Match patterns like: prefix{$param}suffix, {$param}, {-$param}\n const bracePattern = /\\{(-?\\$)([^}]*)\\}/g\n let match\n\n while ((match = bracePattern.exec(segment)) !== null) {\n const prefix = match[1] // \"$\" or \"-$\"\n const paramName = match[2] // The param name after $ or -$\n\n if (!paramName) {\n // This is a wildcard {$} or {-$}, skip\n continue\n }\n\n const isOptional = prefix === '-$'\n\n params.push({\n fullParam: `${prefix}${paramName}`,\n paramName,\n isOptional,\n isValid: VALID_PARAM_NAME_REGEX.test(paramName),\n })\n }\n\n return params\n}\n\n/**\n * Extracts all params from a route path.\n *\n * @param path - The route path (e.g., \"/users/$userId/posts/$postId\")\n * @returns Array of extracted params with validation info\n */\nexport function extractParamsFromPath(path: string): Array<ExtractedParam> {\n if (!path || !path.includes('$')) {\n return []\n }\n\n const segments = path.split('/')\n const allParams: Array<ExtractedParam> = []\n\n for (const segment of segments) {\n const params = extractParamsFromSegment(segment)\n allParams.push(...params)\n }\n\n return allParams\n}\n\n/**\n * Validates a single param name.\n *\n * @param paramName - The param name to validate (without $ prefix)\n * @returns Whether the param name is valid\n */\nexport function isValidParamName(paramName: string): boolean {\n return VALID_PARAM_NAME_REGEX.test(paramName)\n}\n\n/**\n * Gets all invalid params from a route path.\n *\n * @param path - The route path\n * @returns Array of invalid param info\n */\nexport function getInvalidParams(path: string): Array<ExtractedParam> {\n const params = extractParamsFromPath(path)\n return params.filter((p) => !p.isValid)\n}\n"],"names":[],"mappings":";AAwBO,SAAS,yBACd,SACuB;AACvB,QAAM,SAAgC,CAAA;AAGtC,MAAI,CAAC,WAAW,CAAC,QAAQ,SAAS,GAAG,GAAG;AACtC,WAAO;AAAA,EACT;AAGA,MAAI,YAAY,OAAO,YAAY,OAAO;AACxC,WAAO;AAAA,EACT;AAGA,MAAI,QAAQ,WAAW,GAAG,KAAK,CAAC,QAAQ,SAAS,GAAG,GAAG;AACrD,UAAM,YAAY,QAAQ,MAAM,CAAC;AACjC,QAAI,WAAW;AACb,aAAO,KAAK;AAAA,QACV,WAAW;AAAA,QACX;AAAA,QACA,YAAY;AAAA,QACZ,SAAS,uBAAuB,KAAK,SAAS;AAAA,MAAA,CAC/C;AAAA,IACH;AACA,WAAO;AAAA,EACT;AAIA,QAAM,eAAe;AACrB,MAAI;AAEJ,UAAQ,QAAQ,aAAa,KAAK,OAAO,OAAO,MAAM;AACpD,UAAM,SAAS,MAAM,CAAC;AACtB,UAAM,YAAY,MAAM,CAAC;AAEzB,QAAI,CAAC,WAAW;AAEd;AAAA,IACF;AAEA,UAAM,aAAa,WAAW;AAE9B,WAAO,KAAK;AAAA,MACV,WAAW,GAAG,MAAM,GAAG,SAAS;AAAA,MAChC;AAAA,MACA;AAAA,MACA,SAAS,uBAAuB,KAAK,SAAS;AAAA,IAAA,CAC/C;AAAA,EACH;AAEA,SAAO;AACT;AAQO,SAAS,sBAAsB,MAAqC;AACzE,MAAI,CAAC,QAAQ,CAAC,KAAK,SAAS,GAAG,GAAG;AAChC,WAAO,CAAA;AAAA,EACT;AAEA,QAAM,WAAW,KAAK,MAAM,GAAG;AAC/B,QAAM,YAAmC,CAAA;AAEzC,aAAW,WAAW,UAAU;AAC9B,UAAM,SAAS,yBAAyB,OAAO;AAC/C,cAAU,KAAK,GAAG,MAAM;AAAA,EAC1B;AAEA,SAAO;AACT;AAkBO,SAAS,iBAAiB,MAAqC;AACpE,QAAM,SAAS,sBAAsB,IAAI;AACzC,SAAO,OAAO,OAAO,CAAC,MAAM,CAAC,EAAE,OAAO;AACxC;"}
|
package/dist/esm/rules.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { rule, name } from "./rules/create-route-property-order/create-route-property-order.rule.js";
|
|
1
|
+
import { rule as rule$1, name as name$1 } from "./rules/create-route-property-order/create-route-property-order.rule.js";
|
|
2
|
+
import { rule, name } from "./rules/route-param-names/route-param-names.rule.js";
|
|
2
3
|
const rules = {
|
|
4
|
+
[name$1]: rule$1,
|
|
3
5
|
[name]: rule
|
|
4
6
|
};
|
|
5
7
|
export {
|
package/dist/esm/rules.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rules.js","sources":["../../src/rules.ts"],"sourcesContent":["import * as createRoutePropertyOrder from './rules/create-route-property-order/create-route-property-order.rule'\nimport type { ESLintUtils } from '@typescript-eslint/utils'\nimport type { ExtraRuleDocs } from './types'\n\nexport const rules: Record<\n string,\n ESLintUtils.RuleModule<\n string,\n ReadonlyArray<unknown>,\n ExtraRuleDocs,\n ESLintUtils.RuleListener\n >\n> = {\n [createRoutePropertyOrder.name]: createRoutePropertyOrder.rule,\n}\n"],"names":["createRoutePropertyOrder.name","createRoutePropertyOrder.rule"],"mappings":"
|
|
1
|
+
{"version":3,"file":"rules.js","sources":["../../src/rules.ts"],"sourcesContent":["import * as createRoutePropertyOrder from './rules/create-route-property-order/create-route-property-order.rule'\nimport * as routeParamNames from './rules/route-param-names/route-param-names.rule'\nimport type { ESLintUtils } from '@typescript-eslint/utils'\nimport type { ExtraRuleDocs } from './types'\n\nexport const rules: Record<\n string,\n ESLintUtils.RuleModule<\n string,\n ReadonlyArray<unknown>,\n ExtraRuleDocs,\n ESLintUtils.RuleListener\n >\n> = {\n [createRoutePropertyOrder.name]: createRoutePropertyOrder.rule,\n [routeParamNames.name]: routeParamNames.rule,\n}\n"],"names":["createRoutePropertyOrder.name","createRoutePropertyOrder.rule","routeParamNames.name","routeParamNames.rule"],"mappings":";;AAKO,MAAM,QAQT;AAAA,EACF,CAACA,MAA6B,GAAGC;AAAAA,EACjC,CAACC,IAAoB,GAAGC;AAC1B;"}
|
|
@@ -22,7 +22,8 @@ function detectTanstackRouterImports(create) {
|
|
|
22
22
|
};
|
|
23
23
|
const detectionInstructions = {
|
|
24
24
|
ImportDeclaration(node) {
|
|
25
|
-
if (node.specifiers.length > 0 &&
|
|
25
|
+
if (node.specifiers.length > 0 && // `importKind` is parser-dependent and can be undefined (eg. Espree)
|
|
26
|
+
node.importKind !== "type" && node.source.value.startsWith("@tanstack/") && node.source.value.endsWith("-router")) {
|
|
26
27
|
tanstackRouterImportSpecifiers.push(...node.specifiers);
|
|
27
28
|
}
|
|
28
29
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detect-router-imports.js","sources":["../../../src/utils/detect-router-imports.ts"],"sourcesContent":["import { TSESTree } from '@typescript-eslint/utils'\nimport type { ESLintUtils, TSESLint } from '@typescript-eslint/utils'\n\ntype Create = Parameters<\n ReturnType<typeof ESLintUtils.RuleCreator>\n>[0]['create']\n\ntype Context = Parameters<Create>[0]\ntype Options = Parameters<Create>[1]\ntype Helpers = {\n isSpecificTanstackRouterImport: (\n node: TSESTree.Identifier,\n source: string,\n ) => boolean\n isTanstackRouterImport: (node: TSESTree.Identifier) => boolean\n}\n\ntype EnhancedCreate = (\n context: Context,\n options: Options,\n helpers: Helpers,\n) => ReturnType<Create>\n\nexport function detectTanstackRouterImports(create: EnhancedCreate): Create {\n return (context, optionsWithDefault) => {\n const tanstackRouterImportSpecifiers: Array<TSESTree.ImportClause> = []\n\n const helpers: Helpers = {\n isSpecificTanstackRouterImport(node, source) {\n return !!tanstackRouterImportSpecifiers.find((specifier) => {\n if (\n specifier.type === TSESTree.AST_NODE_TYPES.ImportSpecifier &&\n specifier.parent.type ===\n TSESTree.AST_NODE_TYPES.ImportDeclaration &&\n specifier.parent.source.value === source\n ) {\n return node.name === specifier.local.name\n }\n\n return false\n })\n },\n isTanstackRouterImport(node) {\n return !!tanstackRouterImportSpecifiers.find((specifier) => {\n if (specifier.type === TSESTree.AST_NODE_TYPES.ImportSpecifier) {\n return node.name === specifier.local.name\n }\n\n return false\n })\n },\n }\n\n const detectionInstructions: TSESLint.RuleListener = {\n ImportDeclaration(node) {\n if (\n node.specifiers.length > 0 &&\n node.importKind
|
|
1
|
+
{"version":3,"file":"detect-router-imports.js","sources":["../../../src/utils/detect-router-imports.ts"],"sourcesContent":["import { TSESTree } from '@typescript-eslint/utils'\nimport type { ESLintUtils, TSESLint } from '@typescript-eslint/utils'\n\ntype Create = Parameters<\n ReturnType<typeof ESLintUtils.RuleCreator>\n>[0]['create']\n\ntype Context = Parameters<Create>[0]\ntype Options = Parameters<Create>[1]\ntype Helpers = {\n isSpecificTanstackRouterImport: (\n node: TSESTree.Identifier,\n source: string,\n ) => boolean\n isTanstackRouterImport: (node: TSESTree.Identifier) => boolean\n}\n\ntype EnhancedCreate = (\n context: Context,\n options: Options,\n helpers: Helpers,\n) => ReturnType<Create>\n\nexport function detectTanstackRouterImports(create: EnhancedCreate): Create {\n return (context, optionsWithDefault) => {\n const tanstackRouterImportSpecifiers: Array<TSESTree.ImportClause> = []\n\n const helpers: Helpers = {\n isSpecificTanstackRouterImport(node, source) {\n return !!tanstackRouterImportSpecifiers.find((specifier) => {\n if (\n specifier.type === TSESTree.AST_NODE_TYPES.ImportSpecifier &&\n specifier.parent.type ===\n TSESTree.AST_NODE_TYPES.ImportDeclaration &&\n specifier.parent.source.value === source\n ) {\n return node.name === specifier.local.name\n }\n\n return false\n })\n },\n isTanstackRouterImport(node) {\n return !!tanstackRouterImportSpecifiers.find((specifier) => {\n if (specifier.type === TSESTree.AST_NODE_TYPES.ImportSpecifier) {\n return node.name === specifier.local.name\n }\n\n return false\n })\n },\n }\n\n const detectionInstructions: TSESLint.RuleListener = {\n ImportDeclaration(node) {\n if (\n node.specifiers.length > 0 &&\n // `importKind` is parser-dependent and can be undefined (eg. Espree)\n node.importKind !== 'type' &&\n node.source.value.startsWith('@tanstack/') &&\n node.source.value.endsWith('-router')\n ) {\n tanstackRouterImportSpecifiers.push(...node.specifiers)\n }\n },\n }\n\n // Call original rule definition\n const ruleInstructions = create(context, optionsWithDefault, helpers)\n const enhancedRuleInstructions: TSESLint.RuleListener = {}\n\n const allKeys = new Set(\n Object.keys(detectionInstructions).concat(Object.keys(ruleInstructions)),\n )\n\n // Iterate over ALL instructions keys so we can override original rule instructions\n // to prevent their execution if conditions to report errors are not met.\n allKeys.forEach((instruction) => {\n enhancedRuleInstructions[instruction] = (node) => {\n if (instruction in detectionInstructions) {\n detectionInstructions[instruction]?.(node)\n }\n\n const ruleFunction = ruleInstructions[instruction]\n if (ruleFunction !== undefined) {\n return ruleFunction(node)\n }\n\n return undefined\n }\n })\n\n return enhancedRuleInstructions\n }\n}\n"],"names":[],"mappings":";AAuBO,SAAS,4BAA4B,QAAgC;AAC1E,SAAO,CAAC,SAAS,uBAAuB;AACtC,UAAM,iCAA+D,CAAA;AAErE,UAAM,UAAmB;AAAA,MACvB,+BAA+B,MAAM,QAAQ;AAC3C,eAAO,CAAC,CAAC,+BAA+B,KAAK,CAAC,cAAc;AAC1D,cACE,UAAU,SAAS,SAAS,eAAe,mBAC3C,UAAU,OAAO,SACf,SAAS,eAAe,qBAC1B,UAAU,OAAO,OAAO,UAAU,QAClC;AACA,mBAAO,KAAK,SAAS,UAAU,MAAM;AAAA,UACvC;AAEA,iBAAO;AAAA,QACT,CAAC;AAAA,MACH;AAAA,MACA,uBAAuB,MAAM;AAC3B,eAAO,CAAC,CAAC,+BAA+B,KAAK,CAAC,cAAc;AAC1D,cAAI,UAAU,SAAS,SAAS,eAAe,iBAAiB;AAC9D,mBAAO,KAAK,SAAS,UAAU,MAAM;AAAA,UACvC;AAEA,iBAAO;AAAA,QACT,CAAC;AAAA,MACH;AAAA,IAAA;AAGF,UAAM,wBAA+C;AAAA,MACnD,kBAAkB,MAAM;AACtB,YACE,KAAK,WAAW,SAAS;AAAA,QAEzB,KAAK,eAAe,UACpB,KAAK,OAAO,MAAM,WAAW,YAAY,KACzC,KAAK,OAAO,MAAM,SAAS,SAAS,GACpC;AACA,yCAA+B,KAAK,GAAG,KAAK,UAAU;AAAA,QACxD;AAAA,MACF;AAAA,IAAA;AAIF,UAAM,mBAAmB,OAAO,SAAS,oBAAoB,OAAO;AACpE,UAAM,2BAAkD,CAAA;AAExD,UAAM,UAAU,IAAI;AAAA,MAClB,OAAO,KAAK,qBAAqB,EAAE,OAAO,OAAO,KAAK,gBAAgB,CAAC;AAAA,IAAA;AAKzE,YAAQ,QAAQ,CAAC,gBAAgB;AAC/B,+BAAyB,WAAW,IAAI,CAAC,SAAS;AAChD,YAAI,eAAe,uBAAuB;AACxC,gCAAsB,WAAW,IAAI,IAAI;AAAA,QAC3C;AAEA,cAAM,eAAe,iBAAiB,WAAW;AACjD,YAAI,iBAAiB,QAAW;AAC9B,iBAAO,aAAa,IAAI;AAAA,QAC1B;AAEA,eAAO;AAAA,MACT;AAAA,IACF,CAAC;AAED,WAAO;AAAA,EACT;AACF;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/eslint-plugin-router",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.161.2",
|
|
4
4
|
"description": "ESLint plugin for TanStack Router",
|
|
5
5
|
"author": "Manuel Schiller",
|
|
6
6
|
"license": "MIT",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"eslint": "^9.22.0"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
|
-
"eslint": "^8.57.0 || ^9.0.0"
|
|
48
|
+
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0"
|
|
49
49
|
},
|
|
50
50
|
"scripts": {
|
|
51
51
|
"clean": "rimraf ./dist ./coverage",
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { RuleTester } from '@typescript-eslint/rule-tester'
|
|
2
|
+
|
|
3
|
+
import { name, rule } from '../rules/route-param-names/route-param-names.rule'
|
|
4
|
+
|
|
5
|
+
const ruleTester = new RuleTester()
|
|
6
|
+
|
|
7
|
+
ruleTester.run(name, rule, {
|
|
8
|
+
valid: [
|
|
9
|
+
// Valid param names - simple $param format
|
|
10
|
+
{
|
|
11
|
+
name: 'valid simple param: $userId',
|
|
12
|
+
code: `
|
|
13
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
14
|
+
const Route = createFileRoute('/users/$userId')({})
|
|
15
|
+
`,
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: 'valid simple param: $id',
|
|
19
|
+
code: `
|
|
20
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
21
|
+
const Route = createFileRoute('/posts/$id')({})
|
|
22
|
+
`,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'valid simple param: $_id (underscore prefix)',
|
|
26
|
+
code: `
|
|
27
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
28
|
+
const Route = createFileRoute('/items/$_id')({})
|
|
29
|
+
`,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'valid simple param: $$var (dollar prefix)',
|
|
33
|
+
code: `
|
|
34
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
35
|
+
const Route = createFileRoute('/data/$$var')({})
|
|
36
|
+
`,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'valid param with numbers: $user123',
|
|
40
|
+
code: `
|
|
41
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
42
|
+
const Route = createFileRoute('/users/$user123')({})
|
|
43
|
+
`,
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
// Valid param names - braces format {$param}
|
|
47
|
+
{
|
|
48
|
+
name: 'valid braces param: {$userName}',
|
|
49
|
+
code: `
|
|
50
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
51
|
+
const Route = createFileRoute('/users/{$userName}')({})
|
|
52
|
+
`,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: 'valid braces param with prefix/suffix: prefix{$id}suffix',
|
|
56
|
+
code: `
|
|
57
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
58
|
+
const Route = createFileRoute('/items/item-{$id}-details')({})
|
|
59
|
+
`,
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
// Valid optional params - {-$param}
|
|
63
|
+
{
|
|
64
|
+
name: 'valid optional param: {-$optional}',
|
|
65
|
+
code: `
|
|
66
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
67
|
+
const Route = createFileRoute('/search/{-$query}')({})
|
|
68
|
+
`,
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: 'valid optional param with prefix/suffix: prefix{-$opt}suffix',
|
|
72
|
+
code: `
|
|
73
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
74
|
+
const Route = createFileRoute('/filter/by-{-$category}-items')({})
|
|
75
|
+
`,
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
// Wildcards - should be skipped (no validation)
|
|
79
|
+
{
|
|
80
|
+
name: 'wildcard: $ alone',
|
|
81
|
+
code: `
|
|
82
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
83
|
+
const Route = createFileRoute('/files/$')({})
|
|
84
|
+
`,
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: 'wildcard: {$}',
|
|
88
|
+
code: `
|
|
89
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
90
|
+
const Route = createFileRoute('/catch/{$}')({})
|
|
91
|
+
`,
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
// Multiple valid params
|
|
95
|
+
{
|
|
96
|
+
name: 'multiple valid params in path',
|
|
97
|
+
code: `
|
|
98
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
99
|
+
const Route = createFileRoute('/users/$userId/posts/$postId')({})
|
|
100
|
+
`,
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
// createRoute with path property
|
|
104
|
+
{
|
|
105
|
+
name: 'createRoute with valid param in path property',
|
|
106
|
+
code: `
|
|
107
|
+
import { createRoute } from '@tanstack/react-router'
|
|
108
|
+
const Route = createRoute({ path: '/users/$userId' })
|
|
109
|
+
`,
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
// createLazyFileRoute
|
|
113
|
+
{
|
|
114
|
+
name: 'createLazyFileRoute with valid param',
|
|
115
|
+
code: `
|
|
116
|
+
import { createLazyFileRoute } from '@tanstack/react-router'
|
|
117
|
+
const Route = createLazyFileRoute('/users/$userId')({})
|
|
118
|
+
`,
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
// createLazyRoute
|
|
122
|
+
{
|
|
123
|
+
name: 'createLazyRoute with valid param',
|
|
124
|
+
code: `
|
|
125
|
+
import { createLazyRoute } from '@tanstack/react-router'
|
|
126
|
+
const Route = createLazyRoute('/users/$userId')({})
|
|
127
|
+
`,
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
// No params - should pass
|
|
131
|
+
{
|
|
132
|
+
name: 'no params in path',
|
|
133
|
+
code: `
|
|
134
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
135
|
+
const Route = createFileRoute('/users/list')({})
|
|
136
|
+
`,
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
// Not from tanstack router - should be ignored
|
|
140
|
+
{
|
|
141
|
+
name: 'non-tanstack import should be ignored',
|
|
142
|
+
code: `
|
|
143
|
+
import { createFileRoute } from 'other-router'
|
|
144
|
+
const Route = createFileRoute('/users/$123invalid')({})
|
|
145
|
+
`,
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
|
|
149
|
+
invalid: [
|
|
150
|
+
// Invalid param names - starts with number
|
|
151
|
+
{
|
|
152
|
+
name: 'invalid param starting with number: $123',
|
|
153
|
+
code: `
|
|
154
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
155
|
+
const Route = createFileRoute('/users/$123')({})
|
|
156
|
+
`,
|
|
157
|
+
errors: [{ messageId: 'invalidParamName', data: { paramName: '123' } }],
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
name: 'invalid param starting with number: $1user',
|
|
161
|
+
code: `
|
|
162
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
163
|
+
const Route = createFileRoute('/users/$1user')({})
|
|
164
|
+
`,
|
|
165
|
+
errors: [{ messageId: 'invalidParamName', data: { paramName: '1user' } }],
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
// Invalid param names - contains hyphen
|
|
169
|
+
{
|
|
170
|
+
name: 'invalid param with hyphen: $user-name',
|
|
171
|
+
code: `
|
|
172
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
173
|
+
const Route = createFileRoute('/users/$user-name')({})
|
|
174
|
+
`,
|
|
175
|
+
errors: [
|
|
176
|
+
{ messageId: 'invalidParamName', data: { paramName: 'user-name' } },
|
|
177
|
+
],
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
// Invalid param names - contains dot
|
|
181
|
+
{
|
|
182
|
+
name: 'invalid param with dot: {$my.param}',
|
|
183
|
+
code: `
|
|
184
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
185
|
+
const Route = createFileRoute('/users/{$my.param}')({})
|
|
186
|
+
`,
|
|
187
|
+
errors: [
|
|
188
|
+
{ messageId: 'invalidParamName', data: { paramName: 'my.param' } },
|
|
189
|
+
],
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
// Invalid param names - contains space
|
|
193
|
+
{
|
|
194
|
+
name: 'invalid param with space: {$param name}',
|
|
195
|
+
code: `
|
|
196
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
197
|
+
const Route = createFileRoute('/users/{$param name}')({})
|
|
198
|
+
`,
|
|
199
|
+
errors: [
|
|
200
|
+
{ messageId: 'invalidParamName', data: { paramName: 'param name' } },
|
|
201
|
+
],
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
// Invalid optional param
|
|
205
|
+
{
|
|
206
|
+
name: 'invalid optional param: {-$123invalid}',
|
|
207
|
+
code: `
|
|
208
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
209
|
+
const Route = createFileRoute('/search/{-$123invalid}')({})
|
|
210
|
+
`,
|
|
211
|
+
errors: [
|
|
212
|
+
{ messageId: 'invalidParamName', data: { paramName: '123invalid' } },
|
|
213
|
+
],
|
|
214
|
+
},
|
|
215
|
+
|
|
216
|
+
// Multiple invalid params
|
|
217
|
+
{
|
|
218
|
+
name: 'multiple invalid params in path',
|
|
219
|
+
code: `
|
|
220
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
221
|
+
const Route = createFileRoute('/users/$1id/posts/$post-id')({})
|
|
222
|
+
`,
|
|
223
|
+
errors: [
|
|
224
|
+
{ messageId: 'invalidParamName', data: { paramName: '1id' } },
|
|
225
|
+
{ messageId: 'invalidParamName', data: { paramName: 'post-id' } },
|
|
226
|
+
],
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
// createRoute with invalid path property
|
|
230
|
+
{
|
|
231
|
+
name: 'createRoute with invalid param in path property',
|
|
232
|
+
code: `
|
|
233
|
+
import { createRoute } from '@tanstack/react-router'
|
|
234
|
+
const Route = createRoute({ path: '/users/$123' })
|
|
235
|
+
`,
|
|
236
|
+
errors: [{ messageId: 'invalidParamName', data: { paramName: '123' } }],
|
|
237
|
+
},
|
|
238
|
+
|
|
239
|
+
// createLazyFileRoute with invalid param
|
|
240
|
+
{
|
|
241
|
+
name: 'createLazyFileRoute with invalid param',
|
|
242
|
+
code: `
|
|
243
|
+
import { createLazyFileRoute } from '@tanstack/react-router'
|
|
244
|
+
const Route = createLazyFileRoute('/users/$user-id')({})
|
|
245
|
+
`,
|
|
246
|
+
errors: [
|
|
247
|
+
{ messageId: 'invalidParamName', data: { paramName: 'user-id' } },
|
|
248
|
+
],
|
|
249
|
+
},
|
|
250
|
+
|
|
251
|
+
// createLazyRoute with invalid param
|
|
252
|
+
{
|
|
253
|
+
name: 'createLazyRoute with invalid param',
|
|
254
|
+
code: `
|
|
255
|
+
import { createLazyRoute } from '@tanstack/react-router'
|
|
256
|
+
const Route = createLazyRoute('/users/$1abc')({})
|
|
257
|
+
`,
|
|
258
|
+
errors: [{ messageId: 'invalidParamName', data: { paramName: '1abc' } }],
|
|
259
|
+
},
|
|
260
|
+
|
|
261
|
+
// Invalid braces param with prefix/suffix
|
|
262
|
+
{
|
|
263
|
+
name: 'invalid braces param with prefix/suffix',
|
|
264
|
+
code: `
|
|
265
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
266
|
+
const Route = createFileRoute('/items/item-{$123}-details')({})
|
|
267
|
+
`,
|
|
268
|
+
errors: [{ messageId: 'invalidParamName', data: { paramName: '123' } }],
|
|
269
|
+
},
|
|
270
|
+
],
|
|
271
|
+
})
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
extractParamsFromPath,
|
|
4
|
+
extractParamsFromSegment,
|
|
5
|
+
getInvalidParams,
|
|
6
|
+
isValidParamName,
|
|
7
|
+
} from '../rules/route-param-names/route-param-names.utils'
|
|
8
|
+
|
|
9
|
+
describe('isValidParamName', () => {
|
|
10
|
+
it('should return true for valid param names', () => {
|
|
11
|
+
expect(isValidParamName('userId')).toBe(true)
|
|
12
|
+
expect(isValidParamName('id')).toBe(true)
|
|
13
|
+
expect(isValidParamName('_id')).toBe(true)
|
|
14
|
+
expect(isValidParamName('$var')).toBe(true)
|
|
15
|
+
expect(isValidParamName('user123')).toBe(true)
|
|
16
|
+
expect(isValidParamName('_')).toBe(true)
|
|
17
|
+
expect(isValidParamName('$')).toBe(true)
|
|
18
|
+
expect(isValidParamName('ABC')).toBe(true)
|
|
19
|
+
expect(isValidParamName('camelCase')).toBe(true)
|
|
20
|
+
expect(isValidParamName('PascalCase')).toBe(true)
|
|
21
|
+
expect(isValidParamName('snake_case')).toBe(true)
|
|
22
|
+
expect(isValidParamName('$$double')).toBe(true)
|
|
23
|
+
expect(isValidParamName('__double')).toBe(true)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('should return false for invalid param names', () => {
|
|
27
|
+
expect(isValidParamName('123')).toBe(false)
|
|
28
|
+
expect(isValidParamName('1user')).toBe(false)
|
|
29
|
+
expect(isValidParamName('user-name')).toBe(false)
|
|
30
|
+
expect(isValidParamName('user.name')).toBe(false)
|
|
31
|
+
expect(isValidParamName('user name')).toBe(false)
|
|
32
|
+
expect(isValidParamName('')).toBe(false)
|
|
33
|
+
expect(isValidParamName('user@name')).toBe(false)
|
|
34
|
+
expect(isValidParamName('user#name')).toBe(false)
|
|
35
|
+
expect(isValidParamName('-user')).toBe(false)
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
describe('extractParamsFromSegment', () => {
|
|
40
|
+
it('should return empty array for segments without $', () => {
|
|
41
|
+
expect(extractParamsFromSegment('')).toEqual([])
|
|
42
|
+
expect(extractParamsFromSegment('users')).toEqual([])
|
|
43
|
+
expect(extractParamsFromSegment('static-segment')).toEqual([])
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('should skip wildcard segments', () => {
|
|
47
|
+
expect(extractParamsFromSegment('$')).toEqual([])
|
|
48
|
+
expect(extractParamsFromSegment('{$}')).toEqual([])
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('should extract simple $param format', () => {
|
|
52
|
+
const result = extractParamsFromSegment('$userId')
|
|
53
|
+
expect(result).toHaveLength(1)
|
|
54
|
+
expect(result[0]).toEqual({
|
|
55
|
+
fullParam: '$userId',
|
|
56
|
+
paramName: 'userId',
|
|
57
|
+
isOptional: false,
|
|
58
|
+
isValid: true,
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('should extract braces {$param} format', () => {
|
|
63
|
+
const result = extractParamsFromSegment('{$userId}')
|
|
64
|
+
expect(result).toHaveLength(1)
|
|
65
|
+
expect(result[0]).toEqual({
|
|
66
|
+
fullParam: '$userId',
|
|
67
|
+
paramName: 'userId',
|
|
68
|
+
isOptional: false,
|
|
69
|
+
isValid: true,
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('should extract braces with prefix/suffix', () => {
|
|
74
|
+
const result = extractParamsFromSegment('prefix{$id}suffix')
|
|
75
|
+
expect(result).toHaveLength(1)
|
|
76
|
+
expect(result[0]).toEqual({
|
|
77
|
+
fullParam: '$id',
|
|
78
|
+
paramName: 'id',
|
|
79
|
+
isOptional: false,
|
|
80
|
+
isValid: true,
|
|
81
|
+
})
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('should extract optional {-$param} format', () => {
|
|
85
|
+
const result = extractParamsFromSegment('{-$optional}')
|
|
86
|
+
expect(result).toHaveLength(1)
|
|
87
|
+
expect(result[0]).toEqual({
|
|
88
|
+
fullParam: '-$optional',
|
|
89
|
+
paramName: 'optional',
|
|
90
|
+
isOptional: true,
|
|
91
|
+
isValid: true,
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('should extract optional with prefix/suffix', () => {
|
|
96
|
+
const result = extractParamsFromSegment('pre{-$opt}post')
|
|
97
|
+
expect(result).toHaveLength(1)
|
|
98
|
+
expect(result[0]).toEqual({
|
|
99
|
+
fullParam: '-$opt',
|
|
100
|
+
paramName: 'opt',
|
|
101
|
+
isOptional: true,
|
|
102
|
+
isValid: true,
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('should mark invalid param names', () => {
|
|
107
|
+
const result = extractParamsFromSegment('$123invalid')
|
|
108
|
+
expect(result).toHaveLength(1)
|
|
109
|
+
expect(result[0]?.isValid).toBe(false)
|
|
110
|
+
expect(result[0]?.paramName).toBe('123invalid')
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('should mark hyphenated param names as invalid', () => {
|
|
114
|
+
const result = extractParamsFromSegment('$user-name')
|
|
115
|
+
expect(result).toHaveLength(1)
|
|
116
|
+
expect(result[0]?.isValid).toBe(false)
|
|
117
|
+
expect(result[0]?.paramName).toBe('user-name')
|
|
118
|
+
})
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
describe('extractParamsFromPath', () => {
|
|
122
|
+
it('should return empty array for paths without params', () => {
|
|
123
|
+
expect(extractParamsFromPath('')).toEqual([])
|
|
124
|
+
expect(extractParamsFromPath('/')).toEqual([])
|
|
125
|
+
expect(extractParamsFromPath('/users/list')).toEqual([])
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it('should extract single param from path', () => {
|
|
129
|
+
const result = extractParamsFromPath('/users/$userId')
|
|
130
|
+
expect(result).toHaveLength(1)
|
|
131
|
+
expect(result[0]?.paramName).toBe('userId')
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('should extract multiple params from path', () => {
|
|
135
|
+
const result = extractParamsFromPath('/users/$userId/posts/$postId')
|
|
136
|
+
expect(result).toHaveLength(2)
|
|
137
|
+
expect(result[0]?.paramName).toBe('userId')
|
|
138
|
+
expect(result[1]?.paramName).toBe('postId')
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
it('should extract params with various formats', () => {
|
|
142
|
+
const result = extractParamsFromPath(
|
|
143
|
+
'/a/$simple/b/{$braces}/c/{-$optional}',
|
|
144
|
+
)
|
|
145
|
+
expect(result).toHaveLength(3)
|
|
146
|
+
expect(result[0]?.paramName).toBe('simple')
|
|
147
|
+
expect(result[0]?.isOptional).toBe(false)
|
|
148
|
+
expect(result[1]?.paramName).toBe('braces')
|
|
149
|
+
expect(result[1]?.isOptional).toBe(false)
|
|
150
|
+
expect(result[2]?.paramName).toBe('optional')
|
|
151
|
+
expect(result[2]?.isOptional).toBe(true)
|
|
152
|
+
})
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
describe('getInvalidParams', () => {
|
|
156
|
+
it('should return empty array for valid params', () => {
|
|
157
|
+
expect(getInvalidParams('/users/$userId')).toEqual([])
|
|
158
|
+
expect(getInvalidParams('/users/$_id')).toEqual([])
|
|
159
|
+
expect(getInvalidParams('/users/$$var')).toEqual([])
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
it('should return invalid params only', () => {
|
|
163
|
+
const result = getInvalidParams('/users/$123/posts/$validId')
|
|
164
|
+
expect(result).toHaveLength(1)
|
|
165
|
+
expect(result[0]?.paramName).toBe('123')
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it('should return all invalid params', () => {
|
|
169
|
+
const result = getInvalidParams('/users/$1id/posts/$post-id')
|
|
170
|
+
expect(result).toHaveLength(2)
|
|
171
|
+
expect(result[0]?.paramName).toBe('1id')
|
|
172
|
+
expect(result[1]?.paramName).toBe('post-id')
|
|
173
|
+
})
|
|
174
|
+
})
|
package/src/index.ts
CHANGED
|
@@ -26,6 +26,7 @@ Object.assign(plugin.configs, {
|
|
|
26
26
|
plugins: ['@tanstack/eslint-plugin-router'],
|
|
27
27
|
rules: {
|
|
28
28
|
'@tanstack/router/create-route-property-order': 'warn',
|
|
29
|
+
'@tanstack/router/route-param-names': 'error',
|
|
29
30
|
},
|
|
30
31
|
},
|
|
31
32
|
'flat/recommended': [
|
|
@@ -35,6 +36,7 @@ Object.assign(plugin.configs, {
|
|
|
35
36
|
},
|
|
36
37
|
rules: {
|
|
37
38
|
'@tanstack/router/create-route-property-order': 'warn',
|
|
39
|
+
'@tanstack/router/route-param-names': 'error',
|
|
38
40
|
},
|
|
39
41
|
},
|
|
40
42
|
],
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Functions where the path is passed as the first argument (string literal)
|
|
3
|
+
* e.g., createFileRoute('/path/$param')(...)
|
|
4
|
+
*/
|
|
5
|
+
export const pathAsFirstArgFunctions = [
|
|
6
|
+
'createFileRoute',
|
|
7
|
+
'createLazyFileRoute',
|
|
8
|
+
'createLazyRoute',
|
|
9
|
+
] as const
|
|
10
|
+
|
|
11
|
+
export type PathAsFirstArgFunction = (typeof pathAsFirstArgFunctions)[number]
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Functions where the path is a property in the options object
|
|
15
|
+
* e.g., createRoute({ path: '/path/$param' })
|
|
16
|
+
*/
|
|
17
|
+
export const pathAsPropertyFunctions = ['createRoute'] as const
|
|
18
|
+
|
|
19
|
+
export type PathAsPropertyFunction = (typeof pathAsPropertyFunctions)[number]
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* All route functions that need param name validation
|
|
23
|
+
*/
|
|
24
|
+
export const allRouteFunctions = [
|
|
25
|
+
...pathAsFirstArgFunctions,
|
|
26
|
+
...pathAsPropertyFunctions,
|
|
27
|
+
] as const
|
|
28
|
+
|
|
29
|
+
export type RouteFunction = (typeof allRouteFunctions)[number]
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Regex for valid JavaScript identifier (param name)
|
|
33
|
+
* Must start with letter, underscore, or dollar sign
|
|
34
|
+
* Can contain letters, numbers, underscores, or dollar signs
|
|
35
|
+
*/
|
|
36
|
+
export const VALID_PARAM_NAME_REGEX = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/
|