@tanstack/eslint-plugin-query 5.32.1 → 5.35.6
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/exhaustive-deps/exhaustive-deps.rule.cjs +8 -7
- package/dist/cjs/rules/exhaustive-deps/exhaustive-deps.rule.cjs.map +1 -1
- package/dist/cjs/rules/exhaustive-deps/exhaustive-deps.utils.cjs +4 -1
- package/dist/cjs/rules/exhaustive-deps/exhaustive-deps.utils.cjs.map +1 -1
- package/dist/cjs/rules/exhaustive-deps/exhaustive-deps.utils.d.cts +2 -1
- package/dist/esm/rules/exhaustive-deps/exhaustive-deps.rule.js +8 -7
- package/dist/esm/rules/exhaustive-deps/exhaustive-deps.rule.js.map +1 -1
- package/dist/esm/rules/exhaustive-deps/exhaustive-deps.utils.d.ts +2 -1
- package/dist/esm/rules/exhaustive-deps/exhaustive-deps.utils.js +4 -1
- package/dist/esm/rules/exhaustive-deps/exhaustive-deps.utils.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/exhaustive-deps.test.ts +15 -0
- package/src/rules/exhaustive-deps/exhaustive-deps.rule.ts +9 -7
- package/src/rules/exhaustive-deps/exhaustive-deps.utils.ts +8 -1
|
@@ -33,7 +33,7 @@ const rule = createRule({
|
|
|
33
33
|
if (!astUtils.ASTUtils.isObjectExpression(node.parent) || !astUtils.ASTUtils.isIdentifierWithName(node.key, QUERY_KEY)) {
|
|
34
34
|
return;
|
|
35
35
|
}
|
|
36
|
-
const scopeManager = context.
|
|
36
|
+
const scopeManager = context.sourceCode.scopeManager;
|
|
37
37
|
const queryKey = astUtils.ASTUtils.findPropertyWithIdentifierKey(
|
|
38
38
|
node.parent.properties,
|
|
39
39
|
QUERY_KEY
|
|
@@ -61,11 +61,10 @@ const rule = createRule({
|
|
|
61
61
|
queryKeyNode = expression;
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
|
-
const sourceCode = context.getSourceCode();
|
|
65
64
|
const queryKeyValue = queryKeyNode;
|
|
66
65
|
const externalRefs = astUtils.ASTUtils.getExternalRefs({
|
|
67
66
|
scopeManager,
|
|
68
|
-
sourceCode,
|
|
67
|
+
sourceCode: context.sourceCode,
|
|
69
68
|
node: queryFn.value
|
|
70
69
|
});
|
|
71
70
|
const relevantRefs = externalRefs.filter(
|
|
@@ -76,11 +75,11 @@ const rule = createRule({
|
|
|
76
75
|
})
|
|
77
76
|
);
|
|
78
77
|
const existingKeys = astUtils.ASTUtils.getNestedIdentifiers(queryKeyValue).map(
|
|
79
|
-
(identifier) => astUtils.ASTUtils.mapKeyNodeToText(identifier, sourceCode)
|
|
78
|
+
(identifier) => astUtils.ASTUtils.mapKeyNodeToText(identifier, context.sourceCode)
|
|
80
79
|
);
|
|
81
80
|
const missingRefs = relevantRefs.map((ref) => ({
|
|
82
81
|
ref,
|
|
83
|
-
text: astUtils.ASTUtils.mapKeyNodeToText(ref.identifier, sourceCode)
|
|
82
|
+
text: astUtils.ASTUtils.mapKeyNodeToText(ref.identifier, context.sourceCode)
|
|
84
83
|
})).filter(({ ref, text }) => {
|
|
85
84
|
return !ref.isTypeReference && !astUtils.ASTUtils.isAncestorIsCallee(ref.identifier) && !existingKeys.some((existingKey) => existingKey === text) && !existingKeys.includes(text.split(".")[0] ?? "");
|
|
86
85
|
}).map(({ ref, text }) => ({
|
|
@@ -89,8 +88,10 @@ const rule = createRule({
|
|
|
89
88
|
}));
|
|
90
89
|
const uniqueMissingRefs = uniqueBy.uniqueBy(missingRefs, (x) => x.text);
|
|
91
90
|
if (uniqueMissingRefs.length > 0) {
|
|
92
|
-
const missingAsText = uniqueMissingRefs.map(
|
|
93
|
-
|
|
91
|
+
const missingAsText = uniqueMissingRefs.map(
|
|
92
|
+
(ref) => astUtils.ASTUtils.mapKeyNodeToText(ref.identifier, context.sourceCode)
|
|
93
|
+
).join(", ");
|
|
94
|
+
const existingWithMissing = context.sourceCode.getText(queryKeyValue).replace(/\]$/, `, ${missingAsText}]`);
|
|
94
95
|
const suggestions = [];
|
|
95
96
|
if (queryKeyNode.type === utils.AST_NODE_TYPES.ArrayExpression) {
|
|
96
97
|
suggestions.push({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exhaustive-deps.rule.cjs","sources":["../../../../src/rules/exhaustive-deps/exhaustive-deps.rule.ts"],"sourcesContent":["import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils'\nimport { ASTUtils } from '../../utils/ast-utils'\nimport { getDocsUrl } from '../../utils/get-docs-url'\nimport { uniqueBy } from '../../utils/unique-by'\nimport { detectTanstackQueryImports } from '../../utils/detect-react-query-imports'\nimport { ExhaustiveDepsUtils } from './exhaustive-deps.utils'\nimport type { TSESLint } from '@typescript-eslint/utils'\n\nconst QUERY_KEY = 'queryKey'\nconst QUERY_FN = 'queryFn'\n\nexport const name = 'exhaustive-deps'\n\nconst createRule = ESLintUtils.RuleCreator(getDocsUrl)\n\nexport const rule = createRule({\n name,\n meta: {\n type: 'problem',\n docs: {\n description: 'Exhaustive deps rule for useQuery',\n recommended: 'error' as any,\n },\n messages: {\n missingDeps: `The following dependencies are missing in your queryKey: {{deps}}`,\n fixTo: 'Fix to {{result}}',\n },\n hasSuggestions: true,\n fixable: 'code',\n schema: [],\n },\n defaultOptions: [],\n\n create: detectTanstackQueryImports((context) => {\n return {\n Property: (node) => {\n if (\n !ASTUtils.isObjectExpression(node.parent) ||\n !ASTUtils.isIdentifierWithName(node.key, QUERY_KEY)\n ) {\n return\n }\n\n const scopeManager = context.
|
|
1
|
+
{"version":3,"file":"exhaustive-deps.rule.cjs","sources":["../../../../src/rules/exhaustive-deps/exhaustive-deps.rule.ts"],"sourcesContent":["import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils'\nimport { ASTUtils } from '../../utils/ast-utils'\nimport { getDocsUrl } from '../../utils/get-docs-url'\nimport { uniqueBy } from '../../utils/unique-by'\nimport { detectTanstackQueryImports } from '../../utils/detect-react-query-imports'\nimport { ExhaustiveDepsUtils } from './exhaustive-deps.utils'\nimport type { TSESLint } from '@typescript-eslint/utils'\n\nconst QUERY_KEY = 'queryKey'\nconst QUERY_FN = 'queryFn'\n\nexport const name = 'exhaustive-deps'\n\nconst createRule = ESLintUtils.RuleCreator(getDocsUrl)\n\nexport const rule = createRule({\n name,\n meta: {\n type: 'problem',\n docs: {\n description: 'Exhaustive deps rule for useQuery',\n recommended: 'error' as any,\n },\n messages: {\n missingDeps: `The following dependencies are missing in your queryKey: {{deps}}`,\n fixTo: 'Fix to {{result}}',\n },\n hasSuggestions: true,\n fixable: 'code',\n schema: [],\n },\n defaultOptions: [],\n\n create: detectTanstackQueryImports((context) => {\n return {\n Property: (node) => {\n if (\n !ASTUtils.isObjectExpression(node.parent) ||\n !ASTUtils.isIdentifierWithName(node.key, QUERY_KEY)\n ) {\n return\n }\n\n const scopeManager = context.sourceCode.scopeManager\n const queryKey = ASTUtils.findPropertyWithIdentifierKey(\n node.parent.properties,\n QUERY_KEY,\n )\n const queryFn = ASTUtils.findPropertyWithIdentifierKey(\n node.parent.properties,\n QUERY_FN,\n )\n\n if (\n scopeManager === null ||\n queryKey === undefined ||\n queryFn === undefined ||\n !ASTUtils.isNodeOfOneOf(queryFn.value, [\n AST_NODE_TYPES.ArrowFunctionExpression,\n AST_NODE_TYPES.FunctionExpression,\n ])\n ) {\n return\n }\n\n let queryKeyNode = queryKey.value\n\n if (\n queryKeyNode.type === AST_NODE_TYPES.TSAsExpression &&\n queryKeyNode.expression.type === AST_NODE_TYPES.ArrayExpression\n ) {\n queryKeyNode = queryKeyNode.expression\n }\n\n if (queryKeyNode.type === AST_NODE_TYPES.Identifier) {\n const expression = ASTUtils.getReferencedExpressionByIdentifier({\n context,\n node: queryKeyNode,\n })\n\n if (expression?.type === AST_NODE_TYPES.ArrayExpression) {\n queryKeyNode = expression\n }\n }\n\n const queryKeyValue = queryKeyNode\n const externalRefs = ASTUtils.getExternalRefs({\n scopeManager,\n sourceCode: context.sourceCode,\n node: queryFn.value,\n })\n\n const relevantRefs = externalRefs.filter((reference) =>\n ExhaustiveDepsUtils.isRelevantReference({\n context,\n reference,\n scopeManager,\n }),\n )\n\n const existingKeys = ASTUtils.getNestedIdentifiers(queryKeyValue).map(\n (identifier) =>\n ASTUtils.mapKeyNodeToText(identifier, context.sourceCode),\n )\n\n const missingRefs = relevantRefs\n .map((ref) => ({\n ref: ref,\n text: ASTUtils.mapKeyNodeToText(ref.identifier, context.sourceCode),\n }))\n .filter(({ ref, text }) => {\n return (\n !ref.isTypeReference &&\n !ASTUtils.isAncestorIsCallee(ref.identifier) &&\n !existingKeys.some((existingKey) => existingKey === text) &&\n !existingKeys.includes(text.split('.')[0] ?? '')\n )\n })\n .map(({ ref, text }) => ({\n identifier: ref.identifier,\n text: text,\n }))\n\n const uniqueMissingRefs = uniqueBy(missingRefs, (x) => x.text)\n\n if (uniqueMissingRefs.length > 0) {\n const missingAsText = uniqueMissingRefs\n .map((ref) =>\n ASTUtils.mapKeyNodeToText(ref.identifier, context.sourceCode),\n )\n .join(', ')\n\n const existingWithMissing = context.sourceCode\n .getText(queryKeyValue)\n .replace(/\\]$/, `, ${missingAsText}]`)\n\n const suggestions: TSESLint.ReportSuggestionArray<string> = []\n\n if (queryKeyNode.type === AST_NODE_TYPES.ArrayExpression) {\n suggestions.push({\n messageId: 'fixTo',\n data: { result: existingWithMissing },\n fix(fixer) {\n return fixer.replaceText(queryKeyValue, existingWithMissing)\n },\n })\n }\n\n context.report({\n node: node,\n messageId: 'missingDeps',\n data: {\n deps: uniqueMissingRefs.map((ref) => ref.text).join(', '),\n },\n suggest: suggestions,\n })\n }\n },\n }\n }),\n})\n"],"names":["ESLintUtils","getDocsUrl","detectTanstackQueryImports","ASTUtils","AST_NODE_TYPES","ExhaustiveDepsUtils","uniqueBy"],"mappings":"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"}
|
|
@@ -13,7 +13,10 @@ const ExhaustiveDepsUtils = {
|
|
|
13
13
|
})) {
|
|
14
14
|
return false;
|
|
15
15
|
}
|
|
16
|
-
return reference.identifier.name !== "undefined" && reference.identifier.parent.type !== utils.AST_NODE_TYPES.NewExpression && !ExhaustiveDepsUtils.isQueryClientReference(reference);
|
|
16
|
+
return reference.identifier.name !== "undefined" && reference.identifier.parent.type !== utils.AST_NODE_TYPES.NewExpression && !ExhaustiveDepsUtils.isInstanceOfKind(reference.identifier.parent) && !ExhaustiveDepsUtils.isQueryClientReference(reference);
|
|
17
|
+
},
|
|
18
|
+
isInstanceOfKind(node) {
|
|
19
|
+
return node.type === utils.AST_NODE_TYPES.BinaryExpression && node.operator === "instanceof";
|
|
17
20
|
},
|
|
18
21
|
isQueryClientReference(reference) {
|
|
19
22
|
var _a, _b, _c;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exhaustive-deps.utils.cjs","sources":["../../../../src/rules/exhaustive-deps/exhaustive-deps.utils.ts"],"sourcesContent":["import { AST_NODE_TYPES } from '@typescript-eslint/utils'\nimport { ASTUtils } from '../../utils/ast-utils'\nimport type { TSESLint } from '@typescript-eslint/utils'\n\nexport const ExhaustiveDepsUtils = {\n isRelevantReference(params: {\n context: Readonly<TSESLint.RuleContext<string, ReadonlyArray<unknown>>>\n reference: TSESLint.Scope.Reference\n scopeManager: TSESLint.Scope.ScopeManager\n }) {\n const { reference, scopeManager, context } = params\n const component = ASTUtils.getFunctionAncestor(context)\n\n if (\n component !== undefined &&\n !ASTUtils.isDeclaredInNode({\n scopeManager,\n reference,\n functionNode: component,\n })\n ) {\n return false\n }\n\n return (\n reference.identifier.name !== 'undefined' &&\n reference.identifier.parent.type !== AST_NODE_TYPES.NewExpression &&\n !ExhaustiveDepsUtils.isQueryClientReference(reference)\n )\n },\n isQueryClientReference(reference: TSESLint.Scope.Reference) {\n const declarator = reference.resolved?.defs[0]?.node\n\n return (\n declarator?.type === AST_NODE_TYPES.VariableDeclarator &&\n declarator.init?.type === AST_NODE_TYPES.CallExpression &&\n declarator.init.callee.type === AST_NODE_TYPES.Identifier &&\n declarator.init.callee.name === 'useQueryClient'\n )\n },\n}\n"],"names":["ASTUtils","AST_NODE_TYPES"],"mappings":";;;;AAIO,MAAM,sBAAsB;AAAA,EACjC,oBAAoB,QAIjB;AACD,UAAM,EAAE,WAAW,cAAc,QAAA,IAAY;AACvC,UAAA,YAAYA,SAAAA,SAAS,oBAAoB,OAAO;AAEtD,QACE,cAAc,UACd,CAACA,SAAAA,SAAS,iBAAiB;AAAA,MACzB;AAAA,MACA;AAAA,MACA,cAAc;AAAA,IAAA,CACf,GACD;AACO,aAAA;AAAA,IACT;
|
|
1
|
+
{"version":3,"file":"exhaustive-deps.utils.cjs","sources":["../../../../src/rules/exhaustive-deps/exhaustive-deps.utils.ts"],"sourcesContent":["import { AST_NODE_TYPES } from '@typescript-eslint/utils'\nimport { ASTUtils } from '../../utils/ast-utils'\nimport type { TSESLint, TSESTree } from '@typescript-eslint/utils'\n\nexport const ExhaustiveDepsUtils = {\n isRelevantReference(params: {\n context: Readonly<TSESLint.RuleContext<string, ReadonlyArray<unknown>>>\n reference: TSESLint.Scope.Reference\n scopeManager: TSESLint.Scope.ScopeManager\n }) {\n const { reference, scopeManager, context } = params\n const component = ASTUtils.getFunctionAncestor(context)\n\n if (\n component !== undefined &&\n !ASTUtils.isDeclaredInNode({\n scopeManager,\n reference,\n functionNode: component,\n })\n ) {\n return false\n }\n\n return (\n reference.identifier.name !== 'undefined' &&\n reference.identifier.parent.type !== AST_NODE_TYPES.NewExpression &&\n !ExhaustiveDepsUtils.isInstanceOfKind(reference.identifier.parent) &&\n !ExhaustiveDepsUtils.isQueryClientReference(reference)\n )\n },\n isInstanceOfKind(node: TSESTree.Node) {\n return (\n node.type === AST_NODE_TYPES.BinaryExpression &&\n node.operator === 'instanceof'\n )\n },\n isQueryClientReference(reference: TSESLint.Scope.Reference) {\n const declarator = reference.resolved?.defs[0]?.node\n\n return (\n declarator?.type === AST_NODE_TYPES.VariableDeclarator &&\n declarator.init?.type === AST_NODE_TYPES.CallExpression &&\n declarator.init.callee.type === AST_NODE_TYPES.Identifier &&\n declarator.init.callee.name === 'useQueryClient'\n )\n },\n}\n"],"names":["ASTUtils","AST_NODE_TYPES"],"mappings":";;;;AAIO,MAAM,sBAAsB;AAAA,EACjC,oBAAoB,QAIjB;AACD,UAAM,EAAE,WAAW,cAAc,QAAA,IAAY;AACvC,UAAA,YAAYA,SAAAA,SAAS,oBAAoB,OAAO;AAEtD,QACE,cAAc,UACd,CAACA,SAAAA,SAAS,iBAAiB;AAAA,MACzB;AAAA,MACA;AAAA,MACA,cAAc;AAAA,IAAA,CACf,GACD;AACO,aAAA;AAAA,IACT;AAGE,WAAA,UAAU,WAAW,SAAS,eAC9B,UAAU,WAAW,OAAO,SAASC,qBAAe,iBACpD,CAAC,oBAAoB,iBAAiB,UAAU,WAAW,MAAM,KACjE,CAAC,oBAAoB,uBAAuB,SAAS;AAAA,EAEzD;AAAA,EACA,iBAAiB,MAAqB;AACpC,WACE,KAAK,SAASA,MAAe,eAAA,oBAC7B,KAAK,aAAa;AAAA,EAEtB;AAAA,EACA,uBAAuB,WAAqC;;AAC1D,UAAM,cAAa,qBAAU,aAAV,mBAAoB,KAAK,OAAzB,mBAA6B;AAEhD,YACE,yCAAY,UAASA,MAAAA,eAAe,wBACpC,gBAAW,SAAX,mBAAiB,UAASA,MAAe,eAAA,kBACzC,WAAW,KAAK,OAAO,SAASA,MAAAA,eAAe,cAC/C,WAAW,KAAK,OAAO,SAAS;AAAA,EAEpC;AACF;;"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import type { TSESLint } from '@typescript-eslint/utils';
|
|
1
|
+
import type { TSESLint, TSESTree } from '@typescript-eslint/utils';
|
|
2
2
|
export declare const ExhaustiveDepsUtils: {
|
|
3
3
|
isRelevantReference(params: {
|
|
4
4
|
context: Readonly<TSESLint.RuleContext<string, ReadonlyArray<unknown>>>;
|
|
5
5
|
reference: TSESLint.Scope.Reference;
|
|
6
6
|
scopeManager: TSESLint.Scope.ScopeManager;
|
|
7
7
|
}): boolean;
|
|
8
|
+
isInstanceOfKind(node: TSESTree.Node): boolean;
|
|
8
9
|
isQueryClientReference(reference: TSESLint.Scope.Reference): boolean;
|
|
9
10
|
};
|
|
@@ -31,7 +31,7 @@ const rule = createRule({
|
|
|
31
31
|
if (!ASTUtils.isObjectExpression(node.parent) || !ASTUtils.isIdentifierWithName(node.key, QUERY_KEY)) {
|
|
32
32
|
return;
|
|
33
33
|
}
|
|
34
|
-
const scopeManager = context.
|
|
34
|
+
const scopeManager = context.sourceCode.scopeManager;
|
|
35
35
|
const queryKey = ASTUtils.findPropertyWithIdentifierKey(
|
|
36
36
|
node.parent.properties,
|
|
37
37
|
QUERY_KEY
|
|
@@ -59,11 +59,10 @@ const rule = createRule({
|
|
|
59
59
|
queryKeyNode = expression;
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
|
-
const sourceCode = context.getSourceCode();
|
|
63
62
|
const queryKeyValue = queryKeyNode;
|
|
64
63
|
const externalRefs = ASTUtils.getExternalRefs({
|
|
65
64
|
scopeManager,
|
|
66
|
-
sourceCode,
|
|
65
|
+
sourceCode: context.sourceCode,
|
|
67
66
|
node: queryFn.value
|
|
68
67
|
});
|
|
69
68
|
const relevantRefs = externalRefs.filter(
|
|
@@ -74,11 +73,11 @@ const rule = createRule({
|
|
|
74
73
|
})
|
|
75
74
|
);
|
|
76
75
|
const existingKeys = ASTUtils.getNestedIdentifiers(queryKeyValue).map(
|
|
77
|
-
(identifier) => ASTUtils.mapKeyNodeToText(identifier, sourceCode)
|
|
76
|
+
(identifier) => ASTUtils.mapKeyNodeToText(identifier, context.sourceCode)
|
|
78
77
|
);
|
|
79
78
|
const missingRefs = relevantRefs.map((ref) => ({
|
|
80
79
|
ref,
|
|
81
|
-
text: ASTUtils.mapKeyNodeToText(ref.identifier, sourceCode)
|
|
80
|
+
text: ASTUtils.mapKeyNodeToText(ref.identifier, context.sourceCode)
|
|
82
81
|
})).filter(({ ref, text }) => {
|
|
83
82
|
return !ref.isTypeReference && !ASTUtils.isAncestorIsCallee(ref.identifier) && !existingKeys.some((existingKey) => existingKey === text) && !existingKeys.includes(text.split(".")[0] ?? "");
|
|
84
83
|
}).map(({ ref, text }) => ({
|
|
@@ -87,8 +86,10 @@ const rule = createRule({
|
|
|
87
86
|
}));
|
|
88
87
|
const uniqueMissingRefs = uniqueBy(missingRefs, (x) => x.text);
|
|
89
88
|
if (uniqueMissingRefs.length > 0) {
|
|
90
|
-
const missingAsText = uniqueMissingRefs.map(
|
|
91
|
-
|
|
89
|
+
const missingAsText = uniqueMissingRefs.map(
|
|
90
|
+
(ref) => ASTUtils.mapKeyNodeToText(ref.identifier, context.sourceCode)
|
|
91
|
+
).join(", ");
|
|
92
|
+
const existingWithMissing = context.sourceCode.getText(queryKeyValue).replace(/\]$/, `, ${missingAsText}]`);
|
|
92
93
|
const suggestions = [];
|
|
93
94
|
if (queryKeyNode.type === AST_NODE_TYPES.ArrayExpression) {
|
|
94
95
|
suggestions.push({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exhaustive-deps.rule.js","sources":["../../../../src/rules/exhaustive-deps/exhaustive-deps.rule.ts"],"sourcesContent":["import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils'\nimport { ASTUtils } from '../../utils/ast-utils'\nimport { getDocsUrl } from '../../utils/get-docs-url'\nimport { uniqueBy } from '../../utils/unique-by'\nimport { detectTanstackQueryImports } from '../../utils/detect-react-query-imports'\nimport { ExhaustiveDepsUtils } from './exhaustive-deps.utils'\nimport type { TSESLint } from '@typescript-eslint/utils'\n\nconst QUERY_KEY = 'queryKey'\nconst QUERY_FN = 'queryFn'\n\nexport const name = 'exhaustive-deps'\n\nconst createRule = ESLintUtils.RuleCreator(getDocsUrl)\n\nexport const rule = createRule({\n name,\n meta: {\n type: 'problem',\n docs: {\n description: 'Exhaustive deps rule for useQuery',\n recommended: 'error' as any,\n },\n messages: {\n missingDeps: `The following dependencies are missing in your queryKey: {{deps}}`,\n fixTo: 'Fix to {{result}}',\n },\n hasSuggestions: true,\n fixable: 'code',\n schema: [],\n },\n defaultOptions: [],\n\n create: detectTanstackQueryImports((context) => {\n return {\n Property: (node) => {\n if (\n !ASTUtils.isObjectExpression(node.parent) ||\n !ASTUtils.isIdentifierWithName(node.key, QUERY_KEY)\n ) {\n return\n }\n\n const scopeManager = context.
|
|
1
|
+
{"version":3,"file":"exhaustive-deps.rule.js","sources":["../../../../src/rules/exhaustive-deps/exhaustive-deps.rule.ts"],"sourcesContent":["import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils'\nimport { ASTUtils } from '../../utils/ast-utils'\nimport { getDocsUrl } from '../../utils/get-docs-url'\nimport { uniqueBy } from '../../utils/unique-by'\nimport { detectTanstackQueryImports } from '../../utils/detect-react-query-imports'\nimport { ExhaustiveDepsUtils } from './exhaustive-deps.utils'\nimport type { TSESLint } from '@typescript-eslint/utils'\n\nconst QUERY_KEY = 'queryKey'\nconst QUERY_FN = 'queryFn'\n\nexport const name = 'exhaustive-deps'\n\nconst createRule = ESLintUtils.RuleCreator(getDocsUrl)\n\nexport const rule = createRule({\n name,\n meta: {\n type: 'problem',\n docs: {\n description: 'Exhaustive deps rule for useQuery',\n recommended: 'error' as any,\n },\n messages: {\n missingDeps: `The following dependencies are missing in your queryKey: {{deps}}`,\n fixTo: 'Fix to {{result}}',\n },\n hasSuggestions: true,\n fixable: 'code',\n schema: [],\n },\n defaultOptions: [],\n\n create: detectTanstackQueryImports((context) => {\n return {\n Property: (node) => {\n if (\n !ASTUtils.isObjectExpression(node.parent) ||\n !ASTUtils.isIdentifierWithName(node.key, QUERY_KEY)\n ) {\n return\n }\n\n const scopeManager = context.sourceCode.scopeManager\n const queryKey = ASTUtils.findPropertyWithIdentifierKey(\n node.parent.properties,\n QUERY_KEY,\n )\n const queryFn = ASTUtils.findPropertyWithIdentifierKey(\n node.parent.properties,\n QUERY_FN,\n )\n\n if (\n scopeManager === null ||\n queryKey === undefined ||\n queryFn === undefined ||\n !ASTUtils.isNodeOfOneOf(queryFn.value, [\n AST_NODE_TYPES.ArrowFunctionExpression,\n AST_NODE_TYPES.FunctionExpression,\n ])\n ) {\n return\n }\n\n let queryKeyNode = queryKey.value\n\n if (\n queryKeyNode.type === AST_NODE_TYPES.TSAsExpression &&\n queryKeyNode.expression.type === AST_NODE_TYPES.ArrayExpression\n ) {\n queryKeyNode = queryKeyNode.expression\n }\n\n if (queryKeyNode.type === AST_NODE_TYPES.Identifier) {\n const expression = ASTUtils.getReferencedExpressionByIdentifier({\n context,\n node: queryKeyNode,\n })\n\n if (expression?.type === AST_NODE_TYPES.ArrayExpression) {\n queryKeyNode = expression\n }\n }\n\n const queryKeyValue = queryKeyNode\n const externalRefs = ASTUtils.getExternalRefs({\n scopeManager,\n sourceCode: context.sourceCode,\n node: queryFn.value,\n })\n\n const relevantRefs = externalRefs.filter((reference) =>\n ExhaustiveDepsUtils.isRelevantReference({\n context,\n reference,\n scopeManager,\n }),\n )\n\n const existingKeys = ASTUtils.getNestedIdentifiers(queryKeyValue).map(\n (identifier) =>\n ASTUtils.mapKeyNodeToText(identifier, context.sourceCode),\n )\n\n const missingRefs = relevantRefs\n .map((ref) => ({\n ref: ref,\n text: ASTUtils.mapKeyNodeToText(ref.identifier, context.sourceCode),\n }))\n .filter(({ ref, text }) => {\n return (\n !ref.isTypeReference &&\n !ASTUtils.isAncestorIsCallee(ref.identifier) &&\n !existingKeys.some((existingKey) => existingKey === text) &&\n !existingKeys.includes(text.split('.')[0] ?? '')\n )\n })\n .map(({ ref, text }) => ({\n identifier: ref.identifier,\n text: text,\n }))\n\n const uniqueMissingRefs = uniqueBy(missingRefs, (x) => x.text)\n\n if (uniqueMissingRefs.length > 0) {\n const missingAsText = uniqueMissingRefs\n .map((ref) =>\n ASTUtils.mapKeyNodeToText(ref.identifier, context.sourceCode),\n )\n .join(', ')\n\n const existingWithMissing = context.sourceCode\n .getText(queryKeyValue)\n .replace(/\\]$/, `, ${missingAsText}]`)\n\n const suggestions: TSESLint.ReportSuggestionArray<string> = []\n\n if (queryKeyNode.type === AST_NODE_TYPES.ArrayExpression) {\n suggestions.push({\n messageId: 'fixTo',\n data: { result: existingWithMissing },\n fix(fixer) {\n return fixer.replaceText(queryKeyValue, existingWithMissing)\n },\n })\n }\n\n context.report({\n node: node,\n messageId: 'missingDeps',\n data: {\n deps: uniqueMissingRefs.map((ref) => ref.text).join(', '),\n },\n suggest: suggestions,\n })\n }\n },\n }\n }),\n})\n"],"names":[],"mappings":"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"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import type { TSESLint } from '@typescript-eslint/utils';
|
|
1
|
+
import type { TSESLint, TSESTree } from '@typescript-eslint/utils';
|
|
2
2
|
export declare const ExhaustiveDepsUtils: {
|
|
3
3
|
isRelevantReference(params: {
|
|
4
4
|
context: Readonly<TSESLint.RuleContext<string, ReadonlyArray<unknown>>>;
|
|
5
5
|
reference: TSESLint.Scope.Reference;
|
|
6
6
|
scopeManager: TSESLint.Scope.ScopeManager;
|
|
7
7
|
}): boolean;
|
|
8
|
+
isInstanceOfKind(node: TSESTree.Node): boolean;
|
|
8
9
|
isQueryClientReference(reference: TSESLint.Scope.Reference): boolean;
|
|
9
10
|
};
|
|
@@ -11,7 +11,10 @@ const ExhaustiveDepsUtils = {
|
|
|
11
11
|
})) {
|
|
12
12
|
return false;
|
|
13
13
|
}
|
|
14
|
-
return reference.identifier.name !== "undefined" && reference.identifier.parent.type !== AST_NODE_TYPES.NewExpression && !ExhaustiveDepsUtils.isQueryClientReference(reference);
|
|
14
|
+
return reference.identifier.name !== "undefined" && reference.identifier.parent.type !== AST_NODE_TYPES.NewExpression && !ExhaustiveDepsUtils.isInstanceOfKind(reference.identifier.parent) && !ExhaustiveDepsUtils.isQueryClientReference(reference);
|
|
15
|
+
},
|
|
16
|
+
isInstanceOfKind(node) {
|
|
17
|
+
return node.type === AST_NODE_TYPES.BinaryExpression && node.operator === "instanceof";
|
|
15
18
|
},
|
|
16
19
|
isQueryClientReference(reference) {
|
|
17
20
|
var _a, _b, _c;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exhaustive-deps.utils.js","sources":["../../../../src/rules/exhaustive-deps/exhaustive-deps.utils.ts"],"sourcesContent":["import { AST_NODE_TYPES } from '@typescript-eslint/utils'\nimport { ASTUtils } from '../../utils/ast-utils'\nimport type { TSESLint } from '@typescript-eslint/utils'\n\nexport const ExhaustiveDepsUtils = {\n isRelevantReference(params: {\n context: Readonly<TSESLint.RuleContext<string, ReadonlyArray<unknown>>>\n reference: TSESLint.Scope.Reference\n scopeManager: TSESLint.Scope.ScopeManager\n }) {\n const { reference, scopeManager, context } = params\n const component = ASTUtils.getFunctionAncestor(context)\n\n if (\n component !== undefined &&\n !ASTUtils.isDeclaredInNode({\n scopeManager,\n reference,\n functionNode: component,\n })\n ) {\n return false\n }\n\n return (\n reference.identifier.name !== 'undefined' &&\n reference.identifier.parent.type !== AST_NODE_TYPES.NewExpression &&\n !ExhaustiveDepsUtils.isQueryClientReference(reference)\n )\n },\n isQueryClientReference(reference: TSESLint.Scope.Reference) {\n const declarator = reference.resolved?.defs[0]?.node\n\n return (\n declarator?.type === AST_NODE_TYPES.VariableDeclarator &&\n declarator.init?.type === AST_NODE_TYPES.CallExpression &&\n declarator.init.callee.type === AST_NODE_TYPES.Identifier &&\n declarator.init.callee.name === 'useQueryClient'\n )\n },\n}\n"],"names":[],"mappings":";;AAIO,MAAM,sBAAsB;AAAA,EACjC,oBAAoB,QAIjB;AACD,UAAM,EAAE,WAAW,cAAc,QAAA,IAAY;AACvC,UAAA,YAAY,SAAS,oBAAoB,OAAO;AAEtD,QACE,cAAc,UACd,CAAC,SAAS,iBAAiB;AAAA,MACzB;AAAA,MACA;AAAA,MACA,cAAc;AAAA,IAAA,CACf,GACD;AACO,aAAA;AAAA,IACT;
|
|
1
|
+
{"version":3,"file":"exhaustive-deps.utils.js","sources":["../../../../src/rules/exhaustive-deps/exhaustive-deps.utils.ts"],"sourcesContent":["import { AST_NODE_TYPES } from '@typescript-eslint/utils'\nimport { ASTUtils } from '../../utils/ast-utils'\nimport type { TSESLint, TSESTree } from '@typescript-eslint/utils'\n\nexport const ExhaustiveDepsUtils = {\n isRelevantReference(params: {\n context: Readonly<TSESLint.RuleContext<string, ReadonlyArray<unknown>>>\n reference: TSESLint.Scope.Reference\n scopeManager: TSESLint.Scope.ScopeManager\n }) {\n const { reference, scopeManager, context } = params\n const component = ASTUtils.getFunctionAncestor(context)\n\n if (\n component !== undefined &&\n !ASTUtils.isDeclaredInNode({\n scopeManager,\n reference,\n functionNode: component,\n })\n ) {\n return false\n }\n\n return (\n reference.identifier.name !== 'undefined' &&\n reference.identifier.parent.type !== AST_NODE_TYPES.NewExpression &&\n !ExhaustiveDepsUtils.isInstanceOfKind(reference.identifier.parent) &&\n !ExhaustiveDepsUtils.isQueryClientReference(reference)\n )\n },\n isInstanceOfKind(node: TSESTree.Node) {\n return (\n node.type === AST_NODE_TYPES.BinaryExpression &&\n node.operator === 'instanceof'\n )\n },\n isQueryClientReference(reference: TSESLint.Scope.Reference) {\n const declarator = reference.resolved?.defs[0]?.node\n\n return (\n declarator?.type === AST_NODE_TYPES.VariableDeclarator &&\n declarator.init?.type === AST_NODE_TYPES.CallExpression &&\n declarator.init.callee.type === AST_NODE_TYPES.Identifier &&\n declarator.init.callee.name === 'useQueryClient'\n )\n },\n}\n"],"names":[],"mappings":";;AAIO,MAAM,sBAAsB;AAAA,EACjC,oBAAoB,QAIjB;AACD,UAAM,EAAE,WAAW,cAAc,QAAA,IAAY;AACvC,UAAA,YAAY,SAAS,oBAAoB,OAAO;AAEtD,QACE,cAAc,UACd,CAAC,SAAS,iBAAiB;AAAA,MACzB;AAAA,MACA;AAAA,MACA,cAAc;AAAA,IAAA,CACf,GACD;AACO,aAAA;AAAA,IACT;AAGE,WAAA,UAAU,WAAW,SAAS,eAC9B,UAAU,WAAW,OAAO,SAAS,eAAe,iBACpD,CAAC,oBAAoB,iBAAiB,UAAU,WAAW,MAAM,KACjE,CAAC,oBAAoB,uBAAuB,SAAS;AAAA,EAEzD;AAAA,EACA,iBAAiB,MAAqB;AACpC,WACE,KAAK,SAAS,eAAe,oBAC7B,KAAK,aAAa;AAAA,EAEtB;AAAA,EACA,uBAAuB,WAAqC;;AAC1D,UAAM,cAAa,qBAAU,aAAV,mBAAoB,KAAK,OAAzB,mBAA6B;AAEhD,YACE,yCAAY,UAAS,eAAe,wBACpC,gBAAW,SAAX,mBAAiB,UAAS,eAAe,kBACzC,WAAW,KAAK,OAAO,SAAS,eAAe,cAC/C,WAAW,KAAK,OAAO,SAAS;AAAA,EAEpC;AACF;"}
|
package/package.json
CHANGED
|
@@ -387,6 +387,21 @@ ruleTester.run('exhaustive-deps', rule, {
|
|
|
387
387
|
}
|
|
388
388
|
`,
|
|
389
389
|
},
|
|
390
|
+
{
|
|
391
|
+
name: 'instanceof value should not be in query key',
|
|
392
|
+
code: `
|
|
393
|
+
class SomeClass {}
|
|
394
|
+
|
|
395
|
+
function Component({ value }) {
|
|
396
|
+
useQuery({
|
|
397
|
+
queryKey: ['foo'],
|
|
398
|
+
queryFn: () => {
|
|
399
|
+
return value instanceof SomeClass;
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
`,
|
|
404
|
+
},
|
|
390
405
|
],
|
|
391
406
|
invalid: [
|
|
392
407
|
{
|
|
@@ -41,7 +41,7 @@ export const rule = createRule({
|
|
|
41
41
|
return
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
const scopeManager = context.
|
|
44
|
+
const scopeManager = context.sourceCode.scopeManager
|
|
45
45
|
const queryKey = ASTUtils.findPropertyWithIdentifierKey(
|
|
46
46
|
node.parent.properties,
|
|
47
47
|
QUERY_KEY,
|
|
@@ -83,11 +83,10 @@ export const rule = createRule({
|
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
const sourceCode = context.getSourceCode()
|
|
87
86
|
const queryKeyValue = queryKeyNode
|
|
88
87
|
const externalRefs = ASTUtils.getExternalRefs({
|
|
89
88
|
scopeManager,
|
|
90
|
-
sourceCode,
|
|
89
|
+
sourceCode: context.sourceCode,
|
|
91
90
|
node: queryFn.value,
|
|
92
91
|
})
|
|
93
92
|
|
|
@@ -100,13 +99,14 @@ export const rule = createRule({
|
|
|
100
99
|
)
|
|
101
100
|
|
|
102
101
|
const existingKeys = ASTUtils.getNestedIdentifiers(queryKeyValue).map(
|
|
103
|
-
(identifier) =>
|
|
102
|
+
(identifier) =>
|
|
103
|
+
ASTUtils.mapKeyNodeToText(identifier, context.sourceCode),
|
|
104
104
|
)
|
|
105
105
|
|
|
106
106
|
const missingRefs = relevantRefs
|
|
107
107
|
.map((ref) => ({
|
|
108
108
|
ref: ref,
|
|
109
|
-
text: ASTUtils.mapKeyNodeToText(ref.identifier, sourceCode),
|
|
109
|
+
text: ASTUtils.mapKeyNodeToText(ref.identifier, context.sourceCode),
|
|
110
110
|
}))
|
|
111
111
|
.filter(({ ref, text }) => {
|
|
112
112
|
return (
|
|
@@ -125,10 +125,12 @@ export const rule = createRule({
|
|
|
125
125
|
|
|
126
126
|
if (uniqueMissingRefs.length > 0) {
|
|
127
127
|
const missingAsText = uniqueMissingRefs
|
|
128
|
-
.map((ref) =>
|
|
128
|
+
.map((ref) =>
|
|
129
|
+
ASTUtils.mapKeyNodeToText(ref.identifier, context.sourceCode),
|
|
130
|
+
)
|
|
129
131
|
.join(', ')
|
|
130
132
|
|
|
131
|
-
const existingWithMissing = sourceCode
|
|
133
|
+
const existingWithMissing = context.sourceCode
|
|
132
134
|
.getText(queryKeyValue)
|
|
133
135
|
.replace(/\]$/, `, ${missingAsText}]`)
|
|
134
136
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AST_NODE_TYPES } from '@typescript-eslint/utils'
|
|
2
2
|
import { ASTUtils } from '../../utils/ast-utils'
|
|
3
|
-
import type { TSESLint } from '@typescript-eslint/utils'
|
|
3
|
+
import type { TSESLint, TSESTree } from '@typescript-eslint/utils'
|
|
4
4
|
|
|
5
5
|
export const ExhaustiveDepsUtils = {
|
|
6
6
|
isRelevantReference(params: {
|
|
@@ -25,9 +25,16 @@ export const ExhaustiveDepsUtils = {
|
|
|
25
25
|
return (
|
|
26
26
|
reference.identifier.name !== 'undefined' &&
|
|
27
27
|
reference.identifier.parent.type !== AST_NODE_TYPES.NewExpression &&
|
|
28
|
+
!ExhaustiveDepsUtils.isInstanceOfKind(reference.identifier.parent) &&
|
|
28
29
|
!ExhaustiveDepsUtils.isQueryClientReference(reference)
|
|
29
30
|
)
|
|
30
31
|
},
|
|
32
|
+
isInstanceOfKind(node: TSESTree.Node) {
|
|
33
|
+
return (
|
|
34
|
+
node.type === AST_NODE_TYPES.BinaryExpression &&
|
|
35
|
+
node.operator === 'instanceof'
|
|
36
|
+
)
|
|
37
|
+
},
|
|
31
38
|
isQueryClientReference(reference: TSESLint.Scope.Reference) {
|
|
32
39
|
const declarator = reference.resolved?.defs[0]?.node
|
|
33
40
|
|