@tanstack/eslint-plugin-router 1.58.0 → 1.60.0
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/rules/create-route-property-order/constants.cjs +6 -8
- package/dist/cjs/rules/create-route-property-order/constants.cjs.map +1 -1
- package/dist/cjs/rules/create-route-property-order/constants.d.cts +1 -0
- package/dist/cjs/rules/create-route-property-order/create-route-property-order.rule.cjs +4 -5
- package/dist/cjs/rules/create-route-property-order/create-route-property-order.rule.cjs.map +1 -1
- package/dist/cjs/rules/create-route-property-order/create-route-property-order.utils.cjs +28 -9
- package/dist/cjs/rules/create-route-property-order/create-route-property-order.utils.cjs.map +1 -1
- package/dist/cjs/rules/create-route-property-order/create-route-property-order.utils.d.cts +1 -1
- package/dist/esm/rules/create-route-property-order/constants.d.ts +1 -0
- package/dist/esm/rules/create-route-property-order/constants.js +7 -9
- package/dist/esm/rules/create-route-property-order/constants.js.map +1 -1
- package/dist/esm/rules/create-route-property-order/create-route-property-order.rule.js +5 -6
- package/dist/esm/rules/create-route-property-order/create-route-property-order.rule.js.map +1 -1
- package/dist/esm/rules/create-route-property-order/create-route-property-order.utils.d.ts +1 -1
- package/dist/esm/rules/create-route-property-order/create-route-property-order.utils.js +28 -9
- package/dist/esm/rules/create-route-property-order/create-route-property-order.utils.js.map +1 -1
- package/package.json +4 -4
- package/src/__tests__/create-route-property-order.rule.test.ts +38 -5
- package/src/__tests__/create-route-property-order.utils.test.ts +28 -7
- package/src/rules/create-route-property-order/constants.ts +7 -0
- package/src/rules/create-route-property-order/create-route-property-order.rule.ts +6 -7
- package/src/rules/create-route-property-order/create-route-property-order.utils.ts +43 -14
|
@@ -12,16 +12,14 @@ const createRouteFunctions = [
|
|
|
12
12
|
...createRouteFunctionsDirect,
|
|
13
13
|
...createRouteFunctionsIndirect
|
|
14
14
|
];
|
|
15
|
-
const
|
|
16
|
-
"params",
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"loaderDeps",
|
|
21
|
-
"loader"
|
|
15
|
+
const sortRules = [
|
|
16
|
+
[["params", "validateSearch"], ["context"]],
|
|
17
|
+
[["context"], ["beforeLoad"]],
|
|
18
|
+
[["beforeLoad"], ["loaderDeps"]],
|
|
19
|
+
[["loaderDeps"], ["loader"]]
|
|
22
20
|
];
|
|
23
|
-
exports.checkedProperties = checkedProperties;
|
|
24
21
|
exports.createRouteFunctions = createRouteFunctions;
|
|
25
22
|
exports.createRouteFunctionsDirect = createRouteFunctionsDirect;
|
|
26
23
|
exports.createRouteFunctionsIndirect = createRouteFunctionsIndirect;
|
|
24
|
+
exports.sortRules = sortRules;
|
|
27
25
|
//# sourceMappingURL=constants.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.cjs","sources":["../../../../src/rules/create-route-property-order/constants.ts"],"sourcesContent":["export const createRouteFunctionsIndirect = [\n 'createFileRoute',\n 'createRootRouteWithContext',\n] as const\nexport const createRouteFunctionsDirect = [\n 'createRootRoute',\n 'createRoute',\n] as const\n\nexport const createRouteFunctions = [\n ...createRouteFunctionsDirect,\n ...createRouteFunctionsIndirect,\n] as const\n\nexport type CreateRouteFunction = (typeof createRouteFunctions)[number]\n\nexport const checkedProperties = [\n 'params',\n 'validateSearch',\n 'context',\n 'beforeLoad',\n 'loaderDeps',\n 'loader',\n] as const\n"],"names":[],"mappings":";;AAAO,MAAM,+BAA+B;AAAA,EAC1C;AAAA,EACA;AACF;AACO,MAAM,6BAA6B;AAAA,EACxC;AAAA,EACA;AACF;AAEO,MAAM,uBAAuB;AAAA,EAClC,GAAG;AAAA,EACH,GAAG;AACL;
|
|
1
|
+
{"version":3,"file":"constants.cjs","sources":["../../../../src/rules/create-route-property-order/constants.ts"],"sourcesContent":["export const createRouteFunctionsIndirect = [\n 'createFileRoute',\n 'createRootRouteWithContext',\n] as const\nexport const createRouteFunctionsDirect = [\n 'createRootRoute',\n 'createRoute',\n] as const\n\nexport const createRouteFunctions = [\n ...createRouteFunctionsDirect,\n ...createRouteFunctionsIndirect,\n] as const\n\nexport type CreateRouteFunction = (typeof createRouteFunctions)[number]\n\nexport const checkedProperties = [\n 'params',\n 'validateSearch',\n 'context',\n 'beforeLoad',\n 'loaderDeps',\n 'loader',\n] as const\n\nexport const sortRules = [\n [['params', 'validateSearch'], ['context']],\n [['context'], ['beforeLoad']],\n [['beforeLoad'], ['loaderDeps']],\n [['loaderDeps'], ['loader']],\n] as const\n"],"names":[],"mappings":";;AAAO,MAAM,+BAA+B;AAAA,EAC1C;AAAA,EACA;AACF;AACO,MAAM,6BAA6B;AAAA,EACxC;AAAA,EACA;AACF;AAEO,MAAM,uBAAuB;AAAA,EAClC,GAAG;AAAA,EACH,GAAG;AACL;AAaO,MAAM,YAAY;AAAA,EACvB,CAAC,CAAC,UAAU,gBAAgB,GAAG,CAAC,SAAS,CAAC;AAAA,EAC1C,CAAC,CAAC,SAAS,GAAG,CAAC,YAAY,CAAC;AAAA,EAC5B,CAAC,CAAC,YAAY,GAAG,CAAC,YAAY,CAAC;AAAA,EAC/B,CAAC,CAAC,YAAY,GAAG,CAAC,QAAQ,CAAC;AAC7B;;;;;"}
|
|
@@ -3,3 +3,4 @@ export declare const createRouteFunctionsDirect: readonly ["createRootRoute", "c
|
|
|
3
3
|
export declare const createRouteFunctions: readonly ["createRootRoute", "createRoute", "createFileRoute", "createRootRouteWithContext"];
|
|
4
4
|
export type CreateRouteFunction = (typeof createRouteFunctions)[number];
|
|
5
5
|
export declare const checkedProperties: readonly ["params", "validateSearch", "context", "beforeLoad", "loaderDeps", "loader"];
|
|
6
|
+
export declare const sortRules: readonly [readonly [readonly ["params", "validateSearch"], readonly ["context"]], readonly [readonly ["context"], readonly ["beforeLoad"]], readonly [readonly ["beforeLoad"], readonly ["loaderDeps"]], readonly [readonly ["loaderDeps"], readonly ["loader"]]];
|
|
@@ -50,6 +50,9 @@ const rule = createRule({
|
|
|
50
50
|
return;
|
|
51
51
|
}
|
|
52
52
|
const allProperties = argument.properties;
|
|
53
|
+
if (allProperties.length < 2) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
53
56
|
const properties = allProperties.flatMap((p) => {
|
|
54
57
|
if (p.type === utils.AST_NODE_TYPES.Property && p.key.type === utils.AST_NODE_TYPES.Identifier) {
|
|
55
58
|
return { name: p.key.name, property: p };
|
|
@@ -62,11 +65,7 @@ const rule = createRule({
|
|
|
62
65
|
}
|
|
63
66
|
return [];
|
|
64
67
|
});
|
|
65
|
-
const sortedProperties = createRoutePropertyOrder_utils.sortDataByOrder(
|
|
66
|
-
properties,
|
|
67
|
-
constants.checkedProperties,
|
|
68
|
-
"name"
|
|
69
|
-
);
|
|
68
|
+
const sortedProperties = createRoutePropertyOrder_utils.sortDataByOrder(properties, constants.sortRules, "name");
|
|
70
69
|
if (sortedProperties === null) {
|
|
71
70
|
return;
|
|
72
71
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-route-property-order.rule.cjs","sources":["../../../../src/rules/create-route-property-order/create-route-property-order.rule.ts"],"sourcesContent":["import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils'\n\nimport { getDocsUrl } from '../../utils/get-docs-url'\nimport { detectTanstackRouterImports } from '../../utils/detect-router-imports'\nimport { sortDataByOrder } from './create-route-property-order.utils'\nimport {\n
|
|
1
|
+
{"version":3,"file":"create-route-property-order.rule.cjs","sources":["../../../../src/rules/create-route-property-order/create-route-property-order.rule.ts"],"sourcesContent":["import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils'\n\nimport { getDocsUrl } from '../../utils/get-docs-url'\nimport { detectTanstackRouterImports } from '../../utils/detect-router-imports'\nimport { sortDataByOrder } from './create-route-property-order.utils'\nimport {\n createRouteFunctions,\n createRouteFunctionsIndirect,\n sortRules,\n} from './constants'\nimport type { CreateRouteFunction } from './constants'\nimport type { ExtraRuleDocs } from '../../types'\n\nconst createRule = ESLintUtils.RuleCreator<ExtraRuleDocs>(getDocsUrl)\n\nconst createRouteFunctionSet = new Set(createRouteFunctions)\nfunction isCreateRouteFunction(node: any): node is CreateRouteFunction {\n return createRouteFunctionSet.has(node)\n}\n\nexport const name = 'create-route-property-order'\n\nexport const rule = createRule({\n name,\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Ensure correct order of inference sensitive properties for createRoute functions',\n recommended: 'error',\n },\n messages: {\n invalidOrder: 'Invalid order of properties for `{{function}}`.',\n },\n schema: [],\n hasSuggestions: true,\n fixable: 'code',\n },\n defaultOptions: [],\n\n create: detectTanstackRouterImports((context) => {\n return {\n CallExpression(node) {\n if (node.callee.type !== AST_NODE_TYPES.Identifier) {\n return\n }\n const createRouteFunction = node.callee.name\n if (!isCreateRouteFunction(createRouteFunction)) {\n return\n }\n let args = node.arguments\n if (createRouteFunctionsIndirect.includes(createRouteFunction as any)) {\n if (node.parent.type === AST_NODE_TYPES.CallExpression) {\n args = node.parent.arguments\n } else {\n return\n }\n }\n\n const argument = args[0]\n if (argument === undefined || argument.type !== 'ObjectExpression') {\n return\n }\n\n const allProperties = argument.properties\n // no need to sort if there is at max 1 property\n if (allProperties.length < 2) {\n return\n }\n\n const properties = allProperties.flatMap((p) => {\n if (\n p.type === AST_NODE_TYPES.Property &&\n p.key.type === AST_NODE_TYPES.Identifier\n ) {\n return { name: p.key.name, property: p }\n } else if (p.type === AST_NODE_TYPES.SpreadElement) {\n if (p.argument.type === AST_NODE_TYPES.Identifier) {\n return { name: p.argument.name, property: p }\n } else {\n throw new Error('Unsupported spread element')\n }\n }\n return []\n })\n\n const sortedProperties = sortDataByOrder(properties, sortRules, 'name')\n if (sortedProperties === null) {\n return\n }\n context.report({\n node: argument,\n data: { function: node.callee.name },\n messageId: 'invalidOrder',\n fix(fixer) {\n const sourceCode = context.sourceCode\n\n const text = sortedProperties.reduce(\n (sourceText, specifier, index) => {\n let text = ''\n if (index < allProperties.length - 1) {\n text = sourceCode\n .getText()\n .slice(\n allProperties[index]!.range[1],\n allProperties[index + 1]!.range[0],\n )\n }\n return (\n sourceText + sourceCode.getText(specifier.property) + text\n )\n },\n '',\n )\n return fixer.replaceTextRange(\n [allProperties[0]!.range[0], allProperties.at(-1)!.range[1]],\n text,\n )\n },\n })\n },\n }\n }),\n})\n"],"names":["ESLintUtils","getDocsUrl","createRouteFunctions","detectTanstackRouterImports","AST_NODE_TYPES","createRouteFunctionsIndirect","sortDataByOrder","sortRules","text"],"mappings":";;;;;;;AAaA,MAAM,aAAaA,MAAY,YAAA,YAA2BC,WAAAA,UAAU;AAEpE,MAAM,yBAAyB,IAAI,IAAIC,UAAAA,oBAAoB;AAC3D,SAAS,sBAAsB,MAAwC;AAC9D,SAAA,uBAAuB,IAAI,IAAI;AACxC;AAEO,MAAM,OAAO;AAEb,MAAM,OAAO,WAAW;AAAA,EAC7B;AAAA,EACA,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,MACJ,aACE;AAAA,MACF,aAAa;AAAA,IACf;AAAA,IACA,UAAU;AAAA,MACR,cAAc;AAAA,IAChB;AAAA,IACA,QAAQ,CAAC;AAAA,IACT,gBAAgB;AAAA,IAChB,SAAS;AAAA,EACX;AAAA,EACA,gBAAgB,CAAC;AAAA,EAEjB,QAAQC,oBAAAA,4BAA4B,CAAC,YAAY;AACxC,WAAA;AAAA,MACL,eAAe,MAAM;AACnB,YAAI,KAAK,OAAO,SAASC,MAAAA,eAAe,YAAY;AAClD;AAAA,QACF;AACM,cAAA,sBAAsB,KAAK,OAAO;AACpC,YAAA,CAAC,sBAAsB,mBAAmB,GAAG;AAC/C;AAAA,QACF;AACA,YAAI,OAAO,KAAK;AACZ,YAAAC,UAAA,6BAA6B,SAAS,mBAA0B,GAAG;AACrE,cAAI,KAAK,OAAO,SAASD,MAAAA,eAAe,gBAAgB;AACtD,mBAAO,KAAK,OAAO;AAAA,UAAA,OACd;AACL;AAAA,UACF;AAAA,QACF;AAEM,cAAA,WAAW,KAAK,CAAC;AACvB,YAAI,aAAa,UAAa,SAAS,SAAS,oBAAoB;AAClE;AAAA,QACF;AAEA,cAAM,gBAAgB,SAAS;AAE3B,YAAA,cAAc,SAAS,GAAG;AAC5B;AAAA,QACF;AAEA,cAAM,aAAa,cAAc,QAAQ,CAAC,MAAM;AAE5C,cAAA,EAAE,SAASA,MAAAA,eAAe,YAC1B,EAAE,IAAI,SAASA,qBAAe,YAC9B;AACA,mBAAO,EAAE,MAAM,EAAE,IAAI,MAAM,UAAU;UAC5B,WAAA,EAAE,SAASA,MAAAA,eAAe,eAAe;AAClD,gBAAI,EAAE,SAAS,SAASA,MAAAA,eAAe,YAAY;AACjD,qBAAO,EAAE,MAAM,EAAE,SAAS,MAAM,UAAU;YAAE,OACvC;AACC,oBAAA,IAAI,MAAM,4BAA4B;AAAA,YAC9C;AAAA,UACF;AACA,iBAAO;QAAC,CACT;AAED,cAAM,mBAAmBE,+BAAA,gBAAgB,YAAYC,UAAA,WAAW,MAAM;AACtE,YAAI,qBAAqB,MAAM;AAC7B;AAAA,QACF;AACA,gBAAQ,OAAO;AAAA,UACb,MAAM;AAAA,UACN,MAAM,EAAE,UAAU,KAAK,OAAO,KAAK;AAAA,UACnC,WAAW;AAAA,UACX,IAAI,OAAO;AACT,kBAAM,aAAa,QAAQ;AAE3B,kBAAM,OAAO,iBAAiB;AAAA,cAC5B,CAAC,YAAY,WAAW,UAAU;AAChC,oBAAIC,QAAO;AACP,oBAAA,QAAQ,cAAc,SAAS,GAAG;AACpCA,0BAAO,WACJ,QAAA,EACA;AAAA,oBACC,cAAc,KAAK,EAAG,MAAM,CAAC;AAAA,oBAC7B,cAAc,QAAQ,CAAC,EAAG,MAAM,CAAC;AAAA,kBAAA;AAAA,gBAEvC;AACA,uBACE,aAAa,WAAW,QAAQ,UAAU,QAAQ,IAAIA;AAAAA,cAE1D;AAAA,cACA;AAAA,YAAA;AAEF,mBAAO,MAAM;AAAA,cACX,CAAC,cAAc,CAAC,EAAG,MAAM,CAAC,GAAG,cAAc,GAAG,EAAE,EAAG,MAAM,CAAC,CAAC;AAAA,cAC3D;AAAA,YAAA;AAAA,UAEJ;AAAA,QAAA,CACD;AAAA,MACH;AAAA,IAAA;AAAA,EACF,CACD;AACH,CAAC;;;"}
|
|
@@ -1,16 +1,35 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
-
function sortDataByOrder(data,
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
function sortDataByOrder(data, orderRules, key) {
|
|
4
|
+
const getSubsetIndex = (item, subsets) => {
|
|
5
|
+
var _a;
|
|
6
|
+
for (let i = 0; i < subsets.length; i++) {
|
|
7
|
+
if ((_a = subsets[i]) == null ? void 0 : _a.includes(item)) {
|
|
8
|
+
return i;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
return null;
|
|
12
|
+
};
|
|
13
|
+
const orderSets = orderRules.reduce(
|
|
14
|
+
(sets, [A, B]) => [...sets, A, B],
|
|
15
|
+
[]
|
|
16
|
+
);
|
|
17
|
+
const inOrderArray = data.filter(
|
|
18
|
+
(item) => getSubsetIndex(item[key], orderSets) !== null
|
|
19
|
+
);
|
|
11
20
|
let wasResorted = false;
|
|
21
|
+
const sortedArray = inOrderArray.sort((a, b) => {
|
|
22
|
+
const aKey = a[key], bKey = b[key];
|
|
23
|
+
const aSubsetIndex = getSubsetIndex(aKey, orderSets);
|
|
24
|
+
const bSubsetIndex = getSubsetIndex(bKey, orderSets);
|
|
25
|
+
if (aSubsetIndex !== null && bSubsetIndex !== null && aSubsetIndex !== bSubsetIndex) {
|
|
26
|
+
return aSubsetIndex - bSubsetIndex;
|
|
27
|
+
}
|
|
28
|
+
return 0;
|
|
29
|
+
});
|
|
30
|
+
const inOrderIterator = sortedArray.values();
|
|
12
31
|
const result = data.map((item) => {
|
|
13
|
-
if (
|
|
32
|
+
if (getSubsetIndex(item[key], orderSets) !== null) {
|
|
14
33
|
const sortedItem = inOrderIterator.next().value;
|
|
15
34
|
if (sortedItem[key] !== item[key]) {
|
|
16
35
|
wasResorted = true;
|
package/dist/cjs/rules/create-route-property-order/create-route-property-order.utils.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-route-property-order.utils.cjs","sources":["../../../../src/rules/create-route-property-order/create-route-property-order.utils.ts"],"sourcesContent":["export function sortDataByOrder<T, TKey extends keyof T>(\n data: Array<T> | ReadonlyArray<T>,\n
|
|
1
|
+
{"version":3,"file":"create-route-property-order.utils.cjs","sources":["../../../../src/rules/create-route-property-order/create-route-property-order.utils.ts"],"sourcesContent":["export function sortDataByOrder<T, TKey extends keyof T>(\n data: Array<T> | ReadonlyArray<T>,\n orderRules: ReadonlyArray<\n Readonly<[ReadonlyArray<T[TKey]>, ReadonlyArray<T[TKey]>]>\n >,\n key: TKey,\n): Array<T> | null {\n const getSubsetIndex = (\n item: T[TKey],\n subsets: ReadonlyArray<ReadonlyArray<T[TKey]> | Array<T[TKey]>>,\n ): number | null => {\n for (let i = 0; i < subsets.length; i++) {\n if (subsets[i]?.includes(item)) {\n return i\n }\n }\n return null\n }\n\n const orderSets = orderRules.reduce(\n (sets, [A, B]) => [...sets, A, B],\n [] as Array<ReadonlyArray<T[TKey]> | Array<T[TKey]>>,\n )\n\n const inOrderArray = data.filter(\n (item) => getSubsetIndex(item[key], orderSets) !== null,\n )\n\n let wasResorted = false as boolean\n\n // Sort by the relative order defined by the rules\n const sortedArray = inOrderArray.sort((a, b) => {\n const aKey = a[key],\n bKey = b[key]\n const aSubsetIndex = getSubsetIndex(aKey, orderSets)\n const bSubsetIndex = getSubsetIndex(bKey, orderSets)\n\n // If both items belong to different subsets, sort by their subset order\n if (\n aSubsetIndex !== null &&\n bSubsetIndex !== null &&\n aSubsetIndex !== bSubsetIndex\n ) {\n return aSubsetIndex - bSubsetIndex\n }\n\n // If both items belong to the same subset or neither is in the subset, keep their relative order\n return 0\n })\n\n const inOrderIterator = sortedArray.values()\n const result = data.map((item) => {\n if (getSubsetIndex(item[key], orderSets) !== null) {\n const sortedItem = inOrderIterator.next().value!\n if (sortedItem[key] !== item[key]) {\n wasResorted = true\n }\n return sortedItem\n }\n return item\n })\n\n if (!wasResorted) {\n return null\n }\n return result\n}\n"],"names":[],"mappings":";;AAAgB,SAAA,gBACd,MACA,YAGA,KACiB;AACX,QAAA,iBAAiB,CACrB,MACA,YACkB;;AAClB,aAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,WAAI,aAAQ,CAAC,MAAT,mBAAY,SAAS,OAAO;AACvB,eAAA;AAAA,MACT;AAAA,IACF;AACO,WAAA;AAAA,EAAA;AAGT,QAAM,YAAY,WAAW;AAAA,IAC3B,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,GAAG,CAAC;AAAA,IAChC,CAAC;AAAA,EAAA;AAGH,QAAM,eAAe,KAAK;AAAA,IACxB,CAAC,SAAS,eAAe,KAAK,GAAG,GAAG,SAAS,MAAM;AAAA,EAAA;AAGrD,MAAI,cAAc;AAGlB,QAAM,cAAc,aAAa,KAAK,CAAC,GAAG,MAAM;AAC9C,UAAM,OAAO,EAAE,GAAG,GAChB,OAAO,EAAE,GAAG;AACR,UAAA,eAAe,eAAe,MAAM,SAAS;AAC7C,UAAA,eAAe,eAAe,MAAM,SAAS;AAGnD,QACE,iBAAiB,QACjB,iBAAiB,QACjB,iBAAiB,cACjB;AACA,aAAO,eAAe;AAAA,IACxB;AAGO,WAAA;AAAA,EAAA,CACR;AAEK,QAAA,kBAAkB,YAAY;AACpC,QAAM,SAAS,KAAK,IAAI,CAAC,SAAS;AAChC,QAAI,eAAe,KAAK,GAAG,GAAG,SAAS,MAAM,MAAM;AAC3C,YAAA,aAAa,gBAAgB,KAAA,EAAO;AAC1C,UAAI,WAAW,GAAG,MAAM,KAAK,GAAG,GAAG;AACnB,sBAAA;AAAA,MAChB;AACO,aAAA;AAAA,IACT;AACO,WAAA;AAAA,EAAA,CACR;AAED,MAAI,CAAC,aAAa;AACT,WAAA;AAAA,EACT;AACO,SAAA;AACT;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function sortDataByOrder<T, TKey extends keyof T>(data: Array<T> | ReadonlyArray<T>,
|
|
1
|
+
export declare function sortDataByOrder<T, TKey extends keyof T>(data: Array<T> | ReadonlyArray<T>, orderRules: ReadonlyArray<Readonly<[ReadonlyArray<T[TKey]>, ReadonlyArray<T[TKey]>]>>, key: TKey): Array<T> | null;
|
|
@@ -3,3 +3,4 @@ export declare const createRouteFunctionsDirect: readonly ["createRootRoute", "c
|
|
|
3
3
|
export declare const createRouteFunctions: readonly ["createRootRoute", "createRoute", "createFileRoute", "createRootRouteWithContext"];
|
|
4
4
|
export type CreateRouteFunction = (typeof createRouteFunctions)[number];
|
|
5
5
|
export declare const checkedProperties: readonly ["params", "validateSearch", "context", "beforeLoad", "loaderDeps", "loader"];
|
|
6
|
+
export declare const sortRules: readonly [readonly [readonly ["params", "validateSearch"], readonly ["context"]], readonly [readonly ["context"], readonly ["beforeLoad"]], readonly [readonly ["beforeLoad"], readonly ["loaderDeps"]], readonly [readonly ["loaderDeps"], readonly ["loader"]]];
|
|
@@ -10,18 +10,16 @@ const createRouteFunctions = [
|
|
|
10
10
|
...createRouteFunctionsDirect,
|
|
11
11
|
...createRouteFunctionsIndirect
|
|
12
12
|
];
|
|
13
|
-
const
|
|
14
|
-
"params",
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"loaderDeps",
|
|
19
|
-
"loader"
|
|
13
|
+
const sortRules = [
|
|
14
|
+
[["params", "validateSearch"], ["context"]],
|
|
15
|
+
[["context"], ["beforeLoad"]],
|
|
16
|
+
[["beforeLoad"], ["loaderDeps"]],
|
|
17
|
+
[["loaderDeps"], ["loader"]]
|
|
20
18
|
];
|
|
21
19
|
export {
|
|
22
|
-
checkedProperties,
|
|
23
20
|
createRouteFunctions,
|
|
24
21
|
createRouteFunctionsDirect,
|
|
25
|
-
createRouteFunctionsIndirect
|
|
22
|
+
createRouteFunctionsIndirect,
|
|
23
|
+
sortRules
|
|
26
24
|
};
|
|
27
25
|
//# sourceMappingURL=constants.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","sources":["../../../../src/rules/create-route-property-order/constants.ts"],"sourcesContent":["export const createRouteFunctionsIndirect = [\n 'createFileRoute',\n 'createRootRouteWithContext',\n] as const\nexport const createRouteFunctionsDirect = [\n 'createRootRoute',\n 'createRoute',\n] as const\n\nexport const createRouteFunctions = [\n ...createRouteFunctionsDirect,\n ...createRouteFunctionsIndirect,\n] as const\n\nexport type CreateRouteFunction = (typeof createRouteFunctions)[number]\n\nexport const checkedProperties = [\n 'params',\n 'validateSearch',\n 'context',\n 'beforeLoad',\n 'loaderDeps',\n 'loader',\n] as const\n"],"names":[],"mappings":"AAAO,MAAM,+BAA+B;AAAA,EAC1C;AAAA,EACA;AACF;AACO,MAAM,6BAA6B;AAAA,EACxC;AAAA,EACA;AACF;AAEO,MAAM,uBAAuB;AAAA,EAClC,GAAG;AAAA,EACH,GAAG;AACL;
|
|
1
|
+
{"version":3,"file":"constants.js","sources":["../../../../src/rules/create-route-property-order/constants.ts"],"sourcesContent":["export const createRouteFunctionsIndirect = [\n 'createFileRoute',\n 'createRootRouteWithContext',\n] as const\nexport const createRouteFunctionsDirect = [\n 'createRootRoute',\n 'createRoute',\n] as const\n\nexport const createRouteFunctions = [\n ...createRouteFunctionsDirect,\n ...createRouteFunctionsIndirect,\n] as const\n\nexport type CreateRouteFunction = (typeof createRouteFunctions)[number]\n\nexport const checkedProperties = [\n 'params',\n 'validateSearch',\n 'context',\n 'beforeLoad',\n 'loaderDeps',\n 'loader',\n] as const\n\nexport const sortRules = [\n [['params', 'validateSearch'], ['context']],\n [['context'], ['beforeLoad']],\n [['beforeLoad'], ['loaderDeps']],\n [['loaderDeps'], ['loader']],\n] as const\n"],"names":[],"mappings":"AAAO,MAAM,+BAA+B;AAAA,EAC1C;AAAA,EACA;AACF;AACO,MAAM,6BAA6B;AAAA,EACxC;AAAA,EACA;AACF;AAEO,MAAM,uBAAuB;AAAA,EAClC,GAAG;AAAA,EACH,GAAG;AACL;AAaO,MAAM,YAAY;AAAA,EACvB,CAAC,CAAC,UAAU,gBAAgB,GAAG,CAAC,SAAS,CAAC;AAAA,EAC1C,CAAC,CAAC,SAAS,GAAG,CAAC,YAAY,CAAC;AAAA,EAC5B,CAAC,CAAC,YAAY,GAAG,CAAC,YAAY,CAAC;AAAA,EAC/B,CAAC,CAAC,YAAY,GAAG,CAAC,QAAQ,CAAC;AAC7B;"}
|
|
@@ -2,7 +2,7 @@ import { ESLintUtils, AST_NODE_TYPES } from "@typescript-eslint/utils";
|
|
|
2
2
|
import { getDocsUrl } from "../../utils/get-docs-url.js";
|
|
3
3
|
import { detectTanstackRouterImports } from "../../utils/detect-router-imports.js";
|
|
4
4
|
import { sortDataByOrder } from "./create-route-property-order.utils.js";
|
|
5
|
-
import { createRouteFunctions, createRouteFunctionsIndirect,
|
|
5
|
+
import { createRouteFunctions, createRouteFunctionsIndirect, sortRules } from "./constants.js";
|
|
6
6
|
const createRule = ESLintUtils.RuleCreator(getDocsUrl);
|
|
7
7
|
const createRouteFunctionSet = new Set(createRouteFunctions);
|
|
8
8
|
function isCreateRouteFunction(node) {
|
|
@@ -48,6 +48,9 @@ const rule = createRule({
|
|
|
48
48
|
return;
|
|
49
49
|
}
|
|
50
50
|
const allProperties = argument.properties;
|
|
51
|
+
if (allProperties.length < 2) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
51
54
|
const properties = allProperties.flatMap((p) => {
|
|
52
55
|
if (p.type === AST_NODE_TYPES.Property && p.key.type === AST_NODE_TYPES.Identifier) {
|
|
53
56
|
return { name: p.key.name, property: p };
|
|
@@ -60,11 +63,7 @@ const rule = createRule({
|
|
|
60
63
|
}
|
|
61
64
|
return [];
|
|
62
65
|
});
|
|
63
|
-
const sortedProperties = sortDataByOrder(
|
|
64
|
-
properties,
|
|
65
|
-
checkedProperties,
|
|
66
|
-
"name"
|
|
67
|
-
);
|
|
66
|
+
const sortedProperties = sortDataByOrder(properties, sortRules, "name");
|
|
68
67
|
if (sortedProperties === null) {
|
|
69
68
|
return;
|
|
70
69
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-route-property-order.rule.js","sources":["../../../../src/rules/create-route-property-order/create-route-property-order.rule.ts"],"sourcesContent":["import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils'\n\nimport { getDocsUrl } from '../../utils/get-docs-url'\nimport { detectTanstackRouterImports } from '../../utils/detect-router-imports'\nimport { sortDataByOrder } from './create-route-property-order.utils'\nimport {\n
|
|
1
|
+
{"version":3,"file":"create-route-property-order.rule.js","sources":["../../../../src/rules/create-route-property-order/create-route-property-order.rule.ts"],"sourcesContent":["import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils'\n\nimport { getDocsUrl } from '../../utils/get-docs-url'\nimport { detectTanstackRouterImports } from '../../utils/detect-router-imports'\nimport { sortDataByOrder } from './create-route-property-order.utils'\nimport {\n createRouteFunctions,\n createRouteFunctionsIndirect,\n sortRules,\n} from './constants'\nimport type { CreateRouteFunction } from './constants'\nimport type { ExtraRuleDocs } from '../../types'\n\nconst createRule = ESLintUtils.RuleCreator<ExtraRuleDocs>(getDocsUrl)\n\nconst createRouteFunctionSet = new Set(createRouteFunctions)\nfunction isCreateRouteFunction(node: any): node is CreateRouteFunction {\n return createRouteFunctionSet.has(node)\n}\n\nexport const name = 'create-route-property-order'\n\nexport const rule = createRule({\n name,\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Ensure correct order of inference sensitive properties for createRoute functions',\n recommended: 'error',\n },\n messages: {\n invalidOrder: 'Invalid order of properties for `{{function}}`.',\n },\n schema: [],\n hasSuggestions: true,\n fixable: 'code',\n },\n defaultOptions: [],\n\n create: detectTanstackRouterImports((context) => {\n return {\n CallExpression(node) {\n if (node.callee.type !== AST_NODE_TYPES.Identifier) {\n return\n }\n const createRouteFunction = node.callee.name\n if (!isCreateRouteFunction(createRouteFunction)) {\n return\n }\n let args = node.arguments\n if (createRouteFunctionsIndirect.includes(createRouteFunction as any)) {\n if (node.parent.type === AST_NODE_TYPES.CallExpression) {\n args = node.parent.arguments\n } else {\n return\n }\n }\n\n const argument = args[0]\n if (argument === undefined || argument.type !== 'ObjectExpression') {\n return\n }\n\n const allProperties = argument.properties\n // no need to sort if there is at max 1 property\n if (allProperties.length < 2) {\n return\n }\n\n const properties = allProperties.flatMap((p) => {\n if (\n p.type === AST_NODE_TYPES.Property &&\n p.key.type === AST_NODE_TYPES.Identifier\n ) {\n return { name: p.key.name, property: p }\n } else if (p.type === AST_NODE_TYPES.SpreadElement) {\n if (p.argument.type === AST_NODE_TYPES.Identifier) {\n return { name: p.argument.name, property: p }\n } else {\n throw new Error('Unsupported spread element')\n }\n }\n return []\n })\n\n const sortedProperties = sortDataByOrder(properties, sortRules, 'name')\n if (sortedProperties === null) {\n return\n }\n context.report({\n node: argument,\n data: { function: node.callee.name },\n messageId: 'invalidOrder',\n fix(fixer) {\n const sourceCode = context.sourceCode\n\n const text = sortedProperties.reduce(\n (sourceText, specifier, index) => {\n let text = ''\n if (index < allProperties.length - 1) {\n text = sourceCode\n .getText()\n .slice(\n allProperties[index]!.range[1],\n allProperties[index + 1]!.range[0],\n )\n }\n return (\n sourceText + sourceCode.getText(specifier.property) + text\n )\n },\n '',\n )\n return fixer.replaceTextRange(\n [allProperties[0]!.range[0], allProperties.at(-1)!.range[1]],\n text,\n )\n },\n })\n },\n }\n }),\n})\n"],"names":["text"],"mappings":";;;;;AAaA,MAAM,aAAa,YAAY,YAA2B,UAAU;AAEpE,MAAM,yBAAyB,IAAI,IAAI,oBAAoB;AAC3D,SAAS,sBAAsB,MAAwC;AAC9D,SAAA,uBAAuB,IAAI,IAAI;AACxC;AAEO,MAAM,OAAO;AAEb,MAAM,OAAO,WAAW;AAAA,EAC7B;AAAA,EACA,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,MACJ,aACE;AAAA,MACF,aAAa;AAAA,IACf;AAAA,IACA,UAAU;AAAA,MACR,cAAc;AAAA,IAChB;AAAA,IACA,QAAQ,CAAC;AAAA,IACT,gBAAgB;AAAA,IAChB,SAAS;AAAA,EACX;AAAA,EACA,gBAAgB,CAAC;AAAA,EAEjB,QAAQ,4BAA4B,CAAC,YAAY;AACxC,WAAA;AAAA,MACL,eAAe,MAAM;AACnB,YAAI,KAAK,OAAO,SAAS,eAAe,YAAY;AAClD;AAAA,QACF;AACM,cAAA,sBAAsB,KAAK,OAAO;AACpC,YAAA,CAAC,sBAAsB,mBAAmB,GAAG;AAC/C;AAAA,QACF;AACA,YAAI,OAAO,KAAK;AACZ,YAAA,6BAA6B,SAAS,mBAA0B,GAAG;AACrE,cAAI,KAAK,OAAO,SAAS,eAAe,gBAAgB;AACtD,mBAAO,KAAK,OAAO;AAAA,UAAA,OACd;AACL;AAAA,UACF;AAAA,QACF;AAEM,cAAA,WAAW,KAAK,CAAC;AACvB,YAAI,aAAa,UAAa,SAAS,SAAS,oBAAoB;AAClE;AAAA,QACF;AAEA,cAAM,gBAAgB,SAAS;AAE3B,YAAA,cAAc,SAAS,GAAG;AAC5B;AAAA,QACF;AAEA,cAAM,aAAa,cAAc,QAAQ,CAAC,MAAM;AAE5C,cAAA,EAAE,SAAS,eAAe,YAC1B,EAAE,IAAI,SAAS,eAAe,YAC9B;AACA,mBAAO,EAAE,MAAM,EAAE,IAAI,MAAM,UAAU;UAC5B,WAAA,EAAE,SAAS,eAAe,eAAe;AAClD,gBAAI,EAAE,SAAS,SAAS,eAAe,YAAY;AACjD,qBAAO,EAAE,MAAM,EAAE,SAAS,MAAM,UAAU;YAAE,OACvC;AACC,oBAAA,IAAI,MAAM,4BAA4B;AAAA,YAC9C;AAAA,UACF;AACA,iBAAO;QAAC,CACT;AAED,cAAM,mBAAmB,gBAAgB,YAAY,WAAW,MAAM;AACtE,YAAI,qBAAqB,MAAM;AAC7B;AAAA,QACF;AACA,gBAAQ,OAAO;AAAA,UACb,MAAM;AAAA,UACN,MAAM,EAAE,UAAU,KAAK,OAAO,KAAK;AAAA,UACnC,WAAW;AAAA,UACX,IAAI,OAAO;AACT,kBAAM,aAAa,QAAQ;AAE3B,kBAAM,OAAO,iBAAiB;AAAA,cAC5B,CAAC,YAAY,WAAW,UAAU;AAChC,oBAAIA,QAAO;AACP,oBAAA,QAAQ,cAAc,SAAS,GAAG;AACpCA,0BAAO,WACJ,QAAA,EACA;AAAA,oBACC,cAAc,KAAK,EAAG,MAAM,CAAC;AAAA,oBAC7B,cAAc,QAAQ,CAAC,EAAG,MAAM,CAAC;AAAA,kBAAA;AAAA,gBAEvC;AACA,uBACE,aAAa,WAAW,QAAQ,UAAU,QAAQ,IAAIA;AAAAA,cAE1D;AAAA,cACA;AAAA,YAAA;AAEF,mBAAO,MAAM;AAAA,cACX,CAAC,cAAc,CAAC,EAAG,MAAM,CAAC,GAAG,cAAc,GAAG,EAAE,EAAG,MAAM,CAAC,CAAC;AAAA,cAC3D;AAAA,YAAA;AAAA,UAEJ;AAAA,QAAA,CACD;AAAA,MACH;AAAA,IAAA;AAAA,EACF,CACD;AACH,CAAC;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function sortDataByOrder<T, TKey extends keyof T>(data: Array<T> | ReadonlyArray<T>,
|
|
1
|
+
export declare function sortDataByOrder<T, TKey extends keyof T>(data: Array<T> | ReadonlyArray<T>, orderRules: ReadonlyArray<Readonly<[ReadonlyArray<T[TKey]>, ReadonlyArray<T[TKey]>]>>, key: TKey): Array<T> | null;
|
|
@@ -1,14 +1,33 @@
|
|
|
1
|
-
function sortDataByOrder(data,
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
function sortDataByOrder(data, orderRules, key) {
|
|
2
|
+
const getSubsetIndex = (item, subsets) => {
|
|
3
|
+
var _a;
|
|
4
|
+
for (let i = 0; i < subsets.length; i++) {
|
|
5
|
+
if ((_a = subsets[i]) == null ? void 0 : _a.includes(item)) {
|
|
6
|
+
return i;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
return null;
|
|
10
|
+
};
|
|
11
|
+
const orderSets = orderRules.reduce(
|
|
12
|
+
(sets, [A, B]) => [...sets, A, B],
|
|
13
|
+
[]
|
|
14
|
+
);
|
|
15
|
+
const inOrderArray = data.filter(
|
|
16
|
+
(item) => getSubsetIndex(item[key], orderSets) !== null
|
|
17
|
+
);
|
|
9
18
|
let wasResorted = false;
|
|
19
|
+
const sortedArray = inOrderArray.sort((a, b) => {
|
|
20
|
+
const aKey = a[key], bKey = b[key];
|
|
21
|
+
const aSubsetIndex = getSubsetIndex(aKey, orderSets);
|
|
22
|
+
const bSubsetIndex = getSubsetIndex(bKey, orderSets);
|
|
23
|
+
if (aSubsetIndex !== null && bSubsetIndex !== null && aSubsetIndex !== bSubsetIndex) {
|
|
24
|
+
return aSubsetIndex - bSubsetIndex;
|
|
25
|
+
}
|
|
26
|
+
return 0;
|
|
27
|
+
});
|
|
28
|
+
const inOrderIterator = sortedArray.values();
|
|
10
29
|
const result = data.map((item) => {
|
|
11
|
-
if (
|
|
30
|
+
if (getSubsetIndex(item[key], orderSets) !== null) {
|
|
12
31
|
const sortedItem = inOrderIterator.next().value;
|
|
13
32
|
if (sortedItem[key] !== item[key]) {
|
|
14
33
|
wasResorted = true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-route-property-order.utils.js","sources":["../../../../src/rules/create-route-property-order/create-route-property-order.utils.ts"],"sourcesContent":["export function sortDataByOrder<T, TKey extends keyof T>(\n data: Array<T> | ReadonlyArray<T>,\n
|
|
1
|
+
{"version":3,"file":"create-route-property-order.utils.js","sources":["../../../../src/rules/create-route-property-order/create-route-property-order.utils.ts"],"sourcesContent":["export function sortDataByOrder<T, TKey extends keyof T>(\n data: Array<T> | ReadonlyArray<T>,\n orderRules: ReadonlyArray<\n Readonly<[ReadonlyArray<T[TKey]>, ReadonlyArray<T[TKey]>]>\n >,\n key: TKey,\n): Array<T> | null {\n const getSubsetIndex = (\n item: T[TKey],\n subsets: ReadonlyArray<ReadonlyArray<T[TKey]> | Array<T[TKey]>>,\n ): number | null => {\n for (let i = 0; i < subsets.length; i++) {\n if (subsets[i]?.includes(item)) {\n return i\n }\n }\n return null\n }\n\n const orderSets = orderRules.reduce(\n (sets, [A, B]) => [...sets, A, B],\n [] as Array<ReadonlyArray<T[TKey]> | Array<T[TKey]>>,\n )\n\n const inOrderArray = data.filter(\n (item) => getSubsetIndex(item[key], orderSets) !== null,\n )\n\n let wasResorted = false as boolean\n\n // Sort by the relative order defined by the rules\n const sortedArray = inOrderArray.sort((a, b) => {\n const aKey = a[key],\n bKey = b[key]\n const aSubsetIndex = getSubsetIndex(aKey, orderSets)\n const bSubsetIndex = getSubsetIndex(bKey, orderSets)\n\n // If both items belong to different subsets, sort by their subset order\n if (\n aSubsetIndex !== null &&\n bSubsetIndex !== null &&\n aSubsetIndex !== bSubsetIndex\n ) {\n return aSubsetIndex - bSubsetIndex\n }\n\n // If both items belong to the same subset or neither is in the subset, keep their relative order\n return 0\n })\n\n const inOrderIterator = sortedArray.values()\n const result = data.map((item) => {\n if (getSubsetIndex(item[key], orderSets) !== null) {\n const sortedItem = inOrderIterator.next().value!\n if (sortedItem[key] !== item[key]) {\n wasResorted = true\n }\n return sortedItem\n }\n return item\n })\n\n if (!wasResorted) {\n return null\n }\n return result\n}\n"],"names":[],"mappings":"AAAgB,SAAA,gBACd,MACA,YAGA,KACiB;AACX,QAAA,iBAAiB,CACrB,MACA,YACkB;AAVN;AAWZ,aAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,WAAI,aAAQ,CAAC,MAAT,mBAAY,SAAS,OAAO;AACvB,eAAA;AAAA,MACT;AAAA,IACF;AACO,WAAA;AAAA,EAAA;AAGT,QAAM,YAAY,WAAW;AAAA,IAC3B,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,GAAG,CAAC;AAAA,IAChC,CAAC;AAAA,EAAA;AAGH,QAAM,eAAe,KAAK;AAAA,IACxB,CAAC,SAAS,eAAe,KAAK,GAAG,GAAG,SAAS,MAAM;AAAA,EAAA;AAGrD,MAAI,cAAc;AAGlB,QAAM,cAAc,aAAa,KAAK,CAAC,GAAG,MAAM;AAC9C,UAAM,OAAO,EAAE,GAAG,GAChB,OAAO,EAAE,GAAG;AACR,UAAA,eAAe,eAAe,MAAM,SAAS;AAC7C,UAAA,eAAe,eAAe,MAAM,SAAS;AAGnD,QACE,iBAAiB,QACjB,iBAAiB,QACjB,iBAAiB,cACjB;AACA,aAAO,eAAe;AAAA,IACxB;AAGO,WAAA;AAAA,EAAA,CACR;AAEK,QAAA,kBAAkB,YAAY;AACpC,QAAM,SAAS,KAAK,IAAI,CAAC,SAAS;AAChC,QAAI,eAAe,KAAK,GAAG,GAAG,SAAS,MAAM,MAAM;AAC3C,YAAA,aAAa,gBAAgB,KAAA,EAAO;AAC1C,UAAI,WAAW,GAAG,MAAM,KAAK,GAAG,GAAG;AACnB,sBAAA;AAAA,MAChB;AACO,aAAA;AAAA,IACT;AACO,WAAA;AAAA,EAAA,CACR;AAED,MAAI,CAAC,aAAa;AACT,WAAA;AAAA,EACT;AACO,SAAA;AACT;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/eslint-plugin-router",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.60.0",
|
|
4
4
|
"description": "ESLint plugin for TanStack Router",
|
|
5
5
|
"author": "Manuel Schiller",
|
|
6
6
|
"license": "MIT",
|
|
@@ -37,12 +37,12 @@
|
|
|
37
37
|
"src"
|
|
38
38
|
],
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@typescript-eslint/utils": "^8.
|
|
40
|
+
"@typescript-eslint/utils": "^8.8.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"@typescript-eslint/rule-tester": "^8.
|
|
43
|
+
"@typescript-eslint/rule-tester": "^8.8.0",
|
|
44
44
|
"combinate": "^1.1.11",
|
|
45
|
-
"eslint": "^9.
|
|
45
|
+
"eslint": "^9.12.0"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"eslint": "^8.57.0 || ^9.0.0"
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { RuleTester } from '@typescript-eslint/rule-tester'
|
|
2
2
|
import combinate from 'combinate'
|
|
3
|
+
|
|
3
4
|
import {
|
|
4
5
|
name,
|
|
5
6
|
rule,
|
|
@@ -23,6 +24,7 @@ const testedCheckedProperties = [
|
|
|
23
24
|
checkedProperties[0],
|
|
24
25
|
checkedProperties[1],
|
|
25
26
|
checkedProperties[2],
|
|
27
|
+
checkedProperties[3],
|
|
26
28
|
]
|
|
27
29
|
type TestedCheckedProperties = (typeof testedCheckedProperties)[number]
|
|
28
30
|
const orderIndependentProps = ['gcTime', '...foo'] as const
|
|
@@ -45,18 +47,49 @@ const validTestMatrix = combinate({
|
|
|
45
47
|
properties: generatePartialCombinations(testedCheckedProperties, 2),
|
|
46
48
|
})
|
|
47
49
|
|
|
48
|
-
export function generateInvalidPermutations
|
|
49
|
-
arr: ReadonlyArray<
|
|
50
|
-
): Array<{
|
|
50
|
+
export function generateInvalidPermutations(
|
|
51
|
+
arr: ReadonlyArray<TestedCheckedProperties>,
|
|
52
|
+
): Array<{
|
|
53
|
+
invalid: Array<TestedCheckedProperties>
|
|
54
|
+
valid: Array<TestedCheckedProperties>
|
|
55
|
+
}> {
|
|
51
56
|
const combinations = generatePartialCombinations(arr, 2)
|
|
52
|
-
const allPermutations: Array<{
|
|
57
|
+
const allPermutations: Array<{
|
|
58
|
+
invalid: Array<TestedCheckedProperties>
|
|
59
|
+
valid: Array<TestedCheckedProperties>
|
|
60
|
+
}> = []
|
|
53
61
|
|
|
54
62
|
for (const combination of combinations) {
|
|
55
63
|
const permutations = generatePermutations(combination)
|
|
56
64
|
// skip the first permutation as it matches the original combination
|
|
57
65
|
const invalidPermutations = permutations.slice(1)
|
|
66
|
+
|
|
67
|
+
if (
|
|
68
|
+
combination.includes('params') &&
|
|
69
|
+
combination.includes('validateSearch')
|
|
70
|
+
) {
|
|
71
|
+
if (
|
|
72
|
+
combination.indexOf('params') < combination.indexOf('validateSearch')
|
|
73
|
+
) {
|
|
74
|
+
// since we ignore the relative order of 'params' and 'validateSearch', we skip this combination (but keep the other one where `validateSearch` is before `params`)
|
|
75
|
+
continue
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
58
79
|
allPermutations.push(
|
|
59
|
-
...invalidPermutations.map((p) =>
|
|
80
|
+
...invalidPermutations.map((p) => {
|
|
81
|
+
// ignore the relative order of 'params' and 'validateSearch'
|
|
82
|
+
const correctedValid = [...combination].sort((a, b) => {
|
|
83
|
+
if (
|
|
84
|
+
(a === 'params' && b === 'validateSearch') ||
|
|
85
|
+
(a === 'validateSearch' && b === 'params')
|
|
86
|
+
) {
|
|
87
|
+
return p.indexOf(a) - p.indexOf(b)
|
|
88
|
+
}
|
|
89
|
+
return checkedProperties.indexOf(a) - checkedProperties.indexOf(b)
|
|
90
|
+
})
|
|
91
|
+
return { invalid: p, valid: correctedValid }
|
|
92
|
+
}),
|
|
60
93
|
)
|
|
61
94
|
}
|
|
62
95
|
|
|
@@ -6,43 +6,64 @@ describe('create-route-property-order utils', () => {
|
|
|
6
6
|
const testCases = [
|
|
7
7
|
{
|
|
8
8
|
data: [{ key: 'a' }, { key: 'c' }, { key: 'b' }],
|
|
9
|
-
orderArray: [
|
|
9
|
+
orderArray: [
|
|
10
|
+
[['a'], ['b']],
|
|
11
|
+
[['b'], ['c']],
|
|
12
|
+
],
|
|
10
13
|
key: 'key',
|
|
11
14
|
expected: [{ key: 'a' }, { key: 'b' }, { key: 'c' }],
|
|
12
15
|
},
|
|
13
16
|
{
|
|
14
17
|
data: [{ key: 'b' }, { key: 'a' }, { key: 'c' }],
|
|
15
|
-
orderArray: [
|
|
18
|
+
orderArray: [
|
|
19
|
+
[['a'], ['b']],
|
|
20
|
+
[['b'], ['c']],
|
|
21
|
+
],
|
|
16
22
|
key: 'key',
|
|
17
23
|
expected: [{ key: 'a' }, { key: 'b' }, { key: 'c' }],
|
|
18
24
|
},
|
|
19
25
|
{
|
|
20
26
|
data: [{ key: 'a' }, { key: 'b' }, { key: 'c' }],
|
|
21
|
-
orderArray: [
|
|
27
|
+
orderArray: [
|
|
28
|
+
[['a'], ['b']],
|
|
29
|
+
[['b'], ['c']],
|
|
30
|
+
],
|
|
22
31
|
key: 'key',
|
|
23
32
|
expected: null,
|
|
24
33
|
},
|
|
25
34
|
{
|
|
26
35
|
data: [{ key: 'a' }, { key: 'b' }, { key: 'c' }, { key: 'd' }],
|
|
27
|
-
orderArray: [
|
|
36
|
+
orderArray: [
|
|
37
|
+
[['a'], ['b']],
|
|
38
|
+
[['b'], ['c']],
|
|
39
|
+
],
|
|
28
40
|
key: 'key',
|
|
29
41
|
expected: null,
|
|
30
42
|
},
|
|
31
43
|
{
|
|
32
44
|
data: [{ key: 'a' }, { key: 'b' }, { key: 'd' }, { key: 'c' }],
|
|
33
|
-
orderArray: [
|
|
45
|
+
orderArray: [
|
|
46
|
+
[['a'], ['b']],
|
|
47
|
+
[['b'], ['c']],
|
|
48
|
+
],
|
|
34
49
|
key: 'key',
|
|
35
50
|
expected: null,
|
|
36
51
|
},
|
|
37
52
|
{
|
|
38
53
|
data: [{ key: 'd' }, { key: 'a' }, { key: 'b' }, { key: 'c' }],
|
|
39
|
-
orderArray: [
|
|
54
|
+
orderArray: [
|
|
55
|
+
[['a'], ['b']],
|
|
56
|
+
[['b'], ['c']],
|
|
57
|
+
],
|
|
40
58
|
key: 'key',
|
|
41
59
|
expected: null,
|
|
42
60
|
},
|
|
43
61
|
{
|
|
44
62
|
data: [{ key: 'd' }, { key: 'b' }, { key: 'a' }, { key: 'c' }],
|
|
45
|
-
orderArray: [
|
|
63
|
+
orderArray: [
|
|
64
|
+
[['a'], ['b']],
|
|
65
|
+
[['b'], ['c']],
|
|
66
|
+
],
|
|
46
67
|
key: 'key',
|
|
47
68
|
expected: [{ key: 'd' }, { key: 'a' }, { key: 'b' }, { key: 'c' }],
|
|
48
69
|
},
|
|
@@ -22,3 +22,10 @@ export const checkedProperties = [
|
|
|
22
22
|
'loaderDeps',
|
|
23
23
|
'loader',
|
|
24
24
|
] as const
|
|
25
|
+
|
|
26
|
+
export const sortRules = [
|
|
27
|
+
[['params', 'validateSearch'], ['context']],
|
|
28
|
+
[['context'], ['beforeLoad']],
|
|
29
|
+
[['beforeLoad'], ['loaderDeps']],
|
|
30
|
+
[['loaderDeps'], ['loader']],
|
|
31
|
+
] as const
|
|
@@ -4,9 +4,9 @@ import { getDocsUrl } from '../../utils/get-docs-url'
|
|
|
4
4
|
import { detectTanstackRouterImports } from '../../utils/detect-router-imports'
|
|
5
5
|
import { sortDataByOrder } from './create-route-property-order.utils'
|
|
6
6
|
import {
|
|
7
|
-
checkedProperties,
|
|
8
7
|
createRouteFunctions,
|
|
9
8
|
createRouteFunctionsIndirect,
|
|
9
|
+
sortRules,
|
|
10
10
|
} from './constants'
|
|
11
11
|
import type { CreateRouteFunction } from './constants'
|
|
12
12
|
import type { ExtraRuleDocs } from '../../types'
|
|
@@ -63,8 +63,11 @@ export const rule = createRule({
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
const allProperties = argument.properties
|
|
66
|
+
// no need to sort if there is at max 1 property
|
|
67
|
+
if (allProperties.length < 2) {
|
|
68
|
+
return
|
|
69
|
+
}
|
|
66
70
|
|
|
67
|
-
// TODO we need to support spread elements, they would be discarded here
|
|
68
71
|
const properties = allProperties.flatMap((p) => {
|
|
69
72
|
if (
|
|
70
73
|
p.type === AST_NODE_TYPES.Property &&
|
|
@@ -81,11 +84,7 @@ export const rule = createRule({
|
|
|
81
84
|
return []
|
|
82
85
|
})
|
|
83
86
|
|
|
84
|
-
const sortedProperties = sortDataByOrder(
|
|
85
|
-
properties,
|
|
86
|
-
checkedProperties,
|
|
87
|
-
'name',
|
|
88
|
-
)
|
|
87
|
+
const sortedProperties = sortDataByOrder(properties, sortRules, 'name')
|
|
89
88
|
if (sortedProperties === null) {
|
|
90
89
|
return
|
|
91
90
|
}
|
|
@@ -1,27 +1,56 @@
|
|
|
1
1
|
export function sortDataByOrder<T, TKey extends keyof T>(
|
|
2
2
|
data: Array<T> | ReadonlyArray<T>,
|
|
3
|
-
|
|
3
|
+
orderRules: ReadonlyArray<
|
|
4
|
+
Readonly<[ReadonlyArray<T[TKey]>, ReadonlyArray<T[TKey]>]>
|
|
5
|
+
>,
|
|
4
6
|
key: TKey,
|
|
5
7
|
): Array<T> | null {
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
const getSubsetIndex = (
|
|
9
|
+
item: T[TKey],
|
|
10
|
+
subsets: ReadonlyArray<ReadonlyArray<T[TKey]> | Array<T[TKey]>>,
|
|
11
|
+
): number | null => {
|
|
12
|
+
for (let i = 0; i < subsets.length; i++) {
|
|
13
|
+
if (subsets[i]?.includes(item)) {
|
|
14
|
+
return i
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return null
|
|
18
|
+
}
|
|
14
19
|
|
|
15
|
-
|
|
16
|
-
|
|
20
|
+
const orderSets = orderRules.reduce(
|
|
21
|
+
(sets, [A, B]) => [...sets, A, B],
|
|
22
|
+
[] as Array<ReadonlyArray<T[TKey]> | Array<T[TKey]>>,
|
|
23
|
+
)
|
|
17
24
|
|
|
18
|
-
const
|
|
25
|
+
const inOrderArray = data.filter(
|
|
26
|
+
(item) => getSubsetIndex(item[key], orderSets) !== null,
|
|
27
|
+
)
|
|
19
28
|
|
|
20
|
-
// `as boolean` is needed to avoid TS incorrectly inferring that wasResorted is always `true`
|
|
21
29
|
let wasResorted = false as boolean
|
|
22
30
|
|
|
31
|
+
// Sort by the relative order defined by the rules
|
|
32
|
+
const sortedArray = inOrderArray.sort((a, b) => {
|
|
33
|
+
const aKey = a[key],
|
|
34
|
+
bKey = b[key]
|
|
35
|
+
const aSubsetIndex = getSubsetIndex(aKey, orderSets)
|
|
36
|
+
const bSubsetIndex = getSubsetIndex(bKey, orderSets)
|
|
37
|
+
|
|
38
|
+
// If both items belong to different subsets, sort by their subset order
|
|
39
|
+
if (
|
|
40
|
+
aSubsetIndex !== null &&
|
|
41
|
+
bSubsetIndex !== null &&
|
|
42
|
+
aSubsetIndex !== bSubsetIndex
|
|
43
|
+
) {
|
|
44
|
+
return aSubsetIndex - bSubsetIndex
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// If both items belong to the same subset or neither is in the subset, keep their relative order
|
|
48
|
+
return 0
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
const inOrderIterator = sortedArray.values()
|
|
23
52
|
const result = data.map((item) => {
|
|
24
|
-
if (
|
|
53
|
+
if (getSubsetIndex(item[key], orderSets) !== null) {
|
|
25
54
|
const sortedItem = inOrderIterator.next().value!
|
|
26
55
|
if (sortedItem[key] !== item[key]) {
|
|
27
56
|
wasResorted = true
|