@tanstack/eslint-plugin-query 5.57.0 → 5.57.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/rules/infinite-query-property-order/constants.cjs +3 -5
- package/dist/cjs/rules/infinite-query-property-order/constants.cjs.map +1 -1
- package/dist/cjs/rules/infinite-query-property-order/constants.d.cts +1 -0
- package/dist/cjs/rules/infinite-query-property-order/infinite-query-property-order.rule.cjs +6 -7
- package/dist/cjs/rules/infinite-query-property-order/infinite-query-property-order.rule.cjs.map +1 -1
- package/dist/cjs/rules/infinite-query-property-order/infinite-query-property-order.utils.cjs +28 -9
- package/dist/cjs/rules/infinite-query-property-order/infinite-query-property-order.utils.cjs.map +1 -1
- package/dist/cjs/rules/infinite-query-property-order/infinite-query-property-order.utils.d.cts +1 -1
- package/dist/esm/rules/infinite-query-property-order/constants.d.ts +1 -0
- package/dist/esm/rules/infinite-query-property-order/constants.js +4 -6
- package/dist/esm/rules/infinite-query-property-order/constants.js.map +1 -1
- package/dist/esm/rules/infinite-query-property-order/infinite-query-property-order.rule.js +7 -8
- package/dist/esm/rules/infinite-query-property-order/infinite-query-property-order.rule.js.map +1 -1
- package/dist/esm/rules/infinite-query-property-order/infinite-query-property-order.utils.d.ts +1 -1
- package/dist/esm/rules/infinite-query-property-order/infinite-query-property-order.utils.js +28 -9
- package/dist/esm/rules/infinite-query-property-order/infinite-query-property-order.utils.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/infinite-query-property-order.rule.test.ts +91 -12
- package/src/__tests__/infinite-query-property-order.utils.test.ts +28 -7
- package/src/rules/infinite-query-property-order/constants.ts +4 -0
- package/src/rules/infinite-query-property-order/infinite-query-property-order.rule.ts +9 -8
- package/src/rules/infinite-query-property-order/infinite-query-property-order.utils.ts +43 -14
|
@@ -5,11 +5,9 @@ const infiniteQueryFunctions = [
|
|
|
5
5
|
"useInfiniteQuery",
|
|
6
6
|
"useSuspenseInfiniteQuery"
|
|
7
7
|
];
|
|
8
|
-
const
|
|
9
|
-
"queryFn",
|
|
10
|
-
"getPreviousPageParam",
|
|
11
|
-
"getNextPageParam"
|
|
8
|
+
const sortRules = [
|
|
9
|
+
[["queryFn"], ["getPreviousPageParam", "getNextPageParam"]]
|
|
12
10
|
];
|
|
13
|
-
exports.checkedProperties = checkedProperties;
|
|
14
11
|
exports.infiniteQueryFunctions = infiniteQueryFunctions;
|
|
12
|
+
exports.sortRules = sortRules;
|
|
15
13
|
//# sourceMappingURL=constants.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.cjs","sources":["../../../../src/rules/infinite-query-property-order/constants.ts"],"sourcesContent":["export const infiniteQueryFunctions = [\n 'infiniteQueryOptions',\n 'useInfiniteQuery',\n 'useSuspenseInfiniteQuery',\n] as const\n\nexport type InfiniteQueryFunctions = (typeof infiniteQueryFunctions)[number]\n\nexport const checkedProperties = [\n 'queryFn',\n 'getPreviousPageParam',\n 'getNextPageParam',\n] as const\n"],"names":[],"mappings":";;AAAO,MAAM,yBAAyB;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AACF;
|
|
1
|
+
{"version":3,"file":"constants.cjs","sources":["../../../../src/rules/infinite-query-property-order/constants.ts"],"sourcesContent":["export const infiniteQueryFunctions = [\n 'infiniteQueryOptions',\n 'useInfiniteQuery',\n 'useSuspenseInfiniteQuery',\n] as const\n\nexport type InfiniteQueryFunctions = (typeof infiniteQueryFunctions)[number]\n\nexport const checkedProperties = [\n 'queryFn',\n 'getPreviousPageParam',\n 'getNextPageParam',\n] as const\n\nexport const sortRules = [\n [['queryFn'], ['getPreviousPageParam', 'getNextPageParam']],\n] as const\n"],"names":[],"mappings":";;AAAO,MAAM,yBAAyB;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AACF;AAUO,MAAM,YAAY;AAAA,EACvB,CAAC,CAAC,SAAS,GAAG,CAAC,wBAAwB,kBAAkB,CAAC;AAC5D;;;"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export declare const infiniteQueryFunctions: readonly ["infiniteQueryOptions", "useInfiniteQuery", "useSuspenseInfiniteQuery"];
|
|
2
2
|
export type InfiniteQueryFunctions = (typeof infiniteQueryFunctions)[number];
|
|
3
3
|
export declare const checkedProperties: readonly ["queryFn", "getPreviousPageParam", "getNextPageParam"];
|
|
4
|
+
export declare const sortRules: readonly [readonly [readonly ["queryFn"], readonly ["getPreviousPageParam", "getNextPageParam"]]];
|
|
@@ -51,17 +51,16 @@ const rule = createRule({
|
|
|
51
51
|
} else if (p.type === utils.AST_NODE_TYPES.SpreadElement) {
|
|
52
52
|
if (p.argument.type === utils.AST_NODE_TYPES.Identifier) {
|
|
53
53
|
return { name: p.argument.name, property: p };
|
|
54
|
-
} else {
|
|
55
|
-
|
|
54
|
+
} else if (p.argument.type === utils.AST_NODE_TYPES.CallExpression) {
|
|
55
|
+
if (p.argument.callee.type === utils.AST_NODE_TYPES.Identifier) {
|
|
56
|
+
return { name: p.argument.callee.name, property: p };
|
|
57
|
+
}
|
|
56
58
|
}
|
|
59
|
+
throw new Error("Unsupported spread element");
|
|
57
60
|
}
|
|
58
61
|
return [];
|
|
59
62
|
});
|
|
60
|
-
const sortedProperties = infiniteQueryPropertyOrder_utils.sortDataByOrder(
|
|
61
|
-
properties,
|
|
62
|
-
constants.checkedProperties,
|
|
63
|
-
"name"
|
|
64
|
-
);
|
|
63
|
+
const sortedProperties = infiniteQueryPropertyOrder_utils.sortDataByOrder(properties, constants.sortRules, "name");
|
|
65
64
|
if (sortedProperties === null) {
|
|
66
65
|
return;
|
|
67
66
|
}
|
package/dist/cjs/rules/infinite-query-property-order/infinite-query-property-order.rule.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"infinite-query-property-order.rule.cjs","sources":["../../../../src/rules/infinite-query-property-order/infinite-query-property-order.rule.ts"],"sourcesContent":["import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils'\n\nimport { getDocsUrl } from '../../utils/get-docs-url'\nimport { detectTanstackQueryImports } from '../../utils/detect-react-query-imports'\nimport { sortDataByOrder } from './infinite-query-property-order.utils'\nimport {
|
|
1
|
+
{"version":3,"file":"infinite-query-property-order.rule.cjs","sources":["../../../../src/rules/infinite-query-property-order/infinite-query-property-order.rule.ts"],"sourcesContent":["import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils'\n\nimport { getDocsUrl } from '../../utils/get-docs-url'\nimport { detectTanstackQueryImports } from '../../utils/detect-react-query-imports'\nimport { sortDataByOrder } from './infinite-query-property-order.utils'\nimport { infiniteQueryFunctions, sortRules } from './constants'\nimport type { InfiniteQueryFunctions } from './constants'\nimport type { ExtraRuleDocs } from '../../types'\n\nconst createRule = ESLintUtils.RuleCreator<ExtraRuleDocs>(getDocsUrl)\n\nconst infiniteQueryFunctionsSet = new Set(infiniteQueryFunctions)\nfunction isInfiniteQueryFunction(node: any): node is InfiniteQueryFunctions {\n return infiniteQueryFunctionsSet.has(node)\n}\n\nexport const name = 'infinite-query-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 infinite queries',\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: detectTanstackQueryImports((context) => {\n return {\n CallExpression(node) {\n if (node.callee.type !== AST_NODE_TYPES.Identifier) {\n return\n }\n const infiniteQueryFunction = node.callee.name\n if (!isInfiniteQueryFunction(infiniteQueryFunction)) {\n return\n }\n const argument = node.arguments[0]\n if (argument === undefined || argument.type !== 'ObjectExpression') {\n return\n }\n\n const allProperties = argument.properties\n\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 if (p.argument.type === AST_NODE_TYPES.CallExpression) {\n if (p.argument.callee.type === AST_NODE_TYPES.Identifier) {\n return { name: p.argument.callee.name, property: p }\n }\n }\n throw new Error('Unsupported spread element')\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","infiniteQueryFunctions","detectTanstackQueryImports","AST_NODE_TYPES","sortDataByOrder","sortRules","text"],"mappings":";;;;;;;AASA,MAAM,aAAaA,MAAY,YAAA,YAA2BC,WAAAA,UAAU;AAEpE,MAAM,4BAA4B,IAAI,IAAIC,UAAAA,sBAAsB;AAChE,SAAS,wBAAwB,MAA2C;AACnE,SAAA,0BAA0B,IAAI,IAAI;AAC3C;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,wBAAAA,2BAA2B,CAAC,YAAY;AACvC,WAAA;AAAA,MACL,eAAe,MAAM;AACnB,YAAI,KAAK,OAAO,SAASC,MAAAA,eAAe,YAAY;AAClD;AAAA,QACF;AACM,cAAA,wBAAwB,KAAK,OAAO;AACtC,YAAA,CAAC,wBAAwB,qBAAqB,GAAG;AACnD;AAAA,QACF;AACM,cAAA,WAAW,KAAK,UAAU,CAAC;AACjC,YAAI,aAAa,UAAa,SAAS,SAAS,oBAAoB;AAClE;AAAA,QACF;AAEA,cAAM,gBAAgB,SAAS;AAG3B,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;YACjC,WAAA,EAAE,SAAS,SAASA,MAAAA,eAAe,gBAAgB;AAC5D,kBAAI,EAAE,SAAS,OAAO,SAASA,MAAAA,eAAe,YAAY;AACxD,uBAAO,EAAE,MAAM,EAAE,SAAS,OAAO,MAAM,UAAU;cACnD;AAAA,YACF;AACM,kBAAA,IAAI,MAAM,4BAA4B;AAAA,UAC9C;AACA,iBAAO;QAAC,CACT;AAED,cAAM,mBAAmBC,iCAAA,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;;;"}
|
package/dist/cjs/rules/infinite-query-property-order/infinite-query-property-order.utils.cjs
CHANGED
|
@@ -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/infinite-query-property-order/infinite-query-property-order.utils.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"infinite-query-property-order.utils.cjs","sources":["../../../../src/rules/infinite-query-property-order/infinite-query-property-order.utils.ts"],"sourcesContent":["export function sortDataByOrder<T, TKey extends keyof T>(\n data: Array<T> | ReadonlyArray<T>,\n
|
|
1
|
+
{"version":3,"file":"infinite-query-property-order.utils.cjs","sources":["../../../../src/rules/infinite-query-property-order/infinite-query-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;;"}
|
package/dist/cjs/rules/infinite-query-property-order/infinite-query-property-order.utils.d.cts
CHANGED
|
@@ -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,3 +1,4 @@
|
|
|
1
1
|
export declare const infiniteQueryFunctions: readonly ["infiniteQueryOptions", "useInfiniteQuery", "useSuspenseInfiniteQuery"];
|
|
2
2
|
export type InfiniteQueryFunctions = (typeof infiniteQueryFunctions)[number];
|
|
3
3
|
export declare const checkedProperties: readonly ["queryFn", "getPreviousPageParam", "getNextPageParam"];
|
|
4
|
+
export declare const sortRules: readonly [readonly [readonly ["queryFn"], readonly ["getPreviousPageParam", "getNextPageParam"]]];
|
|
@@ -3,13 +3,11 @@ const infiniteQueryFunctions = [
|
|
|
3
3
|
"useInfiniteQuery",
|
|
4
4
|
"useSuspenseInfiniteQuery"
|
|
5
5
|
];
|
|
6
|
-
const
|
|
7
|
-
"queryFn",
|
|
8
|
-
"getPreviousPageParam",
|
|
9
|
-
"getNextPageParam"
|
|
6
|
+
const sortRules = [
|
|
7
|
+
[["queryFn"], ["getPreviousPageParam", "getNextPageParam"]]
|
|
10
8
|
];
|
|
11
9
|
export {
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
infiniteQueryFunctions,
|
|
11
|
+
sortRules
|
|
14
12
|
};
|
|
15
13
|
//# sourceMappingURL=constants.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","sources":["../../../../src/rules/infinite-query-property-order/constants.ts"],"sourcesContent":["export const infiniteQueryFunctions = [\n 'infiniteQueryOptions',\n 'useInfiniteQuery',\n 'useSuspenseInfiniteQuery',\n] as const\n\nexport type InfiniteQueryFunctions = (typeof infiniteQueryFunctions)[number]\n\nexport const checkedProperties = [\n 'queryFn',\n 'getPreviousPageParam',\n 'getNextPageParam',\n] as const\n"],"names":[],"mappings":"AAAO,MAAM,yBAAyB;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AACF;
|
|
1
|
+
{"version":3,"file":"constants.js","sources":["../../../../src/rules/infinite-query-property-order/constants.ts"],"sourcesContent":["export const infiniteQueryFunctions = [\n 'infiniteQueryOptions',\n 'useInfiniteQuery',\n 'useSuspenseInfiniteQuery',\n] as const\n\nexport type InfiniteQueryFunctions = (typeof infiniteQueryFunctions)[number]\n\nexport const checkedProperties = [\n 'queryFn',\n 'getPreviousPageParam',\n 'getNextPageParam',\n] as const\n\nexport const sortRules = [\n [['queryFn'], ['getPreviousPageParam', 'getNextPageParam']],\n] as const\n"],"names":[],"mappings":"AAAO,MAAM,yBAAyB;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AACF;AAUO,MAAM,YAAY;AAAA,EACvB,CAAC,CAAC,SAAS,GAAG,CAAC,wBAAwB,kBAAkB,CAAC;AAC5D;"}
|
|
@@ -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 { detectTanstackQueryImports } from "../../utils/detect-react-query-imports.js";
|
|
4
4
|
import { sortDataByOrder } from "./infinite-query-property-order.utils.js";
|
|
5
|
-
import { infiniteQueryFunctions,
|
|
5
|
+
import { infiniteQueryFunctions, sortRules } from "./constants.js";
|
|
6
6
|
const createRule = ESLintUtils.RuleCreator(getDocsUrl);
|
|
7
7
|
const infiniteQueryFunctionsSet = new Set(infiniteQueryFunctions);
|
|
8
8
|
function isInfiniteQueryFunction(node) {
|
|
@@ -49,17 +49,16 @@ const rule = createRule({
|
|
|
49
49
|
} else if (p.type === AST_NODE_TYPES.SpreadElement) {
|
|
50
50
|
if (p.argument.type === AST_NODE_TYPES.Identifier) {
|
|
51
51
|
return { name: p.argument.name, property: p };
|
|
52
|
-
} else {
|
|
53
|
-
|
|
52
|
+
} else if (p.argument.type === AST_NODE_TYPES.CallExpression) {
|
|
53
|
+
if (p.argument.callee.type === AST_NODE_TYPES.Identifier) {
|
|
54
|
+
return { name: p.argument.callee.name, property: p };
|
|
55
|
+
}
|
|
54
56
|
}
|
|
57
|
+
throw new Error("Unsupported spread element");
|
|
55
58
|
}
|
|
56
59
|
return [];
|
|
57
60
|
});
|
|
58
|
-
const sortedProperties = sortDataByOrder(
|
|
59
|
-
properties,
|
|
60
|
-
checkedProperties,
|
|
61
|
-
"name"
|
|
62
|
-
);
|
|
61
|
+
const sortedProperties = sortDataByOrder(properties, sortRules, "name");
|
|
63
62
|
if (sortedProperties === null) {
|
|
64
63
|
return;
|
|
65
64
|
}
|
package/dist/esm/rules/infinite-query-property-order/infinite-query-property-order.rule.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"infinite-query-property-order.rule.js","sources":["../../../../src/rules/infinite-query-property-order/infinite-query-property-order.rule.ts"],"sourcesContent":["import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils'\n\nimport { getDocsUrl } from '../../utils/get-docs-url'\nimport { detectTanstackQueryImports } from '../../utils/detect-react-query-imports'\nimport { sortDataByOrder } from './infinite-query-property-order.utils'\nimport {
|
|
1
|
+
{"version":3,"file":"infinite-query-property-order.rule.js","sources":["../../../../src/rules/infinite-query-property-order/infinite-query-property-order.rule.ts"],"sourcesContent":["import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils'\n\nimport { getDocsUrl } from '../../utils/get-docs-url'\nimport { detectTanstackQueryImports } from '../../utils/detect-react-query-imports'\nimport { sortDataByOrder } from './infinite-query-property-order.utils'\nimport { infiniteQueryFunctions, sortRules } from './constants'\nimport type { InfiniteQueryFunctions } from './constants'\nimport type { ExtraRuleDocs } from '../../types'\n\nconst createRule = ESLintUtils.RuleCreator<ExtraRuleDocs>(getDocsUrl)\n\nconst infiniteQueryFunctionsSet = new Set(infiniteQueryFunctions)\nfunction isInfiniteQueryFunction(node: any): node is InfiniteQueryFunctions {\n return infiniteQueryFunctionsSet.has(node)\n}\n\nexport const name = 'infinite-query-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 infinite queries',\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: detectTanstackQueryImports((context) => {\n return {\n CallExpression(node) {\n if (node.callee.type !== AST_NODE_TYPES.Identifier) {\n return\n }\n const infiniteQueryFunction = node.callee.name\n if (!isInfiniteQueryFunction(infiniteQueryFunction)) {\n return\n }\n const argument = node.arguments[0]\n if (argument === undefined || argument.type !== 'ObjectExpression') {\n return\n }\n\n const allProperties = argument.properties\n\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 if (p.argument.type === AST_NODE_TYPES.CallExpression) {\n if (p.argument.callee.type === AST_NODE_TYPES.Identifier) {\n return { name: p.argument.callee.name, property: p }\n }\n }\n throw new Error('Unsupported spread element')\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":";;;;;AASA,MAAM,aAAa,YAAY,YAA2B,UAAU;AAEpE,MAAM,4BAA4B,IAAI,IAAI,sBAAsB;AAChE,SAAS,wBAAwB,MAA2C;AACnE,SAAA,0BAA0B,IAAI,IAAI;AAC3C;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,2BAA2B,CAAC,YAAY;AACvC,WAAA;AAAA,MACL,eAAe,MAAM;AACnB,YAAI,KAAK,OAAO,SAAS,eAAe,YAAY;AAClD;AAAA,QACF;AACM,cAAA,wBAAwB,KAAK,OAAO;AACtC,YAAA,CAAC,wBAAwB,qBAAqB,GAAG;AACnD;AAAA,QACF;AACM,cAAA,WAAW,KAAK,UAAU,CAAC;AACjC,YAAI,aAAa,UAAa,SAAS,SAAS,oBAAoB;AAClE;AAAA,QACF;AAEA,cAAM,gBAAgB,SAAS;AAG3B,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;YACjC,WAAA,EAAE,SAAS,SAAS,eAAe,gBAAgB;AAC5D,kBAAI,EAAE,SAAS,OAAO,SAAS,eAAe,YAAY;AACxD,uBAAO,EAAE,MAAM,EAAE,SAAS,OAAO,MAAM,UAAU;cACnD;AAAA,YACF;AACM,kBAAA,IAAI,MAAM,4BAA4B;AAAA,UAC9C;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;"}
|
package/dist/esm/rules/infinite-query-property-order/infinite-query-property-order.utils.d.ts
CHANGED
|
@@ -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;
|
package/dist/esm/rules/infinite-query-property-order/infinite-query-property-order.utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"infinite-query-property-order.utils.js","sources":["../../../../src/rules/infinite-query-property-order/infinite-query-property-order.utils.ts"],"sourcesContent":["export function sortDataByOrder<T, TKey extends keyof T>(\n data: Array<T> | ReadonlyArray<T>,\n
|
|
1
|
+
{"version":3,"file":"infinite-query-property-order.utils.js","sources":["../../../../src/rules/infinite-query-property-order/infinite-query-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
|
@@ -13,13 +13,18 @@ import {
|
|
|
13
13
|
generateInterleavedCombinations,
|
|
14
14
|
generatePartialCombinations,
|
|
15
15
|
generatePermutations,
|
|
16
|
+
normalizeIndent,
|
|
16
17
|
} from './test-utils'
|
|
17
18
|
import type { InfiniteQueryFunctions } from '../rules/infinite-query-property-order/constants'
|
|
18
19
|
|
|
19
20
|
const ruleTester = new RuleTester()
|
|
20
21
|
|
|
21
22
|
type CheckedProperties = (typeof checkedProperties)[number]
|
|
22
|
-
const orderIndependentProps = [
|
|
23
|
+
const orderIndependentProps = [
|
|
24
|
+
'queryKey',
|
|
25
|
+
'...objectExpressionSpread',
|
|
26
|
+
'...callExpressionSpread',
|
|
27
|
+
] as const
|
|
23
28
|
type OrderIndependentProps = (typeof orderIndependentProps)[number]
|
|
24
29
|
|
|
25
30
|
interface TestCase {
|
|
@@ -32,18 +37,60 @@ const validTestMatrix = combinate({
|
|
|
32
37
|
properties: generatePartialCombinations(checkedProperties, 2),
|
|
33
38
|
})
|
|
34
39
|
|
|
35
|
-
export function generateInvalidPermutations
|
|
36
|
-
arr: ReadonlyArray<
|
|
37
|
-
): Array<{
|
|
40
|
+
export function generateInvalidPermutations(
|
|
41
|
+
arr: ReadonlyArray<CheckedProperties>,
|
|
42
|
+
): Array<{
|
|
43
|
+
invalid: Array<CheckedProperties>
|
|
44
|
+
valid: Array<CheckedProperties>
|
|
45
|
+
}> {
|
|
38
46
|
const combinations = generatePartialCombinations(arr, 2)
|
|
39
|
-
const allPermutations: Array<{
|
|
47
|
+
const allPermutations: Array<{
|
|
48
|
+
invalid: Array<CheckedProperties>
|
|
49
|
+
valid: Array<CheckedProperties>
|
|
50
|
+
}> = []
|
|
40
51
|
|
|
41
52
|
for (const combination of combinations) {
|
|
42
53
|
const permutations = generatePermutations(combination)
|
|
43
54
|
// skip the first permutation as it matches the original combination
|
|
44
55
|
const invalidPermutations = permutations.slice(1)
|
|
56
|
+
|
|
57
|
+
if (
|
|
58
|
+
combination.includes('getNextPageParam') &&
|
|
59
|
+
combination.includes('getPreviousPageParam')
|
|
60
|
+
) {
|
|
61
|
+
if (
|
|
62
|
+
combination.indexOf('getNextPageParam') <
|
|
63
|
+
combination.indexOf('getPreviousPageParam')
|
|
64
|
+
) {
|
|
65
|
+
// since we ignore the relative order of 'getPreviousPageParam' and 'getNextPageParam', we skip this combination (but keep the other one where `getPreviousPageParam` is before `getNextPageParam`)
|
|
66
|
+
|
|
67
|
+
continue
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
45
71
|
allPermutations.push(
|
|
46
|
-
...invalidPermutations
|
|
72
|
+
...invalidPermutations
|
|
73
|
+
.map((p) => {
|
|
74
|
+
// ignore the relative order of 'getPreviousPageParam' and 'getNextPageParam'
|
|
75
|
+
const correctedValid = [...combination].sort((a, b) => {
|
|
76
|
+
if (
|
|
77
|
+
(a === 'getNextPageParam' && b === 'getPreviousPageParam') ||
|
|
78
|
+
(a === 'getPreviousPageParam' && b === 'getNextPageParam')
|
|
79
|
+
) {
|
|
80
|
+
return p.indexOf(a) - p.indexOf(b)
|
|
81
|
+
}
|
|
82
|
+
return checkedProperties.indexOf(a) - checkedProperties.indexOf(b)
|
|
83
|
+
})
|
|
84
|
+
return { invalid: p, valid: correctedValid }
|
|
85
|
+
})
|
|
86
|
+
.filter(
|
|
87
|
+
({ invalid }) =>
|
|
88
|
+
// if `getPreviousPageParam` and `getNextPageParam` are next to each other and `queryFn` is not present, we skip this invalid permutation
|
|
89
|
+
Math.abs(
|
|
90
|
+
invalid.indexOf('getNextPageParam') -
|
|
91
|
+
invalid.indexOf('getPreviousPageParam'),
|
|
92
|
+
) !== 1,
|
|
93
|
+
),
|
|
47
94
|
)
|
|
48
95
|
}
|
|
49
96
|
|
|
@@ -80,6 +127,14 @@ const invalidTestMatrix = combinate({
|
|
|
80
127
|
properties: interleavedInvalidPermutations,
|
|
81
128
|
})
|
|
82
129
|
|
|
130
|
+
const callExpressionSpread = normalizeIndent`
|
|
131
|
+
...communitiesQuery({
|
|
132
|
+
filters: {
|
|
133
|
+
...fieldValues,
|
|
134
|
+
placementFormats: [],
|
|
135
|
+
},
|
|
136
|
+
})`
|
|
137
|
+
|
|
83
138
|
function getCode({
|
|
84
139
|
infiniteQueryFunction: infiniteQueryFunction,
|
|
85
140
|
properties,
|
|
@@ -87,10 +142,11 @@ function getCode({
|
|
|
87
142
|
function getPropertyCode(
|
|
88
143
|
property: CheckedProperties | OrderIndependentProps,
|
|
89
144
|
) {
|
|
90
|
-
if (property.startsWith('...')) {
|
|
91
|
-
return property
|
|
92
|
-
}
|
|
93
145
|
switch (property) {
|
|
146
|
+
case '...objectExpressionSpread':
|
|
147
|
+
return `...objectExpressionSpread`
|
|
148
|
+
case '...callExpressionSpread':
|
|
149
|
+
return callExpressionSpread
|
|
94
150
|
case 'queryKey':
|
|
95
151
|
return `queryKey: ['projects']`
|
|
96
152
|
case 'queryFn':
|
|
@@ -100,8 +156,6 @@ function getCode({
|
|
|
100
156
|
case 'getNextPageParam':
|
|
101
157
|
return 'getNextPageParam: (lastPage) => lastPage.nextId ?? undefined'
|
|
102
158
|
}
|
|
103
|
-
|
|
104
|
-
return `${property}: () => null`
|
|
105
159
|
}
|
|
106
160
|
return `
|
|
107
161
|
import { ${infiniteQueryFunction} } from '@tanstack/react-query'
|
|
@@ -121,7 +175,7 @@ const validTestCases = validTestMatrix.map(
|
|
|
121
175
|
|
|
122
176
|
const invalidTestCases = invalidTestMatrix.map(
|
|
123
177
|
({ infiniteQueryFunction, properties }) => ({
|
|
124
|
-
name: `incorrect property order is detected for ${infiniteQueryFunction} with order: ${properties.invalid.join(', ')}`,
|
|
178
|
+
name: `incorrect property order is detected for ${infiniteQueryFunction} with invalid order: ${properties.invalid.join(', ')}, valid order: ${properties.valid.join(', ')}`,
|
|
125
179
|
code: getCode({
|
|
126
180
|
infiniteQueryFunction: infiniteQueryFunction,
|
|
127
181
|
properties: properties.invalid,
|
|
@@ -138,3 +192,28 @@ ruleTester.run(name, rule, {
|
|
|
138
192
|
valid: validTestCases,
|
|
139
193
|
invalid: invalidTestCases,
|
|
140
194
|
})
|
|
195
|
+
|
|
196
|
+
// regression tests
|
|
197
|
+
|
|
198
|
+
const regressionTestCases = {
|
|
199
|
+
valid: [
|
|
200
|
+
{
|
|
201
|
+
name: 'should pass with call expression spread',
|
|
202
|
+
code: normalizeIndent`
|
|
203
|
+
import { useInfiniteQuery } from '@tanstack/react-query'
|
|
204
|
+
const { data, isFetching, isLoading, hasNextPage, fetchNextPage } =
|
|
205
|
+
useInfiniteQuery({
|
|
206
|
+
...communitiesQuery({
|
|
207
|
+
filters: {
|
|
208
|
+
...fieldValues,
|
|
209
|
+
placementFormats: [],
|
|
210
|
+
},
|
|
211
|
+
}),
|
|
212
|
+
refetchOnMount: false,
|
|
213
|
+
})`,
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
invalid: [],
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
ruleTester.run(name, rule, regressionTestCases)
|
|
@@ -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
|
},
|
|
@@ -3,7 +3,7 @@ import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils'
|
|
|
3
3
|
import { getDocsUrl } from '../../utils/get-docs-url'
|
|
4
4
|
import { detectTanstackQueryImports } from '../../utils/detect-react-query-imports'
|
|
5
5
|
import { sortDataByOrder } from './infinite-query-property-order.utils'
|
|
6
|
-
import {
|
|
6
|
+
import { infiniteQueryFunctions, sortRules } from './constants'
|
|
7
7
|
import type { InfiniteQueryFunctions } from './constants'
|
|
8
8
|
import type { ExtraRuleDocs } from '../../types'
|
|
9
9
|
|
|
@@ -50,6 +50,8 @@ export const rule = createRule({
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
const allProperties = argument.properties
|
|
53
|
+
|
|
54
|
+
// no need to sort if there is at max 1 property
|
|
53
55
|
if (allProperties.length < 2) {
|
|
54
56
|
return
|
|
55
57
|
}
|
|
@@ -63,18 +65,17 @@ export const rule = createRule({
|
|
|
63
65
|
} else if (p.type === AST_NODE_TYPES.SpreadElement) {
|
|
64
66
|
if (p.argument.type === AST_NODE_TYPES.Identifier) {
|
|
65
67
|
return { name: p.argument.name, property: p }
|
|
66
|
-
} else {
|
|
67
|
-
|
|
68
|
+
} else if (p.argument.type === AST_NODE_TYPES.CallExpression) {
|
|
69
|
+
if (p.argument.callee.type === AST_NODE_TYPES.Identifier) {
|
|
70
|
+
return { name: p.argument.callee.name, property: p }
|
|
71
|
+
}
|
|
68
72
|
}
|
|
73
|
+
throw new Error('Unsupported spread element')
|
|
69
74
|
}
|
|
70
75
|
return []
|
|
71
76
|
})
|
|
72
77
|
|
|
73
|
-
const sortedProperties = sortDataByOrder(
|
|
74
|
-
properties,
|
|
75
|
-
checkedProperties,
|
|
76
|
-
'name',
|
|
77
|
-
)
|
|
78
|
+
const sortedProperties = sortDataByOrder(properties, sortRules, 'name')
|
|
78
79
|
if (sortedProperties === null) {
|
|
79
80
|
return
|
|
80
81
|
}
|
|
@@ -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
|