@tanstack/eslint-plugin-query 5.56.1 → 5.57.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.
Files changed (41) hide show
  1. package/dist/cjs/index.cjs +4 -2
  2. package/dist/cjs/index.cjs.map +1 -1
  3. package/dist/cjs/rules/infinite-query-property-order/constants.cjs +15 -0
  4. package/dist/cjs/rules/infinite-query-property-order/constants.cjs.map +1 -0
  5. package/dist/cjs/rules/infinite-query-property-order/constants.d.cts +3 -0
  6. package/dist/cjs/rules/infinite-query-property-order/infinite-query-property-order.rule.cjs +99 -0
  7. package/dist/cjs/rules/infinite-query-property-order/infinite-query-property-order.rule.cjs.map +1 -0
  8. package/dist/cjs/rules/infinite-query-property-order/infinite-query-property-order.rule.d.cts +4 -0
  9. package/dist/cjs/rules/infinite-query-property-order/infinite-query-property-order.utils.cjs +28 -0
  10. package/dist/cjs/rules/infinite-query-property-order/infinite-query-property-order.utils.cjs.map +1 -0
  11. package/dist/cjs/rules/infinite-query-property-order/infinite-query-property-order.utils.d.cts +1 -0
  12. package/dist/cjs/rules/no-unstable-deps/no-unstable-deps.rule.cjs +1 -1
  13. package/dist/cjs/rules/no-unstable-deps/no-unstable-deps.rule.cjs.map +1 -1
  14. package/dist/cjs/rules.cjs +3 -1
  15. package/dist/cjs/rules.cjs.map +1 -1
  16. package/dist/esm/index.js +4 -2
  17. package/dist/esm/index.js.map +1 -1
  18. package/dist/esm/rules/infinite-query-property-order/constants.d.ts +3 -0
  19. package/dist/esm/rules/infinite-query-property-order/constants.js +15 -0
  20. package/dist/esm/rules/infinite-query-property-order/constants.js.map +1 -0
  21. package/dist/esm/rules/infinite-query-property-order/infinite-query-property-order.rule.d.ts +4 -0
  22. package/dist/esm/rules/infinite-query-property-order/infinite-query-property-order.rule.js +99 -0
  23. package/dist/esm/rules/infinite-query-property-order/infinite-query-property-order.rule.js.map +1 -0
  24. package/dist/esm/rules/infinite-query-property-order/infinite-query-property-order.utils.d.ts +1 -0
  25. package/dist/esm/rules/infinite-query-property-order/infinite-query-property-order.utils.js +28 -0
  26. package/dist/esm/rules/infinite-query-property-order/infinite-query-property-order.utils.js.map +1 -0
  27. package/dist/esm/rules/no-unstable-deps/no-unstable-deps.rule.js +1 -1
  28. package/dist/esm/rules/no-unstable-deps/no-unstable-deps.rule.js.map +1 -1
  29. package/dist/esm/rules.js +3 -1
  30. package/dist/esm/rules.js.map +1 -1
  31. package/package.json +2 -1
  32. package/src/__tests__/infinite-query-property-order.rule.test.ts +140 -0
  33. package/src/__tests__/infinite-query-property-order.utils.test.ts +58 -0
  34. package/src/__tests__/test-utils.test.ts +104 -0
  35. package/src/__tests__/test-utils.ts +103 -0
  36. package/src/index.ts +2 -0
  37. package/src/rules/infinite-query-property-order/constants.ts +13 -0
  38. package/src/rules/infinite-query-property-order/infinite-query-property-order.rule.ts +114 -0
  39. package/src/rules/infinite-query-property-order/infinite-query-property-order.utils.ts +38 -0
  40. package/src/rules/no-unstable-deps/no-unstable-deps.rule.ts +1 -1
  41. package/src/rules.ts +2 -0
@@ -14,7 +14,8 @@ Object.assign(plugin.configs, {
14
14
  "@tanstack/query/exhaustive-deps": "error",
15
15
  "@tanstack/query/no-rest-destructuring": "warn",
16
16
  "@tanstack/query/stable-query-client": "error",
17
- "@tanstack/query/no-unstable-deps": "error"
17
+ "@tanstack/query/no-unstable-deps": "error",
18
+ "@tanstack/query/infinite-query-property-order": "error"
18
19
  }
19
20
  },
20
21
  "flat/recommended": [
@@ -26,7 +27,8 @@ Object.assign(plugin.configs, {
26
27
  "@tanstack/query/exhaustive-deps": "error",
27
28
  "@tanstack/query/no-rest-destructuring": "warn",
28
29
  "@tanstack/query/stable-query-client": "error",
29
- "@tanstack/query/no-unstable-deps": "error"
30
+ "@tanstack/query/no-unstable-deps": "error",
31
+ "@tanstack/query/infinite-query-property-order": "error"
30
32
  }
31
33
  }
32
34
  ]
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../../src/index.ts"],"sourcesContent":["import { rules } from './rules'\nimport type { ESLint, Linter } from 'eslint'\nimport type { RuleModule } from '@typescript-eslint/utils/ts-eslint'\n\ntype RuleKey = keyof typeof rules\n\ninterface Plugin extends Omit<ESLint.Plugin, 'rules'> {\n rules: Record<RuleKey, RuleModule<any, any, any>>\n configs: {\n recommended: ESLint.ConfigData\n 'flat/recommended': Array<Linter.FlatConfig>\n }\n}\n\nconst plugin: Plugin = {\n meta: {\n name: '@tanstack/eslint-plugin-query',\n },\n configs: {} as Plugin['configs'],\n rules,\n}\n\n// Assign configs here so we can reference `plugin`\nObject.assign(plugin.configs, {\n recommended: {\n plugins: ['@tanstack/eslint-plugin-query'],\n rules: {\n '@tanstack/query/exhaustive-deps': 'error',\n '@tanstack/query/no-rest-destructuring': 'warn',\n '@tanstack/query/stable-query-client': 'error',\n '@tanstack/query/no-unstable-deps': 'error',\n },\n },\n 'flat/recommended': [\n {\n plugins: {\n '@tanstack/query': plugin,\n },\n rules: {\n '@tanstack/query/exhaustive-deps': 'error',\n '@tanstack/query/no-rest-destructuring': 'warn',\n '@tanstack/query/stable-query-client': 'error',\n '@tanstack/query/no-unstable-deps': 'error',\n },\n },\n ],\n})\n\nexport default plugin\n"],"names":["rules"],"mappings":";;AAcA,MAAM,SAAiB;AAAA,EACrB,MAAM;AAAA,IACJ,MAAM;AAAA,EACR;AAAA,EACA,SAAS,CAAC;AAAA,EAAA,OACVA,MAAA;AACF;AAGA,OAAO,OAAO,OAAO,SAAS;AAAA,EAC5B,aAAa;AAAA,IACX,SAAS,CAAC,+BAA+B;AAAA,IACzC,OAAO;AAAA,MACL,mCAAmC;AAAA,MACnC,yCAAyC;AAAA,MACzC,uCAAuC;AAAA,MACvC,oCAAoC;AAAA,IACtC;AAAA,EACF;AAAA,EACA,oBAAoB;AAAA,IAClB;AAAA,MACE,SAAS;AAAA,QACP,mBAAmB;AAAA,MACrB;AAAA,MACA,OAAO;AAAA,QACL,mCAAmC;AAAA,QACnC,yCAAyC;AAAA,QACzC,uCAAuC;AAAA,QACvC,oCAAoC;AAAA,MACtC;AAAA,IACF;AAAA,EACF;AACF,CAAC;;"}
1
+ {"version":3,"file":"index.cjs","sources":["../../src/index.ts"],"sourcesContent":["import { rules } from './rules'\nimport type { ESLint, Linter } from 'eslint'\nimport type { RuleModule } from '@typescript-eslint/utils/ts-eslint'\n\ntype RuleKey = keyof typeof rules\n\ninterface Plugin extends Omit<ESLint.Plugin, 'rules'> {\n rules: Record<RuleKey, RuleModule<any, any, any>>\n configs: {\n recommended: ESLint.ConfigData\n 'flat/recommended': Array<Linter.FlatConfig>\n }\n}\n\nconst plugin: Plugin = {\n meta: {\n name: '@tanstack/eslint-plugin-query',\n },\n configs: {} as Plugin['configs'],\n rules,\n}\n\n// Assign configs here so we can reference `plugin`\nObject.assign(plugin.configs, {\n recommended: {\n plugins: ['@tanstack/eslint-plugin-query'],\n rules: {\n '@tanstack/query/exhaustive-deps': 'error',\n '@tanstack/query/no-rest-destructuring': 'warn',\n '@tanstack/query/stable-query-client': 'error',\n '@tanstack/query/no-unstable-deps': 'error',\n '@tanstack/query/infinite-query-property-order': 'error',\n },\n },\n 'flat/recommended': [\n {\n plugins: {\n '@tanstack/query': plugin,\n },\n rules: {\n '@tanstack/query/exhaustive-deps': 'error',\n '@tanstack/query/no-rest-destructuring': 'warn',\n '@tanstack/query/stable-query-client': 'error',\n '@tanstack/query/no-unstable-deps': 'error',\n '@tanstack/query/infinite-query-property-order': 'error',\n },\n },\n ],\n})\n\nexport default plugin\n"],"names":["rules"],"mappings":";;AAcA,MAAM,SAAiB;AAAA,EACrB,MAAM;AAAA,IACJ,MAAM;AAAA,EACR;AAAA,EACA,SAAS,CAAC;AAAA,EAAA,OACVA,MAAA;AACF;AAGA,OAAO,OAAO,OAAO,SAAS;AAAA,EAC5B,aAAa;AAAA,IACX,SAAS,CAAC,+BAA+B;AAAA,IACzC,OAAO;AAAA,MACL,mCAAmC;AAAA,MACnC,yCAAyC;AAAA,MACzC,uCAAuC;AAAA,MACvC,oCAAoC;AAAA,MACpC,iDAAiD;AAAA,IACnD;AAAA,EACF;AAAA,EACA,oBAAoB;AAAA,IAClB;AAAA,MACE,SAAS;AAAA,QACP,mBAAmB;AAAA,MACrB;AAAA,MACA,OAAO;AAAA,QACL,mCAAmC;AAAA,QACnC,yCAAyC;AAAA,QACzC,uCAAuC;AAAA,QACvC,oCAAoC;AAAA,QACpC,iDAAiD;AAAA,MACnD;AAAA,IACF;AAAA,EACF;AACF,CAAC;;"}
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const infiniteQueryFunctions = [
4
+ "infiniteQueryOptions",
5
+ "useInfiniteQuery",
6
+ "useSuspenseInfiniteQuery"
7
+ ];
8
+ const checkedProperties = [
9
+ "queryFn",
10
+ "getPreviousPageParam",
11
+ "getNextPageParam"
12
+ ];
13
+ exports.checkedProperties = checkedProperties;
14
+ exports.infiniteQueryFunctions = infiniteQueryFunctions;
15
+ //# sourceMappingURL=constants.cjs.map
@@ -0,0 +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;AAIO,MAAM,oBAAoB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AACF;;;"}
@@ -0,0 +1,3 @@
1
+ export declare const infiniteQueryFunctions: readonly ["infiniteQueryOptions", "useInfiniteQuery", "useSuspenseInfiniteQuery"];
2
+ export type InfiniteQueryFunctions = (typeof infiniteQueryFunctions)[number];
3
+ export declare const checkedProperties: readonly ["queryFn", "getPreviousPageParam", "getNextPageParam"];
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const utils = require("@typescript-eslint/utils");
4
+ const getDocsUrl = require("../../utils/get-docs-url.cjs");
5
+ const detectReactQueryImports = require("../../utils/detect-react-query-imports.cjs");
6
+ const infiniteQueryPropertyOrder_utils = require("./infinite-query-property-order.utils.cjs");
7
+ const constants = require("./constants.cjs");
8
+ const createRule = utils.ESLintUtils.RuleCreator(getDocsUrl.getDocsUrl);
9
+ const infiniteQueryFunctionsSet = new Set(constants.infiniteQueryFunctions);
10
+ function isInfiniteQueryFunction(node) {
11
+ return infiniteQueryFunctionsSet.has(node);
12
+ }
13
+ const name = "infinite-query-property-order";
14
+ const rule = createRule({
15
+ name,
16
+ meta: {
17
+ type: "problem",
18
+ docs: {
19
+ description: "Ensure correct order of inference sensitive properties for infinite queries",
20
+ recommended: "error"
21
+ },
22
+ messages: {
23
+ invalidOrder: "Invalid order of properties for `{{function}}`."
24
+ },
25
+ schema: [],
26
+ hasSuggestions: true,
27
+ fixable: "code"
28
+ },
29
+ defaultOptions: [],
30
+ create: detectReactQueryImports.detectTanstackQueryImports((context) => {
31
+ return {
32
+ CallExpression(node) {
33
+ if (node.callee.type !== utils.AST_NODE_TYPES.Identifier) {
34
+ return;
35
+ }
36
+ const infiniteQueryFunction = node.callee.name;
37
+ if (!isInfiniteQueryFunction(infiniteQueryFunction)) {
38
+ return;
39
+ }
40
+ const argument = node.arguments[0];
41
+ if (argument === void 0 || argument.type !== "ObjectExpression") {
42
+ return;
43
+ }
44
+ const allProperties = argument.properties;
45
+ if (allProperties.length < 2) {
46
+ return;
47
+ }
48
+ const properties = allProperties.flatMap((p) => {
49
+ if (p.type === utils.AST_NODE_TYPES.Property && p.key.type === utils.AST_NODE_TYPES.Identifier) {
50
+ return { name: p.key.name, property: p };
51
+ } else if (p.type === utils.AST_NODE_TYPES.SpreadElement) {
52
+ if (p.argument.type === utils.AST_NODE_TYPES.Identifier) {
53
+ return { name: p.argument.name, property: p };
54
+ } else {
55
+ throw new Error("Unsupported spread element");
56
+ }
57
+ }
58
+ return [];
59
+ });
60
+ const sortedProperties = infiniteQueryPropertyOrder_utils.sortDataByOrder(
61
+ properties,
62
+ constants.checkedProperties,
63
+ "name"
64
+ );
65
+ if (sortedProperties === null) {
66
+ return;
67
+ }
68
+ context.report({
69
+ node: argument,
70
+ data: { function: node.callee.name },
71
+ messageId: "invalidOrder",
72
+ fix(fixer) {
73
+ const sourceCode = context.sourceCode;
74
+ const text = sortedProperties.reduce(
75
+ (sourceText, specifier, index) => {
76
+ let text2 = "";
77
+ if (index < allProperties.length - 1) {
78
+ text2 = sourceCode.getText().slice(
79
+ allProperties[index].range[1],
80
+ allProperties[index + 1].range[0]
81
+ );
82
+ }
83
+ return sourceText + sourceCode.getText(specifier.property) + text2;
84
+ },
85
+ ""
86
+ );
87
+ return fixer.replaceTextRange(
88
+ [allProperties[0].range[0], allProperties.at(-1).range[1]],
89
+ text
90
+ );
91
+ }
92
+ });
93
+ }
94
+ };
95
+ })
96
+ });
97
+ exports.name = name;
98
+ exports.rule = rule;
99
+ //# sourceMappingURL=infinite-query-property-order.rule.cjs.map
@@ -0,0 +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 { checkedProperties, infiniteQueryFunctions } 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 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(\n properties,\n checkedProperties,\n 'name',\n )\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","checkedProperties","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;AAC3B,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,mBAAmBC,iCAAA;AAAA,UACvB;AAAA,UACAC,UAAA;AAAA,UACA;AAAA,QAAA;AAEF,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;;;"}
@@ -0,0 +1,4 @@
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
2
+ import { ExtraRuleDocs } from '../../types';
3
+ export declare const name = "infinite-query-property-order";
4
+ export declare const rule: ESLintUtils.RuleModule<string, readonly unknown[], ExtraRuleDocs, ESLintUtils.RuleListener>;
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ function sortDataByOrder(data, orderArray, key) {
4
+ const orderMap = new Map(orderArray.map((item, index) => [item, index]));
5
+ const inOrderArray = data.filter((item) => orderMap.has(item[key])).sort((a, b) => {
6
+ const indexA = orderMap.get(a[key]);
7
+ const indexB = orderMap.get(b[key]);
8
+ return indexA - indexB;
9
+ });
10
+ const inOrderIterator = inOrderArray.values();
11
+ let wasResorted = false;
12
+ const result = data.map((item) => {
13
+ if (orderMap.has(item[key])) {
14
+ const sortedItem = inOrderIterator.next().value;
15
+ if (sortedItem[key] !== item[key]) {
16
+ wasResorted = true;
17
+ }
18
+ return sortedItem;
19
+ }
20
+ return item;
21
+ });
22
+ if (!wasResorted) {
23
+ return null;
24
+ }
25
+ return result;
26
+ }
27
+ exports.sortDataByOrder = sortDataByOrder;
28
+ //# sourceMappingURL=infinite-query-property-order.utils.cjs.map
@@ -0,0 +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 orderArray: Array<T[TKey]> | ReadonlyArray<T[TKey]>,\n key: TKey,\n): Array<T> | null {\n const orderMap = new Map(orderArray.map((item, index) => [item, index]))\n\n // Separate items that are in orderArray from those that are not\n const inOrderArray = data\n .filter((item) => orderMap.has(item[key]))\n .sort((a, b) => {\n const indexA = orderMap.get(a[key])!\n const indexB = orderMap.get(b[key])!\n\n return indexA - indexB\n })\n\n const inOrderIterator = inOrderArray.values()\n\n // `as boolean` is needed to avoid TS incorrectly inferring that wasResorted is always `true`\n let wasResorted = false as boolean\n\n const result = data.map((item) => {\n if (orderMap.has(item[key])) {\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,YACA,KACiB;AACjB,QAAM,WAAW,IAAI,IAAI,WAAW,IAAI,CAAC,MAAM,UAAU,CAAC,MAAM,KAAK,CAAC,CAAC;AAGvE,QAAM,eAAe,KAClB,OAAO,CAAC,SAAS,SAAS,IAAI,KAAK,GAAG,CAAC,CAAC,EACxC,KAAK,CAAC,GAAG,MAAM;AACd,UAAM,SAAS,SAAS,IAAI,EAAE,GAAG,CAAC;AAClC,UAAM,SAAS,SAAS,IAAI,EAAE,GAAG,CAAC;AAElC,WAAO,SAAS;AAAA,EAAA,CACjB;AAEG,QAAA,kBAAkB,aAAa;AAGrC,MAAI,cAAc;AAElB,QAAM,SAAS,KAAK,IAAI,CAAC,SAAS;AAChC,QAAI,SAAS,IAAI,KAAK,GAAG,CAAC,GAAG;AACrB,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;;"}
@@ -0,0 +1 @@
1
+ export declare function sortDataByOrder<T, TKey extends keyof T>(data: Array<T> | ReadonlyArray<T>, orderArray: Array<T[TKey]> | ReadonlyArray<T[TKey]>, key: TKey): Array<T> | null;
@@ -20,7 +20,7 @@ const rule = createRule({
20
20
  meta: {
21
21
  type: "problem",
22
22
  docs: {
23
- description: "Disallow putting the result of useMutation directly in a React hook dependency array",
23
+ description: "Disallow putting the result of query hooks directly in a React hook dependency array",
24
24
  recommended: "error"
25
25
  },
26
26
  messages: {
@@ -1 +1 @@
1
- {"version":3,"file":"no-unstable-deps.rule.cjs","sources":["../../../../src/rules/no-unstable-deps/no-unstable-deps.rule.ts"],"sourcesContent":["import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils'\nimport { getDocsUrl } from '../../utils/get-docs-url'\nimport { detectTanstackQueryImports } from '../../utils/detect-react-query-imports'\nimport type { TSESTree } from '@typescript-eslint/utils'\nimport type { ExtraRuleDocs } from '../../types'\n\nexport const name = 'no-unstable-deps'\n\nexport const reactHookNames = ['useEffect', 'useCallback', 'useMemo']\nexport const useQueryHookNames = [\n 'useQuery',\n 'useSuspenseQuery',\n 'useQueries',\n 'useSuspenseQueries',\n 'useInfiniteQuery',\n 'useSuspenseInfiniteQuery',\n]\nconst allHookNames = ['useMutation', ...useQueryHookNames]\nconst createRule = ESLintUtils.RuleCreator<ExtraRuleDocs>(getDocsUrl)\n\nexport const rule = createRule({\n name,\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Disallow putting the result of useMutation directly in a React hook dependency array',\n recommended: 'error',\n },\n messages: {\n noUnstableDeps: `The result of {{queryHook}} is not referentially stable, so don't pass it directly into the dependencies array of {{reactHook}}. Instead, destructure the return value of {{queryHook}} and pass the destructured values into the dependency array of {{reactHook}}.`,\n },\n schema: [],\n },\n defaultOptions: [],\n\n create: detectTanstackQueryImports((context) => {\n const trackedVariables: Record<string, string> = {}\n const hookAliasMap: Record<string, string> = {}\n\n function getReactHook(node: TSESTree.CallExpression): string | undefined {\n if (node.callee.type === 'Identifier') {\n const calleeName = node.callee.name\n // Check if the identifier is a known React hook or an alias\n if (reactHookNames.includes(calleeName) || calleeName in hookAliasMap) {\n return calleeName\n }\n } else if (\n node.callee.type === 'MemberExpression' &&\n node.callee.object.type === 'Identifier' &&\n node.callee.object.name === 'React' &&\n node.callee.property.type === 'Identifier' &&\n reactHookNames.includes(node.callee.property.name)\n ) {\n // Member expression case: `React.useCallback`\n return node.callee.property.name\n }\n return undefined\n }\n\n function collectVariableNames(\n pattern: TSESTree.BindingName,\n queryHook: string,\n ) {\n if (pattern.type === AST_NODE_TYPES.Identifier) {\n trackedVariables[pattern.name] = queryHook\n }\n }\n\n return {\n ImportDeclaration(node: TSESTree.ImportDeclaration) {\n if (\n node.specifiers.length > 0 &&\n node.importKind === 'value' &&\n node.source.value === 'React'\n ) {\n node.specifiers.forEach((specifier) => {\n if (\n specifier.type === AST_NODE_TYPES.ImportSpecifier &&\n reactHookNames.includes(specifier.imported.name)\n ) {\n // Track alias or direct import\n hookAliasMap[specifier.local.name] = specifier.imported.name\n }\n })\n }\n },\n\n VariableDeclarator(node) {\n if (\n node.init !== null &&\n node.init.type === AST_NODE_TYPES.CallExpression &&\n node.init.callee.type === AST_NODE_TYPES.Identifier &&\n allHookNames.includes(node.init.callee.name)\n ) {\n collectVariableNames(node.id, node.init.callee.name)\n }\n },\n CallExpression: (node) => {\n const reactHook = getReactHook(node)\n if (\n reactHook !== undefined &&\n node.arguments.length > 1 &&\n node.arguments[1]?.type === AST_NODE_TYPES.ArrayExpression\n ) {\n const depsArray = node.arguments[1].elements\n depsArray.forEach((dep) => {\n if (\n dep !== null &&\n dep.type === AST_NODE_TYPES.Identifier &&\n trackedVariables[dep.name] !== undefined\n ) {\n const queryHook = trackedVariables[dep.name]\n context.report({\n node: dep,\n messageId: 'noUnstableDeps',\n data: {\n queryHook,\n reactHook,\n },\n })\n }\n })\n }\n },\n }\n }),\n})\n"],"names":["ESLintUtils","getDocsUrl","detectTanstackQueryImports","AST_NODE_TYPES"],"mappings":"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"}
1
+ {"version":3,"file":"no-unstable-deps.rule.cjs","sources":["../../../../src/rules/no-unstable-deps/no-unstable-deps.rule.ts"],"sourcesContent":["import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils'\nimport { getDocsUrl } from '../../utils/get-docs-url'\nimport { detectTanstackQueryImports } from '../../utils/detect-react-query-imports'\nimport type { TSESTree } from '@typescript-eslint/utils'\nimport type { ExtraRuleDocs } from '../../types'\n\nexport const name = 'no-unstable-deps'\n\nexport const reactHookNames = ['useEffect', 'useCallback', 'useMemo']\nexport const useQueryHookNames = [\n 'useQuery',\n 'useSuspenseQuery',\n 'useQueries',\n 'useSuspenseQueries',\n 'useInfiniteQuery',\n 'useSuspenseInfiniteQuery',\n]\nconst allHookNames = ['useMutation', ...useQueryHookNames]\nconst createRule = ESLintUtils.RuleCreator<ExtraRuleDocs>(getDocsUrl)\n\nexport const rule = createRule({\n name,\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Disallow putting the result of query hooks directly in a React hook dependency array',\n recommended: 'error',\n },\n messages: {\n noUnstableDeps: `The result of {{queryHook}} is not referentially stable, so don't pass it directly into the dependencies array of {{reactHook}}. Instead, destructure the return value of {{queryHook}} and pass the destructured values into the dependency array of {{reactHook}}.`,\n },\n schema: [],\n },\n defaultOptions: [],\n\n create: detectTanstackQueryImports((context) => {\n const trackedVariables: Record<string, string> = {}\n const hookAliasMap: Record<string, string> = {}\n\n function getReactHook(node: TSESTree.CallExpression): string | undefined {\n if (node.callee.type === 'Identifier') {\n const calleeName = node.callee.name\n // Check if the identifier is a known React hook or an alias\n if (reactHookNames.includes(calleeName) || calleeName in hookAliasMap) {\n return calleeName\n }\n } else if (\n node.callee.type === 'MemberExpression' &&\n node.callee.object.type === 'Identifier' &&\n node.callee.object.name === 'React' &&\n node.callee.property.type === 'Identifier' &&\n reactHookNames.includes(node.callee.property.name)\n ) {\n // Member expression case: `React.useCallback`\n return node.callee.property.name\n }\n return undefined\n }\n\n function collectVariableNames(\n pattern: TSESTree.BindingName,\n queryHook: string,\n ) {\n if (pattern.type === AST_NODE_TYPES.Identifier) {\n trackedVariables[pattern.name] = queryHook\n }\n }\n\n return {\n ImportDeclaration(node: TSESTree.ImportDeclaration) {\n if (\n node.specifiers.length > 0 &&\n node.importKind === 'value' &&\n node.source.value === 'React'\n ) {\n node.specifiers.forEach((specifier) => {\n if (\n specifier.type === AST_NODE_TYPES.ImportSpecifier &&\n reactHookNames.includes(specifier.imported.name)\n ) {\n // Track alias or direct import\n hookAliasMap[specifier.local.name] = specifier.imported.name\n }\n })\n }\n },\n\n VariableDeclarator(node) {\n if (\n node.init !== null &&\n node.init.type === AST_NODE_TYPES.CallExpression &&\n node.init.callee.type === AST_NODE_TYPES.Identifier &&\n allHookNames.includes(node.init.callee.name)\n ) {\n collectVariableNames(node.id, node.init.callee.name)\n }\n },\n CallExpression: (node) => {\n const reactHook = getReactHook(node)\n if (\n reactHook !== undefined &&\n node.arguments.length > 1 &&\n node.arguments[1]?.type === AST_NODE_TYPES.ArrayExpression\n ) {\n const depsArray = node.arguments[1].elements\n depsArray.forEach((dep) => {\n if (\n dep !== null &&\n dep.type === AST_NODE_TYPES.Identifier &&\n trackedVariables[dep.name] !== undefined\n ) {\n const queryHook = trackedVariables[dep.name]\n context.report({\n node: dep,\n messageId: 'noUnstableDeps',\n data: {\n queryHook,\n reactHook,\n },\n })\n }\n })\n }\n },\n }\n }),\n})\n"],"names":["ESLintUtils","getDocsUrl","detectTanstackQueryImports","AST_NODE_TYPES"],"mappings":"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"}
@@ -4,11 +4,13 @@ const exhaustiveDeps_rule = require("./rules/exhaustive-deps/exhaustive-deps.rul
4
4
  const stableQueryClient_rule = require("./rules/stable-query-client/stable-query-client.rule.cjs");
5
5
  const noRestDestructuring_rule = require("./rules/no-rest-destructuring/no-rest-destructuring.rule.cjs");
6
6
  const noUnstableDeps_rule = require("./rules/no-unstable-deps/no-unstable-deps.rule.cjs");
7
+ const infiniteQueryPropertyOrder_rule = require("./rules/infinite-query-property-order/infinite-query-property-order.rule.cjs");
7
8
  const rules = {
8
9
  [exhaustiveDeps_rule.name]: exhaustiveDeps_rule.rule,
9
10
  [stableQueryClient_rule.name]: stableQueryClient_rule.rule,
10
11
  [noRestDestructuring_rule.name]: noRestDestructuring_rule.rule,
11
- [noUnstableDeps_rule.name]: noUnstableDeps_rule.rule
12
+ [noUnstableDeps_rule.name]: noUnstableDeps_rule.rule,
13
+ [infiniteQueryPropertyOrder_rule.name]: infiniteQueryPropertyOrder_rule.rule
12
14
  };
13
15
  exports.rules = rules;
14
16
  //# sourceMappingURL=rules.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"rules.cjs","sources":["../../src/rules.ts"],"sourcesContent":["import * as exhaustiveDeps from './rules/exhaustive-deps/exhaustive-deps.rule'\nimport * as stableQueryClient from './rules/stable-query-client/stable-query-client.rule'\nimport * as noRestDestructuring from './rules/no-rest-destructuring/no-rest-destructuring.rule'\nimport * as noUnstableDeps from './rules/no-unstable-deps/no-unstable-deps.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 [exhaustiveDeps.name]: exhaustiveDeps.rule,\n [stableQueryClient.name]: stableQueryClient.rule,\n [noRestDestructuring.name]: noRestDestructuring.rule,\n [noUnstableDeps.name]: noUnstableDeps.rule,\n}\n"],"names":["exhaustiveDeps.name","exhaustiveDeps.rule","stableQueryClient.name","stableQueryClient.rule","noRestDestructuring.name","noRestDestructuring.rule","noUnstableDeps.name","noUnstableDeps.rule"],"mappings":";;;;;;AAOO,MAAM,QAQT;AAAA,EACF,CAACA,oBAAmB,IAAA,GAAGC,oBAAe;AAAA,EACtC,CAACC,uBAAsB,IAAA,GAAGC,uBAAkB;AAAA,EAC5C,CAACC,yBAAwB,IAAA,GAAGC,yBAAoB;AAAA,EAChD,CAACC,oBAAmB,IAAA,GAAGC,oBAAe;AACxC;;"}
1
+ {"version":3,"file":"rules.cjs","sources":["../../src/rules.ts"],"sourcesContent":["import * as exhaustiveDeps from './rules/exhaustive-deps/exhaustive-deps.rule'\nimport * as stableQueryClient from './rules/stable-query-client/stable-query-client.rule'\nimport * as noRestDestructuring from './rules/no-rest-destructuring/no-rest-destructuring.rule'\nimport * as noUnstableDeps from './rules/no-unstable-deps/no-unstable-deps.rule'\nimport * as infiniteQueryPropertyOrder from './rules/infinite-query-property-order/infinite-query-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 [exhaustiveDeps.name]: exhaustiveDeps.rule,\n [stableQueryClient.name]: stableQueryClient.rule,\n [noRestDestructuring.name]: noRestDestructuring.rule,\n [noUnstableDeps.name]: noUnstableDeps.rule,\n [infiniteQueryPropertyOrder.name]: infiniteQueryPropertyOrder.rule,\n}\n"],"names":["exhaustiveDeps.name","exhaustiveDeps.rule","stableQueryClient.name","stableQueryClient.rule","noRestDestructuring.name","noRestDestructuring.rule","noUnstableDeps.name","noUnstableDeps.rule","infiniteQueryPropertyOrder.name","infiniteQueryPropertyOrder.rule"],"mappings":";;;;;;;AAQO,MAAM,QAQT;AAAA,EACF,CAACA,oBAAmB,IAAA,GAAGC,oBAAe;AAAA,EACtC,CAACC,uBAAsB,IAAA,GAAGC,uBAAkB;AAAA,EAC5C,CAACC,yBAAwB,IAAA,GAAGC,yBAAoB;AAAA,EAChD,CAACC,oBAAmB,IAAA,GAAGC,oBAAe;AAAA,EACtC,CAACC,gCAA+B,IAAA,GAAGC,gCAA2B;AAChE;;"}
package/dist/esm/index.js CHANGED
@@ -13,7 +13,8 @@ Object.assign(plugin.configs, {
13
13
  "@tanstack/query/exhaustive-deps": "error",
14
14
  "@tanstack/query/no-rest-destructuring": "warn",
15
15
  "@tanstack/query/stable-query-client": "error",
16
- "@tanstack/query/no-unstable-deps": "error"
16
+ "@tanstack/query/no-unstable-deps": "error",
17
+ "@tanstack/query/infinite-query-property-order": "error"
17
18
  }
18
19
  },
19
20
  "flat/recommended": [
@@ -25,7 +26,8 @@ Object.assign(plugin.configs, {
25
26
  "@tanstack/query/exhaustive-deps": "error",
26
27
  "@tanstack/query/no-rest-destructuring": "warn",
27
28
  "@tanstack/query/stable-query-client": "error",
28
- "@tanstack/query/no-unstable-deps": "error"
29
+ "@tanstack/query/no-unstable-deps": "error",
30
+ "@tanstack/query/infinite-query-property-order": "error"
29
31
  }
30
32
  }
31
33
  ]
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../src/index.ts"],"sourcesContent":["import { rules } from './rules'\nimport type { ESLint, Linter } from 'eslint'\nimport type { RuleModule } from '@typescript-eslint/utils/ts-eslint'\n\ntype RuleKey = keyof typeof rules\n\ninterface Plugin extends Omit<ESLint.Plugin, 'rules'> {\n rules: Record<RuleKey, RuleModule<any, any, any>>\n configs: {\n recommended: ESLint.ConfigData\n 'flat/recommended': Array<Linter.FlatConfig>\n }\n}\n\nconst plugin: Plugin = {\n meta: {\n name: '@tanstack/eslint-plugin-query',\n },\n configs: {} as Plugin['configs'],\n rules,\n}\n\n// Assign configs here so we can reference `plugin`\nObject.assign(plugin.configs, {\n recommended: {\n plugins: ['@tanstack/eslint-plugin-query'],\n rules: {\n '@tanstack/query/exhaustive-deps': 'error',\n '@tanstack/query/no-rest-destructuring': 'warn',\n '@tanstack/query/stable-query-client': 'error',\n '@tanstack/query/no-unstable-deps': 'error',\n },\n },\n 'flat/recommended': [\n {\n plugins: {\n '@tanstack/query': plugin,\n },\n rules: {\n '@tanstack/query/exhaustive-deps': 'error',\n '@tanstack/query/no-rest-destructuring': 'warn',\n '@tanstack/query/stable-query-client': 'error',\n '@tanstack/query/no-unstable-deps': 'error',\n },\n },\n ],\n})\n\nexport default plugin\n"],"names":[],"mappings":";AAcA,MAAM,SAAiB;AAAA,EACrB,MAAM;AAAA,IACJ,MAAM;AAAA,EACR;AAAA,EACA,SAAS,CAAC;AAAA,EACV;AACF;AAGA,OAAO,OAAO,OAAO,SAAS;AAAA,EAC5B,aAAa;AAAA,IACX,SAAS,CAAC,+BAA+B;AAAA,IACzC,OAAO;AAAA,MACL,mCAAmC;AAAA,MACnC,yCAAyC;AAAA,MACzC,uCAAuC;AAAA,MACvC,oCAAoC;AAAA,IACtC;AAAA,EACF;AAAA,EACA,oBAAoB;AAAA,IAClB;AAAA,MACE,SAAS;AAAA,QACP,mBAAmB;AAAA,MACrB;AAAA,MACA,OAAO;AAAA,QACL,mCAAmC;AAAA,QACnC,yCAAyC;AAAA,QACzC,uCAAuC;AAAA,QACvC,oCAAoC;AAAA,MACtC;AAAA,IACF;AAAA,EACF;AACF,CAAC;"}
1
+ {"version":3,"file":"index.js","sources":["../../src/index.ts"],"sourcesContent":["import { rules } from './rules'\nimport type { ESLint, Linter } from 'eslint'\nimport type { RuleModule } from '@typescript-eslint/utils/ts-eslint'\n\ntype RuleKey = keyof typeof rules\n\ninterface Plugin extends Omit<ESLint.Plugin, 'rules'> {\n rules: Record<RuleKey, RuleModule<any, any, any>>\n configs: {\n recommended: ESLint.ConfigData\n 'flat/recommended': Array<Linter.FlatConfig>\n }\n}\n\nconst plugin: Plugin = {\n meta: {\n name: '@tanstack/eslint-plugin-query',\n },\n configs: {} as Plugin['configs'],\n rules,\n}\n\n// Assign configs here so we can reference `plugin`\nObject.assign(plugin.configs, {\n recommended: {\n plugins: ['@tanstack/eslint-plugin-query'],\n rules: {\n '@tanstack/query/exhaustive-deps': 'error',\n '@tanstack/query/no-rest-destructuring': 'warn',\n '@tanstack/query/stable-query-client': 'error',\n '@tanstack/query/no-unstable-deps': 'error',\n '@tanstack/query/infinite-query-property-order': 'error',\n },\n },\n 'flat/recommended': [\n {\n plugins: {\n '@tanstack/query': plugin,\n },\n rules: {\n '@tanstack/query/exhaustive-deps': 'error',\n '@tanstack/query/no-rest-destructuring': 'warn',\n '@tanstack/query/stable-query-client': 'error',\n '@tanstack/query/no-unstable-deps': 'error',\n '@tanstack/query/infinite-query-property-order': 'error',\n },\n },\n ],\n})\n\nexport default plugin\n"],"names":[],"mappings":";AAcA,MAAM,SAAiB;AAAA,EACrB,MAAM;AAAA,IACJ,MAAM;AAAA,EACR;AAAA,EACA,SAAS,CAAC;AAAA,EACV;AACF;AAGA,OAAO,OAAO,OAAO,SAAS;AAAA,EAC5B,aAAa;AAAA,IACX,SAAS,CAAC,+BAA+B;AAAA,IACzC,OAAO;AAAA,MACL,mCAAmC;AAAA,MACnC,yCAAyC;AAAA,MACzC,uCAAuC;AAAA,MACvC,oCAAoC;AAAA,MACpC,iDAAiD;AAAA,IACnD;AAAA,EACF;AAAA,EACA,oBAAoB;AAAA,IAClB;AAAA,MACE,SAAS;AAAA,QACP,mBAAmB;AAAA,MACrB;AAAA,MACA,OAAO;AAAA,QACL,mCAAmC;AAAA,QACnC,yCAAyC;AAAA,QACzC,uCAAuC;AAAA,QACvC,oCAAoC;AAAA,QACpC,iDAAiD;AAAA,MACnD;AAAA,IACF;AAAA,EACF;AACF,CAAC;"}
@@ -0,0 +1,3 @@
1
+ export declare const infiniteQueryFunctions: readonly ["infiniteQueryOptions", "useInfiniteQuery", "useSuspenseInfiniteQuery"];
2
+ export type InfiniteQueryFunctions = (typeof infiniteQueryFunctions)[number];
3
+ export declare const checkedProperties: readonly ["queryFn", "getPreviousPageParam", "getNextPageParam"];
@@ -0,0 +1,15 @@
1
+ const infiniteQueryFunctions = [
2
+ "infiniteQueryOptions",
3
+ "useInfiniteQuery",
4
+ "useSuspenseInfiniteQuery"
5
+ ];
6
+ const checkedProperties = [
7
+ "queryFn",
8
+ "getPreviousPageParam",
9
+ "getNextPageParam"
10
+ ];
11
+ export {
12
+ checkedProperties,
13
+ infiniteQueryFunctions
14
+ };
15
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +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;AAIO,MAAM,oBAAoB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AACF;"}
@@ -0,0 +1,4 @@
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
2
+ import { ExtraRuleDocs } from '../../types';
3
+ export declare const name = "infinite-query-property-order";
4
+ export declare const rule: ESLintUtils.RuleModule<string, readonly unknown[], ExtraRuleDocs, ESLintUtils.RuleListener>;
@@ -0,0 +1,99 @@
1
+ import { ESLintUtils, AST_NODE_TYPES } from "@typescript-eslint/utils";
2
+ import { getDocsUrl } from "../../utils/get-docs-url.js";
3
+ import { detectTanstackQueryImports } from "../../utils/detect-react-query-imports.js";
4
+ import { sortDataByOrder } from "./infinite-query-property-order.utils.js";
5
+ import { infiniteQueryFunctions, checkedProperties } from "./constants.js";
6
+ const createRule = ESLintUtils.RuleCreator(getDocsUrl);
7
+ const infiniteQueryFunctionsSet = new Set(infiniteQueryFunctions);
8
+ function isInfiniteQueryFunction(node) {
9
+ return infiniteQueryFunctionsSet.has(node);
10
+ }
11
+ const name = "infinite-query-property-order";
12
+ const rule = createRule({
13
+ name,
14
+ meta: {
15
+ type: "problem",
16
+ docs: {
17
+ description: "Ensure correct order of inference sensitive properties for infinite queries",
18
+ recommended: "error"
19
+ },
20
+ messages: {
21
+ invalidOrder: "Invalid order of properties for `{{function}}`."
22
+ },
23
+ schema: [],
24
+ hasSuggestions: true,
25
+ fixable: "code"
26
+ },
27
+ defaultOptions: [],
28
+ create: detectTanstackQueryImports((context) => {
29
+ return {
30
+ CallExpression(node) {
31
+ if (node.callee.type !== AST_NODE_TYPES.Identifier) {
32
+ return;
33
+ }
34
+ const infiniteQueryFunction = node.callee.name;
35
+ if (!isInfiniteQueryFunction(infiniteQueryFunction)) {
36
+ return;
37
+ }
38
+ const argument = node.arguments[0];
39
+ if (argument === void 0 || argument.type !== "ObjectExpression") {
40
+ return;
41
+ }
42
+ const allProperties = argument.properties;
43
+ if (allProperties.length < 2) {
44
+ return;
45
+ }
46
+ const properties = allProperties.flatMap((p) => {
47
+ if (p.type === AST_NODE_TYPES.Property && p.key.type === AST_NODE_TYPES.Identifier) {
48
+ return { name: p.key.name, property: p };
49
+ } else if (p.type === AST_NODE_TYPES.SpreadElement) {
50
+ if (p.argument.type === AST_NODE_TYPES.Identifier) {
51
+ return { name: p.argument.name, property: p };
52
+ } else {
53
+ throw new Error("Unsupported spread element");
54
+ }
55
+ }
56
+ return [];
57
+ });
58
+ const sortedProperties = sortDataByOrder(
59
+ properties,
60
+ checkedProperties,
61
+ "name"
62
+ );
63
+ if (sortedProperties === null) {
64
+ return;
65
+ }
66
+ context.report({
67
+ node: argument,
68
+ data: { function: node.callee.name },
69
+ messageId: "invalidOrder",
70
+ fix(fixer) {
71
+ const sourceCode = context.sourceCode;
72
+ const text = sortedProperties.reduce(
73
+ (sourceText, specifier, index) => {
74
+ let text2 = "";
75
+ if (index < allProperties.length - 1) {
76
+ text2 = sourceCode.getText().slice(
77
+ allProperties[index].range[1],
78
+ allProperties[index + 1].range[0]
79
+ );
80
+ }
81
+ return sourceText + sourceCode.getText(specifier.property) + text2;
82
+ },
83
+ ""
84
+ );
85
+ return fixer.replaceTextRange(
86
+ [allProperties[0].range[0], allProperties.at(-1).range[1]],
87
+ text
88
+ );
89
+ }
90
+ });
91
+ }
92
+ };
93
+ })
94
+ });
95
+ export {
96
+ name,
97
+ rule
98
+ };
99
+ //# sourceMappingURL=infinite-query-property-order.rule.js.map
@@ -0,0 +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 { checkedProperties, infiniteQueryFunctions } 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 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(\n properties,\n checkedProperties,\n 'name',\n )\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;AAC3B,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;AAAA,UACvB;AAAA,UACA;AAAA,UACA;AAAA,QAAA;AAEF,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;"}
@@ -0,0 +1 @@
1
+ export declare function sortDataByOrder<T, TKey extends keyof T>(data: Array<T> | ReadonlyArray<T>, orderArray: Array<T[TKey]> | ReadonlyArray<T[TKey]>, key: TKey): Array<T> | null;
@@ -0,0 +1,28 @@
1
+ function sortDataByOrder(data, orderArray, key) {
2
+ const orderMap = new Map(orderArray.map((item, index) => [item, index]));
3
+ const inOrderArray = data.filter((item) => orderMap.has(item[key])).sort((a, b) => {
4
+ const indexA = orderMap.get(a[key]);
5
+ const indexB = orderMap.get(b[key]);
6
+ return indexA - indexB;
7
+ });
8
+ const inOrderIterator = inOrderArray.values();
9
+ let wasResorted = false;
10
+ const result = data.map((item) => {
11
+ if (orderMap.has(item[key])) {
12
+ const sortedItem = inOrderIterator.next().value;
13
+ if (sortedItem[key] !== item[key]) {
14
+ wasResorted = true;
15
+ }
16
+ return sortedItem;
17
+ }
18
+ return item;
19
+ });
20
+ if (!wasResorted) {
21
+ return null;
22
+ }
23
+ return result;
24
+ }
25
+ export {
26
+ sortDataByOrder
27
+ };
28
+ //# sourceMappingURL=infinite-query-property-order.utils.js.map
@@ -0,0 +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 orderArray: Array<T[TKey]> | ReadonlyArray<T[TKey]>,\n key: TKey,\n): Array<T> | null {\n const orderMap = new Map(orderArray.map((item, index) => [item, index]))\n\n // Separate items that are in orderArray from those that are not\n const inOrderArray = data\n .filter((item) => orderMap.has(item[key]))\n .sort((a, b) => {\n const indexA = orderMap.get(a[key])!\n const indexB = orderMap.get(b[key])!\n\n return indexA - indexB\n })\n\n const inOrderIterator = inOrderArray.values()\n\n // `as boolean` is needed to avoid TS incorrectly inferring that wasResorted is always `true`\n let wasResorted = false as boolean\n\n const result = data.map((item) => {\n if (orderMap.has(item[key])) {\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,YACA,KACiB;AACjB,QAAM,WAAW,IAAI,IAAI,WAAW,IAAI,CAAC,MAAM,UAAU,CAAC,MAAM,KAAK,CAAC,CAAC;AAGvE,QAAM,eAAe,KAClB,OAAO,CAAC,SAAS,SAAS,IAAI,KAAK,GAAG,CAAC,CAAC,EACxC,KAAK,CAAC,GAAG,MAAM;AACd,UAAM,SAAS,SAAS,IAAI,EAAE,GAAG,CAAC;AAClC,UAAM,SAAS,SAAS,IAAI,EAAE,GAAG,CAAC;AAElC,WAAO,SAAS;AAAA,EAAA,CACjB;AAEG,QAAA,kBAAkB,aAAa;AAGrC,MAAI,cAAc;AAElB,QAAM,SAAS,KAAK,IAAI,CAAC,SAAS;AAChC,QAAI,SAAS,IAAI,KAAK,GAAG,CAAC,GAAG;AACrB,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;"}
@@ -18,7 +18,7 @@ const rule = createRule({
18
18
  meta: {
19
19
  type: "problem",
20
20
  docs: {
21
- description: "Disallow putting the result of useMutation directly in a React hook dependency array",
21
+ description: "Disallow putting the result of query hooks directly in a React hook dependency array",
22
22
  recommended: "error"
23
23
  },
24
24
  messages: {
@@ -1 +1 @@
1
- {"version":3,"file":"no-unstable-deps.rule.js","sources":["../../../../src/rules/no-unstable-deps/no-unstable-deps.rule.ts"],"sourcesContent":["import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils'\nimport { getDocsUrl } from '../../utils/get-docs-url'\nimport { detectTanstackQueryImports } from '../../utils/detect-react-query-imports'\nimport type { TSESTree } from '@typescript-eslint/utils'\nimport type { ExtraRuleDocs } from '../../types'\n\nexport const name = 'no-unstable-deps'\n\nexport const reactHookNames = ['useEffect', 'useCallback', 'useMemo']\nexport const useQueryHookNames = [\n 'useQuery',\n 'useSuspenseQuery',\n 'useQueries',\n 'useSuspenseQueries',\n 'useInfiniteQuery',\n 'useSuspenseInfiniteQuery',\n]\nconst allHookNames = ['useMutation', ...useQueryHookNames]\nconst createRule = ESLintUtils.RuleCreator<ExtraRuleDocs>(getDocsUrl)\n\nexport const rule = createRule({\n name,\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Disallow putting the result of useMutation directly in a React hook dependency array',\n recommended: 'error',\n },\n messages: {\n noUnstableDeps: `The result of {{queryHook}} is not referentially stable, so don't pass it directly into the dependencies array of {{reactHook}}. Instead, destructure the return value of {{queryHook}} and pass the destructured values into the dependency array of {{reactHook}}.`,\n },\n schema: [],\n },\n defaultOptions: [],\n\n create: detectTanstackQueryImports((context) => {\n const trackedVariables: Record<string, string> = {}\n const hookAliasMap: Record<string, string> = {}\n\n function getReactHook(node: TSESTree.CallExpression): string | undefined {\n if (node.callee.type === 'Identifier') {\n const calleeName = node.callee.name\n // Check if the identifier is a known React hook or an alias\n if (reactHookNames.includes(calleeName) || calleeName in hookAliasMap) {\n return calleeName\n }\n } else if (\n node.callee.type === 'MemberExpression' &&\n node.callee.object.type === 'Identifier' &&\n node.callee.object.name === 'React' &&\n node.callee.property.type === 'Identifier' &&\n reactHookNames.includes(node.callee.property.name)\n ) {\n // Member expression case: `React.useCallback`\n return node.callee.property.name\n }\n return undefined\n }\n\n function collectVariableNames(\n pattern: TSESTree.BindingName,\n queryHook: string,\n ) {\n if (pattern.type === AST_NODE_TYPES.Identifier) {\n trackedVariables[pattern.name] = queryHook\n }\n }\n\n return {\n ImportDeclaration(node: TSESTree.ImportDeclaration) {\n if (\n node.specifiers.length > 0 &&\n node.importKind === 'value' &&\n node.source.value === 'React'\n ) {\n node.specifiers.forEach((specifier) => {\n if (\n specifier.type === AST_NODE_TYPES.ImportSpecifier &&\n reactHookNames.includes(specifier.imported.name)\n ) {\n // Track alias or direct import\n hookAliasMap[specifier.local.name] = specifier.imported.name\n }\n })\n }\n },\n\n VariableDeclarator(node) {\n if (\n node.init !== null &&\n node.init.type === AST_NODE_TYPES.CallExpression &&\n node.init.callee.type === AST_NODE_TYPES.Identifier &&\n allHookNames.includes(node.init.callee.name)\n ) {\n collectVariableNames(node.id, node.init.callee.name)\n }\n },\n CallExpression: (node) => {\n const reactHook = getReactHook(node)\n if (\n reactHook !== undefined &&\n node.arguments.length > 1 &&\n node.arguments[1]?.type === AST_NODE_TYPES.ArrayExpression\n ) {\n const depsArray = node.arguments[1].elements\n depsArray.forEach((dep) => {\n if (\n dep !== null &&\n dep.type === AST_NODE_TYPES.Identifier &&\n trackedVariables[dep.name] !== undefined\n ) {\n const queryHook = trackedVariables[dep.name]\n context.report({\n node: dep,\n messageId: 'noUnstableDeps',\n data: {\n queryHook,\n reactHook,\n },\n })\n }\n })\n }\n },\n }\n }),\n})\n"],"names":[],"mappings":";;;AAMO,MAAM,OAAO;AAEb,MAAM,iBAAiB,CAAC,aAAa,eAAe,SAAS;AAC7D,MAAM,oBAAoB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AACA,MAAM,eAAe,CAAC,eAAe,GAAG,iBAAiB;AACzD,MAAM,aAAa,YAAY,YAA2B,UAAU;AAE7D,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,gBAAgB;AAAA,IAClB;AAAA,IACA,QAAQ,CAAC;AAAA,EACX;AAAA,EACA,gBAAgB,CAAC;AAAA,EAEjB,QAAQ,2BAA2B,CAAC,YAAY;AAC9C,UAAM,mBAA2C,CAAA;AACjD,UAAM,eAAuC,CAAA;AAE7C,aAAS,aAAa,MAAmD;AACnE,UAAA,KAAK,OAAO,SAAS,cAAc;AAC/B,cAAA,aAAa,KAAK,OAAO;AAE/B,YAAI,eAAe,SAAS,UAAU,KAAK,cAAc,cAAc;AAC9D,iBAAA;AAAA,QACT;AAAA,MACF,WACE,KAAK,OAAO,SAAS,sBACrB,KAAK,OAAO,OAAO,SAAS,gBAC5B,KAAK,OAAO,OAAO,SAAS,WAC5B,KAAK,OAAO,SAAS,SAAS,gBAC9B,eAAe,SAAS,KAAK,OAAO,SAAS,IAAI,GACjD;AAEO,eAAA,KAAK,OAAO,SAAS;AAAA,MAC9B;AACO,aAAA;AAAA,IACT;AAES,aAAA,qBACP,SACA,WACA;AACI,UAAA,QAAQ,SAAS,eAAe,YAAY;AAC7B,yBAAA,QAAQ,IAAI,IAAI;AAAA,MACnC;AAAA,IACF;AAEO,WAAA;AAAA,MACL,kBAAkB,MAAkC;AAEhD,YAAA,KAAK,WAAW,SAAS,KACzB,KAAK,eAAe,WACpB,KAAK,OAAO,UAAU,SACtB;AACK,eAAA,WAAW,QAAQ,CAAC,cAAc;AAEnC,gBAAA,UAAU,SAAS,eAAe,mBAClC,eAAe,SAAS,UAAU,SAAS,IAAI,GAC/C;AAEA,2BAAa,UAAU,MAAM,IAAI,IAAI,UAAU,SAAS;AAAA,YAC1D;AAAA,UAAA,CACD;AAAA,QACH;AAAA,MACF;AAAA,MAEA,mBAAmB,MAAM;AAErB,YAAA,KAAK,SAAS,QACd,KAAK,KAAK,SAAS,eAAe,kBAClC,KAAK,KAAK,OAAO,SAAS,eAAe,cACzC,aAAa,SAAS,KAAK,KAAK,OAAO,IAAI,GAC3C;AACA,+BAAqB,KAAK,IAAI,KAAK,KAAK,OAAO,IAAI;AAAA,QACrD;AAAA,MACF;AAAA,MACA,gBAAgB,CAAC,SAAS;;AAClB,cAAA,YAAY,aAAa,IAAI;AACnC,YACE,cAAc,UACd,KAAK,UAAU,SAAS,OACxB,UAAK,UAAU,CAAC,MAAhB,mBAAmB,UAAS,eAAe,iBAC3C;AACA,gBAAM,YAAY,KAAK,UAAU,CAAC,EAAE;AAC1B,oBAAA,QAAQ,CAAC,QAAQ;AAEvB,gBAAA,QAAQ,QACR,IAAI,SAAS,eAAe,cAC5B,iBAAiB,IAAI,IAAI,MAAM,QAC/B;AACM,oBAAA,YAAY,iBAAiB,IAAI,IAAI;AAC3C,sBAAQ,OAAO;AAAA,gBACb,MAAM;AAAA,gBACN,WAAW;AAAA,gBACX,MAAM;AAAA,kBACJ;AAAA,kBACA;AAAA,gBACF;AAAA,cAAA,CACD;AAAA,YACH;AAAA,UAAA,CACD;AAAA,QACH;AAAA,MACF;AAAA,IAAA;AAAA,EACF,CACD;AACH,CAAC;"}
1
+ {"version":3,"file":"no-unstable-deps.rule.js","sources":["../../../../src/rules/no-unstable-deps/no-unstable-deps.rule.ts"],"sourcesContent":["import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils'\nimport { getDocsUrl } from '../../utils/get-docs-url'\nimport { detectTanstackQueryImports } from '../../utils/detect-react-query-imports'\nimport type { TSESTree } from '@typescript-eslint/utils'\nimport type { ExtraRuleDocs } from '../../types'\n\nexport const name = 'no-unstable-deps'\n\nexport const reactHookNames = ['useEffect', 'useCallback', 'useMemo']\nexport const useQueryHookNames = [\n 'useQuery',\n 'useSuspenseQuery',\n 'useQueries',\n 'useSuspenseQueries',\n 'useInfiniteQuery',\n 'useSuspenseInfiniteQuery',\n]\nconst allHookNames = ['useMutation', ...useQueryHookNames]\nconst createRule = ESLintUtils.RuleCreator<ExtraRuleDocs>(getDocsUrl)\n\nexport const rule = createRule({\n name,\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Disallow putting the result of query hooks directly in a React hook dependency array',\n recommended: 'error',\n },\n messages: {\n noUnstableDeps: `The result of {{queryHook}} is not referentially stable, so don't pass it directly into the dependencies array of {{reactHook}}. Instead, destructure the return value of {{queryHook}} and pass the destructured values into the dependency array of {{reactHook}}.`,\n },\n schema: [],\n },\n defaultOptions: [],\n\n create: detectTanstackQueryImports((context) => {\n const trackedVariables: Record<string, string> = {}\n const hookAliasMap: Record<string, string> = {}\n\n function getReactHook(node: TSESTree.CallExpression): string | undefined {\n if (node.callee.type === 'Identifier') {\n const calleeName = node.callee.name\n // Check if the identifier is a known React hook or an alias\n if (reactHookNames.includes(calleeName) || calleeName in hookAliasMap) {\n return calleeName\n }\n } else if (\n node.callee.type === 'MemberExpression' &&\n node.callee.object.type === 'Identifier' &&\n node.callee.object.name === 'React' &&\n node.callee.property.type === 'Identifier' &&\n reactHookNames.includes(node.callee.property.name)\n ) {\n // Member expression case: `React.useCallback`\n return node.callee.property.name\n }\n return undefined\n }\n\n function collectVariableNames(\n pattern: TSESTree.BindingName,\n queryHook: string,\n ) {\n if (pattern.type === AST_NODE_TYPES.Identifier) {\n trackedVariables[pattern.name] = queryHook\n }\n }\n\n return {\n ImportDeclaration(node: TSESTree.ImportDeclaration) {\n if (\n node.specifiers.length > 0 &&\n node.importKind === 'value' &&\n node.source.value === 'React'\n ) {\n node.specifiers.forEach((specifier) => {\n if (\n specifier.type === AST_NODE_TYPES.ImportSpecifier &&\n reactHookNames.includes(specifier.imported.name)\n ) {\n // Track alias or direct import\n hookAliasMap[specifier.local.name] = specifier.imported.name\n }\n })\n }\n },\n\n VariableDeclarator(node) {\n if (\n node.init !== null &&\n node.init.type === AST_NODE_TYPES.CallExpression &&\n node.init.callee.type === AST_NODE_TYPES.Identifier &&\n allHookNames.includes(node.init.callee.name)\n ) {\n collectVariableNames(node.id, node.init.callee.name)\n }\n },\n CallExpression: (node) => {\n const reactHook = getReactHook(node)\n if (\n reactHook !== undefined &&\n node.arguments.length > 1 &&\n node.arguments[1]?.type === AST_NODE_TYPES.ArrayExpression\n ) {\n const depsArray = node.arguments[1].elements\n depsArray.forEach((dep) => {\n if (\n dep !== null &&\n dep.type === AST_NODE_TYPES.Identifier &&\n trackedVariables[dep.name] !== undefined\n ) {\n const queryHook = trackedVariables[dep.name]\n context.report({\n node: dep,\n messageId: 'noUnstableDeps',\n data: {\n queryHook,\n reactHook,\n },\n })\n }\n })\n }\n },\n }\n }),\n})\n"],"names":[],"mappings":";;;AAMO,MAAM,OAAO;AAEb,MAAM,iBAAiB,CAAC,aAAa,eAAe,SAAS;AAC7D,MAAM,oBAAoB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AACA,MAAM,eAAe,CAAC,eAAe,GAAG,iBAAiB;AACzD,MAAM,aAAa,YAAY,YAA2B,UAAU;AAE7D,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,gBAAgB;AAAA,IAClB;AAAA,IACA,QAAQ,CAAC;AAAA,EACX;AAAA,EACA,gBAAgB,CAAC;AAAA,EAEjB,QAAQ,2BAA2B,CAAC,YAAY;AAC9C,UAAM,mBAA2C,CAAA;AACjD,UAAM,eAAuC,CAAA;AAE7C,aAAS,aAAa,MAAmD;AACnE,UAAA,KAAK,OAAO,SAAS,cAAc;AAC/B,cAAA,aAAa,KAAK,OAAO;AAE/B,YAAI,eAAe,SAAS,UAAU,KAAK,cAAc,cAAc;AAC9D,iBAAA;AAAA,QACT;AAAA,MACF,WACE,KAAK,OAAO,SAAS,sBACrB,KAAK,OAAO,OAAO,SAAS,gBAC5B,KAAK,OAAO,OAAO,SAAS,WAC5B,KAAK,OAAO,SAAS,SAAS,gBAC9B,eAAe,SAAS,KAAK,OAAO,SAAS,IAAI,GACjD;AAEO,eAAA,KAAK,OAAO,SAAS;AAAA,MAC9B;AACO,aAAA;AAAA,IACT;AAES,aAAA,qBACP,SACA,WACA;AACI,UAAA,QAAQ,SAAS,eAAe,YAAY;AAC7B,yBAAA,QAAQ,IAAI,IAAI;AAAA,MACnC;AAAA,IACF;AAEO,WAAA;AAAA,MACL,kBAAkB,MAAkC;AAEhD,YAAA,KAAK,WAAW,SAAS,KACzB,KAAK,eAAe,WACpB,KAAK,OAAO,UAAU,SACtB;AACK,eAAA,WAAW,QAAQ,CAAC,cAAc;AAEnC,gBAAA,UAAU,SAAS,eAAe,mBAClC,eAAe,SAAS,UAAU,SAAS,IAAI,GAC/C;AAEA,2BAAa,UAAU,MAAM,IAAI,IAAI,UAAU,SAAS;AAAA,YAC1D;AAAA,UAAA,CACD;AAAA,QACH;AAAA,MACF;AAAA,MAEA,mBAAmB,MAAM;AAErB,YAAA,KAAK,SAAS,QACd,KAAK,KAAK,SAAS,eAAe,kBAClC,KAAK,KAAK,OAAO,SAAS,eAAe,cACzC,aAAa,SAAS,KAAK,KAAK,OAAO,IAAI,GAC3C;AACA,+BAAqB,KAAK,IAAI,KAAK,KAAK,OAAO,IAAI;AAAA,QACrD;AAAA,MACF;AAAA,MACA,gBAAgB,CAAC,SAAS;;AAClB,cAAA,YAAY,aAAa,IAAI;AACnC,YACE,cAAc,UACd,KAAK,UAAU,SAAS,OACxB,UAAK,UAAU,CAAC,MAAhB,mBAAmB,UAAS,eAAe,iBAC3C;AACA,gBAAM,YAAY,KAAK,UAAU,CAAC,EAAE;AAC1B,oBAAA,QAAQ,CAAC,QAAQ;AAEvB,gBAAA,QAAQ,QACR,IAAI,SAAS,eAAe,cAC5B,iBAAiB,IAAI,IAAI,MAAM,QAC/B;AACM,oBAAA,YAAY,iBAAiB,IAAI,IAAI;AAC3C,sBAAQ,OAAO;AAAA,gBACb,MAAM;AAAA,gBACN,WAAW;AAAA,gBACX,MAAM;AAAA,kBACJ;AAAA,kBACA;AAAA,gBACF;AAAA,cAAA,CACD;AAAA,YACH;AAAA,UAAA,CACD;AAAA,QACH;AAAA,MACF;AAAA,IAAA;AAAA,EACF,CACD;AACH,CAAC;"}
package/dist/esm/rules.js CHANGED
@@ -2,11 +2,13 @@ import { name, rule } from "./rules/exhaustive-deps/exhaustive-deps.rule.js";
2
2
  import { name as name$1, rule as rule$1 } from "./rules/stable-query-client/stable-query-client.rule.js";
3
3
  import { name as name$2, rule as rule$2 } from "./rules/no-rest-destructuring/no-rest-destructuring.rule.js";
4
4
  import { name as name$3, rule as rule$3 } from "./rules/no-unstable-deps/no-unstable-deps.rule.js";
5
+ import { name as name$4, rule as rule$4 } from "./rules/infinite-query-property-order/infinite-query-property-order.rule.js";
5
6
  const rules = {
6
7
  [name]: rule,
7
8
  [name$1]: rule$1,
8
9
  [name$2]: rule$2,
9
- [name$3]: rule$3
10
+ [name$3]: rule$3,
11
+ [name$4]: rule$4
10
12
  };
11
13
  export {
12
14
  rules
@@ -1 +1 @@
1
- {"version":3,"file":"rules.js","sources":["../../src/rules.ts"],"sourcesContent":["import * as exhaustiveDeps from './rules/exhaustive-deps/exhaustive-deps.rule'\nimport * as stableQueryClient from './rules/stable-query-client/stable-query-client.rule'\nimport * as noRestDestructuring from './rules/no-rest-destructuring/no-rest-destructuring.rule'\nimport * as noUnstableDeps from './rules/no-unstable-deps/no-unstable-deps.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 [exhaustiveDeps.name]: exhaustiveDeps.rule,\n [stableQueryClient.name]: stableQueryClient.rule,\n [noRestDestructuring.name]: noRestDestructuring.rule,\n [noUnstableDeps.name]: noUnstableDeps.rule,\n}\n"],"names":["exhaustiveDeps.name","exhaustiveDeps.rule","stableQueryClient.name","stableQueryClient.rule","noRestDestructuring.name","noRestDestructuring.rule","noUnstableDeps.name","noUnstableDeps.rule"],"mappings":";;;;AAOO,MAAM,QAQT;AAAA,EACF,CAACA,IAAmB,GAAGC;AAAAA,EACvB,CAACC,MAAsB,GAAGC;AAAAA,EAC1B,CAACC,MAAwB,GAAGC;AAAAA,EAC5B,CAACC,MAAmB,GAAGC;AACzB;"}
1
+ {"version":3,"file":"rules.js","sources":["../../src/rules.ts"],"sourcesContent":["import * as exhaustiveDeps from './rules/exhaustive-deps/exhaustive-deps.rule'\nimport * as stableQueryClient from './rules/stable-query-client/stable-query-client.rule'\nimport * as noRestDestructuring from './rules/no-rest-destructuring/no-rest-destructuring.rule'\nimport * as noUnstableDeps from './rules/no-unstable-deps/no-unstable-deps.rule'\nimport * as infiniteQueryPropertyOrder from './rules/infinite-query-property-order/infinite-query-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 [exhaustiveDeps.name]: exhaustiveDeps.rule,\n [stableQueryClient.name]: stableQueryClient.rule,\n [noRestDestructuring.name]: noRestDestructuring.rule,\n [noUnstableDeps.name]: noUnstableDeps.rule,\n [infiniteQueryPropertyOrder.name]: infiniteQueryPropertyOrder.rule,\n}\n"],"names":["exhaustiveDeps.name","exhaustiveDeps.rule","stableQueryClient.name","stableQueryClient.rule","noRestDestructuring.name","noRestDestructuring.rule","noUnstableDeps.name","noUnstableDeps.rule","infiniteQueryPropertyOrder.name","infiniteQueryPropertyOrder.rule"],"mappings":";;;;;AAQO,MAAM,QAQT;AAAA,EACF,CAACA,IAAmB,GAAGC;AAAAA,EACvB,CAACC,MAAsB,GAAGC;AAAAA,EAC1B,CAACC,MAAwB,GAAGC;AAAAA,EAC5B,CAACC,MAAmB,GAAGC;AAAAA,EACvB,CAACC,MAA+B,GAAGC;AACrC;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/eslint-plugin-query",
3
- "version": "5.56.1",
3
+ "version": "5.57.0",
4
4
  "description": "ESLint plugin for TanStack Query",
5
5
  "author": "Eliya Cohen",
6
6
  "license": "MIT",
@@ -41,6 +41,7 @@
41
41
  },
42
42
  "devDependencies": {
43
43
  "@typescript-eslint/rule-tester": "^8.3.0",
44
+ "combinate": "^1.1.11",
44
45
  "eslint": "^9.9.1"
45
46
  },
46
47
  "peerDependencies": {
@@ -0,0 +1,140 @@
1
+ import { RuleTester } from '@typescript-eslint/rule-tester'
2
+ import combinate from 'combinate'
3
+
4
+ import {
5
+ checkedProperties,
6
+ infiniteQueryFunctions,
7
+ } from '../rules/infinite-query-property-order/constants'
8
+ import {
9
+ name,
10
+ rule,
11
+ } from '../rules/infinite-query-property-order/infinite-query-property-order.rule'
12
+ import {
13
+ generateInterleavedCombinations,
14
+ generatePartialCombinations,
15
+ generatePermutations,
16
+ } from './test-utils'
17
+ import type { InfiniteQueryFunctions } from '../rules/infinite-query-property-order/constants'
18
+
19
+ const ruleTester = new RuleTester()
20
+
21
+ type CheckedProperties = (typeof checkedProperties)[number]
22
+ const orderIndependentProps = ['queryKey', '...foo'] as const
23
+ type OrderIndependentProps = (typeof orderIndependentProps)[number]
24
+
25
+ interface TestCase {
26
+ infiniteQueryFunction: InfiniteQueryFunctions
27
+ properties: Array<CheckedProperties | OrderIndependentProps>
28
+ }
29
+
30
+ const validTestMatrix = combinate({
31
+ infiniteQueryFunction: [...infiniteQueryFunctions],
32
+ properties: generatePartialCombinations(checkedProperties, 2),
33
+ })
34
+
35
+ export function generateInvalidPermutations<T>(
36
+ arr: ReadonlyArray<T>,
37
+ ): Array<{ invalid: Array<T>; valid: Array<T> }> {
38
+ const combinations = generatePartialCombinations(arr, 2)
39
+ const allPermutations: Array<{ invalid: Array<T>; valid: Array<T> }> = []
40
+
41
+ for (const combination of combinations) {
42
+ const permutations = generatePermutations(combination)
43
+ // skip the first permutation as it matches the original combination
44
+ const invalidPermutations = permutations.slice(1)
45
+ allPermutations.push(
46
+ ...invalidPermutations.map((p) => ({ invalid: p, valid: combination })),
47
+ )
48
+ }
49
+
50
+ return allPermutations
51
+ }
52
+
53
+ const invalidPermutations = generateInvalidPermutations(checkedProperties)
54
+
55
+ type Interleaved = CheckedProperties | OrderIndependentProps
56
+ const interleavedInvalidPermutations: Array<{
57
+ invalid: Array<Interleaved>
58
+ valid: Array<Interleaved>
59
+ }> = []
60
+ for (const invalidPermutation of invalidPermutations) {
61
+ const invalid = generateInterleavedCombinations(
62
+ invalidPermutation.invalid,
63
+ orderIndependentProps,
64
+ )
65
+ const valid = generateInterleavedCombinations(
66
+ invalidPermutation.valid,
67
+ orderIndependentProps,
68
+ )
69
+
70
+ for (let i = 0; i < invalid.length; i++) {
71
+ interleavedInvalidPermutations.push({
72
+ invalid: invalid[i]!,
73
+ valid: valid[i]!,
74
+ })
75
+ }
76
+ }
77
+
78
+ const invalidTestMatrix = combinate({
79
+ infiniteQueryFunction: [...infiniteQueryFunctions],
80
+ properties: interleavedInvalidPermutations,
81
+ })
82
+
83
+ function getCode({
84
+ infiniteQueryFunction: infiniteQueryFunction,
85
+ properties,
86
+ }: TestCase) {
87
+ function getPropertyCode(
88
+ property: CheckedProperties | OrderIndependentProps,
89
+ ) {
90
+ if (property.startsWith('...')) {
91
+ return property
92
+ }
93
+ switch (property) {
94
+ case 'queryKey':
95
+ return `queryKey: ['projects']`
96
+ case 'queryFn':
97
+ return 'queryFn: async ({ pageParam }) => { \n await fetch(`/api/projects?cursor=${pageParam}`) \n return await response.json() \n }'
98
+ case 'getPreviousPageParam':
99
+ return 'getPreviousPageParam: (firstPage) => firstPage.previousId ?? undefined'
100
+ case 'getNextPageParam':
101
+ return 'getNextPageParam: (lastPage) => lastPage.nextId ?? undefined'
102
+ }
103
+
104
+ return `${property}: () => null`
105
+ }
106
+ return `
107
+ import { ${infiniteQueryFunction} } from '@tanstack/react-query'
108
+
109
+ ${infiniteQueryFunction}({
110
+ ${properties.map(getPropertyCode).join(',\n ')}
111
+ })
112
+ `
113
+ }
114
+
115
+ const validTestCases = validTestMatrix.map(
116
+ ({ infiniteQueryFunction, properties }) => ({
117
+ name: `should pass when order is correct for ${infiniteQueryFunction} with order: ${properties.join(', ')}`,
118
+ code: getCode({ infiniteQueryFunction, properties }),
119
+ }),
120
+ )
121
+
122
+ const invalidTestCases = invalidTestMatrix.map(
123
+ ({ infiniteQueryFunction, properties }) => ({
124
+ name: `incorrect property order is detected for ${infiniteQueryFunction} with order: ${properties.invalid.join(', ')}`,
125
+ code: getCode({
126
+ infiniteQueryFunction: infiniteQueryFunction,
127
+ properties: properties.invalid,
128
+ }),
129
+ errors: [{ messageId: 'invalidOrder' }],
130
+ output: getCode({
131
+ infiniteQueryFunction: infiniteQueryFunction,
132
+ properties: properties.valid,
133
+ }),
134
+ }),
135
+ )
136
+
137
+ ruleTester.run(name, rule, {
138
+ valid: validTestCases,
139
+ invalid: invalidTestCases,
140
+ })
@@ -0,0 +1,58 @@
1
+ import { describe, expect, test } from 'vitest'
2
+ import { sortDataByOrder } from '../rules/infinite-query-property-order/infinite-query-property-order.utils'
3
+
4
+ describe('create-route-property-order utils', () => {
5
+ describe('sortDataByOrder', () => {
6
+ const testCases = [
7
+ {
8
+ data: [{ key: 'a' }, { key: 'c' }, { key: 'b' }],
9
+ orderArray: ['a', 'b', 'c'],
10
+ key: 'key',
11
+ expected: [{ key: 'a' }, { key: 'b' }, { key: 'c' }],
12
+ },
13
+ {
14
+ data: [{ key: 'b' }, { key: 'a' }, { key: 'c' }],
15
+ orderArray: ['a', 'b', 'c'],
16
+ key: 'key',
17
+ expected: [{ key: 'a' }, { key: 'b' }, { key: 'c' }],
18
+ },
19
+ {
20
+ data: [{ key: 'a' }, { key: 'b' }, { key: 'c' }],
21
+ orderArray: ['a', 'b', 'c'],
22
+ key: 'key',
23
+ expected: null,
24
+ },
25
+ {
26
+ data: [{ key: 'a' }, { key: 'b' }, { key: 'c' }, { key: 'd' }],
27
+ orderArray: ['a', 'b', 'c'],
28
+ key: 'key',
29
+ expected: null,
30
+ },
31
+ {
32
+ data: [{ key: 'a' }, { key: 'b' }, { key: 'd' }, { key: 'c' }],
33
+ orderArray: ['a', 'b', 'c'],
34
+ key: 'key',
35
+ expected: null,
36
+ },
37
+ {
38
+ data: [{ key: 'd' }, { key: 'a' }, { key: 'b' }, { key: 'c' }],
39
+ orderArray: ['a', 'b', 'c'],
40
+ key: 'key',
41
+ expected: null,
42
+ },
43
+ {
44
+ data: [{ key: 'd' }, { key: 'b' }, { key: 'a' }, { key: 'c' }],
45
+ orderArray: ['a', 'b', 'c'],
46
+ key: 'key',
47
+ expected: [{ key: 'd' }, { key: 'a' }, { key: 'b' }, { key: 'c' }],
48
+ },
49
+ ] as const
50
+ test.each(testCases)(
51
+ '$data $orderArray $key $expected',
52
+ ({ data, orderArray, key, expected }) => {
53
+ const sortedData = sortDataByOrder(data, orderArray, key)
54
+ expect(sortedData).toEqual(expected)
55
+ },
56
+ )
57
+ })
58
+ })
@@ -0,0 +1,104 @@
1
+ import { describe, expect, test } from 'vitest'
2
+ import {
3
+ expectArrayEqualIgnoreOrder,
4
+ generateInterleavedCombinations,
5
+ generatePartialCombinations,
6
+ generatePermutations,
7
+ } from './test-utils'
8
+
9
+ describe('test-utils', () => {
10
+ describe('generatePermutations', () => {
11
+ const testCases = [
12
+ {
13
+ input: ['a', 'b', 'c'],
14
+ expected: [
15
+ ['a', 'b', 'c'],
16
+ ['a', 'c', 'b'],
17
+ ['b', 'a', 'c'],
18
+ ['b', 'c', 'a'],
19
+ ['c', 'a', 'b'],
20
+ ['c', 'b', 'a'],
21
+ ],
22
+ },
23
+ {
24
+ input: ['a', 'b'],
25
+ expected: [
26
+ ['a', 'b'],
27
+ ['b', 'a'],
28
+ ],
29
+ },
30
+ {
31
+ input: ['a'],
32
+ expected: [['a']],
33
+ },
34
+ ]
35
+ test.each(testCases)('$input $expected', ({ input, expected }) => {
36
+ const permutations = generatePermutations(input)
37
+ expect(permutations).toEqual(expected)
38
+ })
39
+ })
40
+
41
+ describe('generatePartialCombinations', () => {
42
+ const testCases = [
43
+ {
44
+ input: ['a', 'b', 'c'],
45
+ minLength: 2,
46
+ expected: [
47
+ ['a', 'b'],
48
+ ['a', 'c'],
49
+ ['b', 'c'],
50
+ ['a', 'b', 'c'],
51
+ ],
52
+ },
53
+ {
54
+ input: ['a', 'b'],
55
+ expected: [['a', 'b']],
56
+ minLength: 2,
57
+ },
58
+ {
59
+ input: ['a'],
60
+ expected: [],
61
+ minLength: 2,
62
+ },
63
+ {
64
+ input: ['a'],
65
+ expected: [['a']],
66
+ minLength: 1,
67
+ },
68
+ {
69
+ input: ['a'],
70
+ expected: [[], ['a']],
71
+ minLength: 0,
72
+ },
73
+ ]
74
+ test.each(testCases)(
75
+ '$input $minLength $expected',
76
+ ({ input, minLength, expected }) => {
77
+ const combinations = generatePartialCombinations(input, minLength)
78
+ expectArrayEqualIgnoreOrder(combinations, expected)
79
+ },
80
+ )
81
+ })
82
+
83
+ describe('generateInterleavedCombinations', () => {
84
+ const testCases = [
85
+ {
86
+ data: ['a', 'b'],
87
+ additional: ['x'],
88
+ expected: [
89
+ ['a', 'b'],
90
+ ['x', 'a', 'b'],
91
+ ['a', 'x', 'b'],
92
+ ['a', 'b', 'x'],
93
+ ],
94
+ },
95
+ ]
96
+ test.each(testCases)(
97
+ '$input $expected',
98
+ ({ data, additional, expected }) => {
99
+ const combinations = generateInterleavedCombinations(data, additional)
100
+ expectArrayEqualIgnoreOrder(combinations, expected)
101
+ },
102
+ )
103
+ })
104
+ })
@@ -1,5 +1,108 @@
1
+ import { expect } from 'vitest'
2
+
1
3
  export function normalizeIndent(template: TemplateStringsArray) {
2
4
  const codeLines = template[0]?.split('\n') ?? ['']
3
5
  const leftPadding = codeLines[1]?.match(/\s+/)?.[0] ?? ''
4
6
  return codeLines.map((line) => line.slice(leftPadding.length)).join('\n')
5
7
  }
8
+
9
+ export function generatePermutations<T>(arr: Array<T>): Array<Array<T>> {
10
+ if (arr.length <= 1) {
11
+ return [arr]
12
+ }
13
+
14
+ const result: Array<Array<T>> = []
15
+ for (let i = 0; i < arr.length; i++) {
16
+ const rest = arr.slice(0, i).concat(arr.slice(i + 1))
17
+ const restPermutations = generatePermutations(rest)
18
+ for (const perm of restPermutations) {
19
+ result.push([arr[i]!, ...perm])
20
+ }
21
+ }
22
+
23
+ return result
24
+ }
25
+
26
+ export function generatePartialCombinations<T>(
27
+ arr: ReadonlyArray<T>,
28
+ minLength: number,
29
+ ): Array<Array<T>> {
30
+ const result: Array<Array<T>> = []
31
+
32
+ function backtrack(start: number, current: Array<T>) {
33
+ if (current.length > minLength - 1) {
34
+ result.push([...current])
35
+ }
36
+ for (let i = start; i < arr.length; i++) {
37
+ current.push(arr[i]!)
38
+ backtrack(i + 1, current)
39
+ current.pop()
40
+ }
41
+ }
42
+ backtrack(0, [])
43
+ return result
44
+ }
45
+
46
+ export function expectArrayEqualIgnoreOrder<T>(a: Array<T>, b: Array<T>) {
47
+ expect([...a].sort()).toEqual([...b].sort())
48
+ }
49
+
50
+ export function generateInterleavedCombinations<
51
+ TData,
52
+ TAdditional,
53
+ TResult extends TData | TAdditional,
54
+ >(
55
+ data: Array<TData> | ReadonlyArray<TData>,
56
+ additional: Array<TAdditional> | ReadonlyArray<TAdditional>,
57
+ ): Array<Array<TResult>> {
58
+ const result: Array<Array<TResult>> = []
59
+
60
+ function getSubsets(array: Array<TAdditional>): Array<Array<TAdditional>> {
61
+ return array.reduce(
62
+ (subsets, value) => {
63
+ return subsets.concat(subsets.map((set) => [...set, value]))
64
+ },
65
+ [[]] as Array<Array<TAdditional>>,
66
+ )
67
+ }
68
+
69
+ function insertAtPositions(
70
+ data: Array<TResult>,
71
+ subset: Array<TResult>,
72
+ ): Array<Array<TResult>> {
73
+ const combinations: Array<Array<TResult>> = []
74
+
75
+ const recurse = (
76
+ currentData: Array<TResult>,
77
+ currentSubset: Array<TResult>,
78
+ start: number,
79
+ ): void => {
80
+ if (currentSubset.length === 0) {
81
+ combinations.push([...currentData])
82
+ return
83
+ }
84
+
85
+ for (let i = start; i <= currentData.length; i++) {
86
+ const newData = [
87
+ ...currentData.slice(0, i),
88
+ currentSubset[0]!,
89
+ ...currentData.slice(i),
90
+ ]
91
+ recurse(newData, currentSubset.slice(1), i + 1)
92
+ }
93
+ }
94
+
95
+ recurse(data, subset, 0)
96
+ return combinations
97
+ }
98
+
99
+ const subsets = getSubsets(additional as Array<TAdditional>)
100
+
101
+ subsets.forEach((subset) => {
102
+ result.push(
103
+ ...insertAtPositions(data as Array<TResult>, subset as Array<TResult>),
104
+ )
105
+ })
106
+
107
+ return result
108
+ }
package/src/index.ts CHANGED
@@ -29,6 +29,7 @@ Object.assign(plugin.configs, {
29
29
  '@tanstack/query/no-rest-destructuring': 'warn',
30
30
  '@tanstack/query/stable-query-client': 'error',
31
31
  '@tanstack/query/no-unstable-deps': 'error',
32
+ '@tanstack/query/infinite-query-property-order': 'error',
32
33
  },
33
34
  },
34
35
  'flat/recommended': [
@@ -41,6 +42,7 @@ Object.assign(plugin.configs, {
41
42
  '@tanstack/query/no-rest-destructuring': 'warn',
42
43
  '@tanstack/query/stable-query-client': 'error',
43
44
  '@tanstack/query/no-unstable-deps': 'error',
45
+ '@tanstack/query/infinite-query-property-order': 'error',
44
46
  },
45
47
  },
46
48
  ],
@@ -0,0 +1,13 @@
1
+ export const infiniteQueryFunctions = [
2
+ 'infiniteQueryOptions',
3
+ 'useInfiniteQuery',
4
+ 'useSuspenseInfiniteQuery',
5
+ ] as const
6
+
7
+ export type InfiniteQueryFunctions = (typeof infiniteQueryFunctions)[number]
8
+
9
+ export const checkedProperties = [
10
+ 'queryFn',
11
+ 'getPreviousPageParam',
12
+ 'getNextPageParam',
13
+ ] as const
@@ -0,0 +1,114 @@
1
+ import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils'
2
+
3
+ import { getDocsUrl } from '../../utils/get-docs-url'
4
+ import { detectTanstackQueryImports } from '../../utils/detect-react-query-imports'
5
+ import { sortDataByOrder } from './infinite-query-property-order.utils'
6
+ import { checkedProperties, infiniteQueryFunctions } from './constants'
7
+ import type { InfiniteQueryFunctions } from './constants'
8
+ import type { ExtraRuleDocs } from '../../types'
9
+
10
+ const createRule = ESLintUtils.RuleCreator<ExtraRuleDocs>(getDocsUrl)
11
+
12
+ const infiniteQueryFunctionsSet = new Set(infiniteQueryFunctions)
13
+ function isInfiniteQueryFunction(node: any): node is InfiniteQueryFunctions {
14
+ return infiniteQueryFunctionsSet.has(node)
15
+ }
16
+
17
+ export const name = 'infinite-query-property-order'
18
+
19
+ export const rule = createRule({
20
+ name,
21
+ meta: {
22
+ type: 'problem',
23
+ docs: {
24
+ description:
25
+ 'Ensure correct order of inference sensitive properties for infinite queries',
26
+ recommended: 'error',
27
+ },
28
+ messages: {
29
+ invalidOrder: 'Invalid order of properties for `{{function}}`.',
30
+ },
31
+ schema: [],
32
+ hasSuggestions: true,
33
+ fixable: 'code',
34
+ },
35
+ defaultOptions: [],
36
+
37
+ create: detectTanstackQueryImports((context) => {
38
+ return {
39
+ CallExpression(node) {
40
+ if (node.callee.type !== AST_NODE_TYPES.Identifier) {
41
+ return
42
+ }
43
+ const infiniteQueryFunction = node.callee.name
44
+ if (!isInfiniteQueryFunction(infiniteQueryFunction)) {
45
+ return
46
+ }
47
+ const argument = node.arguments[0]
48
+ if (argument === undefined || argument.type !== 'ObjectExpression') {
49
+ return
50
+ }
51
+
52
+ const allProperties = argument.properties
53
+ if (allProperties.length < 2) {
54
+ return
55
+ }
56
+
57
+ const properties = allProperties.flatMap((p) => {
58
+ if (
59
+ p.type === AST_NODE_TYPES.Property &&
60
+ p.key.type === AST_NODE_TYPES.Identifier
61
+ ) {
62
+ return { name: p.key.name, property: p }
63
+ } else if (p.type === AST_NODE_TYPES.SpreadElement) {
64
+ if (p.argument.type === AST_NODE_TYPES.Identifier) {
65
+ return { name: p.argument.name, property: p }
66
+ } else {
67
+ throw new Error('Unsupported spread element')
68
+ }
69
+ }
70
+ return []
71
+ })
72
+
73
+ const sortedProperties = sortDataByOrder(
74
+ properties,
75
+ checkedProperties,
76
+ 'name',
77
+ )
78
+ if (sortedProperties === null) {
79
+ return
80
+ }
81
+ context.report({
82
+ node: argument,
83
+ data: { function: node.callee.name },
84
+ messageId: 'invalidOrder',
85
+ fix(fixer) {
86
+ const sourceCode = context.sourceCode
87
+
88
+ const text = sortedProperties.reduce(
89
+ (sourceText, specifier, index) => {
90
+ let text = ''
91
+ if (index < allProperties.length - 1) {
92
+ text = sourceCode
93
+ .getText()
94
+ .slice(
95
+ allProperties[index]!.range[1],
96
+ allProperties[index + 1]!.range[0],
97
+ )
98
+ }
99
+ return (
100
+ sourceText + sourceCode.getText(specifier.property) + text
101
+ )
102
+ },
103
+ '',
104
+ )
105
+ return fixer.replaceTextRange(
106
+ [allProperties[0]!.range[0], allProperties.at(-1)!.range[1]],
107
+ text,
108
+ )
109
+ },
110
+ })
111
+ },
112
+ }
113
+ }),
114
+ })
@@ -0,0 +1,38 @@
1
+ export function sortDataByOrder<T, TKey extends keyof T>(
2
+ data: Array<T> | ReadonlyArray<T>,
3
+ orderArray: Array<T[TKey]> | ReadonlyArray<T[TKey]>,
4
+ key: TKey,
5
+ ): Array<T> | null {
6
+ const orderMap = new Map(orderArray.map((item, index) => [item, index]))
7
+
8
+ // Separate items that are in orderArray from those that are not
9
+ const inOrderArray = data
10
+ .filter((item) => orderMap.has(item[key]))
11
+ .sort((a, b) => {
12
+ const indexA = orderMap.get(a[key])!
13
+ const indexB = orderMap.get(b[key])!
14
+
15
+ return indexA - indexB
16
+ })
17
+
18
+ const inOrderIterator = inOrderArray.values()
19
+
20
+ // `as boolean` is needed to avoid TS incorrectly inferring that wasResorted is always `true`
21
+ let wasResorted = false as boolean
22
+
23
+ const result = data.map((item) => {
24
+ if (orderMap.has(item[key])) {
25
+ const sortedItem = inOrderIterator.next().value!
26
+ if (sortedItem[key] !== item[key]) {
27
+ wasResorted = true
28
+ }
29
+ return sortedItem
30
+ }
31
+ return item
32
+ })
33
+
34
+ if (!wasResorted) {
35
+ return null
36
+ }
37
+ return result
38
+ }
@@ -24,7 +24,7 @@ export const rule = createRule({
24
24
  type: 'problem',
25
25
  docs: {
26
26
  description:
27
- 'Disallow putting the result of useMutation directly in a React hook dependency array',
27
+ 'Disallow putting the result of query hooks directly in a React hook dependency array',
28
28
  recommended: 'error',
29
29
  },
30
30
  messages: {
package/src/rules.ts CHANGED
@@ -2,6 +2,7 @@ import * as exhaustiveDeps from './rules/exhaustive-deps/exhaustive-deps.rule'
2
2
  import * as stableQueryClient from './rules/stable-query-client/stable-query-client.rule'
3
3
  import * as noRestDestructuring from './rules/no-rest-destructuring/no-rest-destructuring.rule'
4
4
  import * as noUnstableDeps from './rules/no-unstable-deps/no-unstable-deps.rule'
5
+ import * as infiniteQueryPropertyOrder from './rules/infinite-query-property-order/infinite-query-property-order.rule'
5
6
  import type { ESLintUtils } from '@typescript-eslint/utils'
6
7
  import type { ExtraRuleDocs } from './types'
7
8
 
@@ -18,4 +19,5 @@ export const rules: Record<
18
19
  [stableQueryClient.name]: stableQueryClient.rule,
19
20
  [noRestDestructuring.name]: noRestDestructuring.rule,
20
21
  [noUnstableDeps.name]: noUnstableDeps.rule,
22
+ [infiniteQueryPropertyOrder.name]: infiniteQueryPropertyOrder.rule,
21
23
  }