@tanstack/eslint-plugin-query 5.52.3 → 5.56.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.d.cts +0 -1
- package/dist/cjs/rules/exhaustive-deps/exhaustive-deps.rule.cjs +4 -4
- package/dist/cjs/rules/exhaustive-deps/exhaustive-deps.rule.cjs.map +1 -1
- package/dist/cjs/rules/exhaustive-deps/exhaustive-deps.rule.d.cts +0 -1
- package/dist/cjs/rules/exhaustive-deps/exhaustive-deps.utils.d.cts +0 -1
- package/dist/cjs/rules/no-rest-destructuring/no-rest-destructuring.rule.d.cts +0 -1
- package/dist/cjs/rules/no-rest-destructuring/no-rest-destructuring.utils.d.cts +0 -1
- package/dist/cjs/rules/no-unstable-deps/no-unstable-deps.rule.d.cts +0 -1
- package/dist/cjs/rules/stable-query-client/stable-query-client.rule.d.cts +0 -1
- package/dist/cjs/rules.d.cts +0 -1
- package/dist/cjs/utils/ast-utils.d.cts +1 -2
- package/dist/cjs/utils/detect-react-query-imports.d.cts +0 -1
- package/dist/esm/index.d.ts +0 -1
- package/dist/esm/rules/exhaustive-deps/exhaustive-deps.rule.d.ts +0 -1
- package/dist/esm/rules/exhaustive-deps/exhaustive-deps.rule.js +4 -4
- package/dist/esm/rules/exhaustive-deps/exhaustive-deps.rule.js.map +1 -1
- package/dist/esm/rules/exhaustive-deps/exhaustive-deps.utils.d.ts +0 -1
- package/dist/esm/rules/no-rest-destructuring/no-rest-destructuring.rule.d.ts +0 -1
- package/dist/esm/rules/no-rest-destructuring/no-rest-destructuring.utils.d.ts +0 -1
- package/dist/esm/rules/no-unstable-deps/no-unstable-deps.rule.d.ts +0 -1
- package/dist/esm/rules/stable-query-client/stable-query-client.rule.d.ts +0 -1
- package/dist/esm/rules.d.ts +0 -1
- package/dist/esm/utils/ast-utils.d.ts +1 -2
- package/dist/esm/utils/detect-react-query-imports.d.ts +0 -1
- package/package.json +5 -5
- package/src/__tests__/exhaustive-deps.test.ts +56 -15
- package/src/__tests__/no-rest-destructuring.test.ts +1 -4
- package/src/__tests__/no-unstable-deps.test.ts +4 -7
- package/src/__tests__/stable-query-client.test.ts +3 -6
- package/src/rules/exhaustive-deps/exhaustive-deps.rule.ts +8 -6
package/dist/cjs/index.d.cts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { rules } from './rules.cjs';
|
|
2
2
|
import { ESLint, Linter } from 'eslint';
|
|
3
3
|
import { RuleModule } from '@typescript-eslint/utils/ts-eslint';
|
|
4
|
-
|
|
5
4
|
type RuleKey = keyof typeof rules;
|
|
6
5
|
interface Plugin extends Omit<ESLint.Plugin, 'rules'> {
|
|
7
6
|
rules: Record<RuleKey, RuleModule<any, any, any>>;
|
|
@@ -62,7 +62,6 @@ const rule = createRule({
|
|
|
62
62
|
queryKeyNode = expression;
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
-
const queryKeyValue = queryKeyNode;
|
|
66
65
|
const externalRefs = astUtils.ASTUtils.getExternalRefs({
|
|
67
66
|
scopeManager,
|
|
68
67
|
sourceCode: context.sourceCode,
|
|
@@ -76,7 +75,7 @@ const rule = createRule({
|
|
|
76
75
|
node: getQueryFnRelevantNode(queryFn)
|
|
77
76
|
})
|
|
78
77
|
);
|
|
79
|
-
const existingKeys = astUtils.ASTUtils.getNestedIdentifiers(
|
|
78
|
+
const existingKeys = astUtils.ASTUtils.getNestedIdentifiers(queryKeyNode).map(
|
|
80
79
|
(identifier) => astUtils.ASTUtils.mapKeyNodeToText(identifier, context.sourceCode)
|
|
81
80
|
);
|
|
82
81
|
const missingRefs = relevantRefs.map((ref) => ({
|
|
@@ -93,14 +92,15 @@ const rule = createRule({
|
|
|
93
92
|
const missingAsText = uniqueMissingRefs.map(
|
|
94
93
|
(ref) => astUtils.ASTUtils.mapKeyNodeToText(ref.identifier, context.sourceCode)
|
|
95
94
|
).join(", ");
|
|
96
|
-
const
|
|
95
|
+
const queryKeyValue = context.sourceCode.getText(queryKeyNode);
|
|
96
|
+
const existingWithMissing = queryKeyValue === "[]" ? `[${missingAsText}]` : queryKeyValue.replace(/\]$/, `, ${missingAsText}]`);
|
|
97
97
|
const suggestions = [];
|
|
98
98
|
if (queryKeyNode.type === utils.AST_NODE_TYPES.ArrayExpression) {
|
|
99
99
|
suggestions.push({
|
|
100
100
|
messageId: "fixTo",
|
|
101
101
|
data: { result: existingWithMissing },
|
|
102
102
|
fix(fixer) {
|
|
103
|
-
return fixer.replaceText(
|
|
103
|
+
return fixer.replaceText(queryKeyNode, existingWithMissing);
|
|
104
104
|
}
|
|
105
105
|
});
|
|
106
106
|
}
|
|
@@ -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, TSESTree } from '@typescript-eslint/utils'\nimport type { ExtraRuleDocs } from '../../types'\n\nconst QUERY_KEY = 'queryKey'\nconst QUERY_FN = 'queryFn'\n\nexport const name = 'exhaustive-deps'\n\nconst createRule = ESLintUtils.RuleCreator<ExtraRuleDocs>(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',\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 AST_NODE_TYPES.ConditionalExpression,\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
|
|
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, TSESTree } from '@typescript-eslint/utils'\nimport type { ExtraRuleDocs } from '../../types'\n\nconst QUERY_KEY = 'queryKey'\nconst QUERY_FN = 'queryFn'\n\nexport const name = 'exhaustive-deps'\n\nconst createRule = ESLintUtils.RuleCreator<ExtraRuleDocs>(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',\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 AST_NODE_TYPES.ConditionalExpression,\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 externalRefs = ASTUtils.getExternalRefs({\n scopeManager,\n sourceCode: context.sourceCode,\n node: getQueryFnRelevantNode(queryFn),\n })\n\n const relevantRefs = externalRefs.filter((reference) =>\n ExhaustiveDepsUtils.isRelevantReference({\n sourceCode: context.sourceCode,\n reference,\n scopeManager,\n node: getQueryFnRelevantNode(queryFn),\n }),\n )\n\n const existingKeys = ASTUtils.getNestedIdentifiers(queryKeyNode).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 queryKeyValue = context.sourceCode.getText(queryKeyNode)\n\n const existingWithMissing =\n queryKeyValue === '[]'\n ? `[${missingAsText}]`\n : queryKeyValue.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(queryKeyNode, 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\nfunction getQueryFnRelevantNode(queryFn: TSESTree.Property) {\n if (queryFn.value.type !== AST_NODE_TYPES.ConditionalExpression) {\n return queryFn.value\n }\n\n if (\n queryFn.value.consequent.type === AST_NODE_TYPES.Identifier &&\n queryFn.value.consequent.name === 'skipToken'\n ) {\n return queryFn.value.alternate\n }\n\n return queryFn.value.consequent\n}\n"],"names":["ESLintUtils","getDocsUrl","detectTanstackQueryImports","ASTUtils","AST_NODE_TYPES","ExhaustiveDepsUtils","uniqueBy"],"mappings":"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"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
2
|
import { ExtraRuleDocs } from '../../types';
|
|
3
|
-
|
|
4
3
|
export declare const name = "exhaustive-deps";
|
|
5
4
|
export declare const rule: ESLintUtils.RuleModule<string, readonly unknown[], ExtraRuleDocs, ESLintUtils.RuleListener>;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
2
|
import { ExtraRuleDocs } from '../../types';
|
|
3
|
-
|
|
4
3
|
export declare const name = "no-rest-destructuring";
|
|
5
4
|
export declare const rule: ESLintUtils.RuleModule<string, readonly unknown[], ExtraRuleDocs, ESLintUtils.RuleListener>;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
2
|
import { ExtraRuleDocs } from '../../types';
|
|
3
|
-
|
|
4
3
|
export declare const name = "stable-query-client";
|
|
5
4
|
export declare const rule: ESLintUtils.RuleModule<string, readonly unknown[], ExtraRuleDocs, ESLintUtils.RuleListener>;
|
package/dist/cjs/rules.d.cts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { AST_NODE_TYPES, TSESLint, TSESTree } from '@typescript-eslint/utils';
|
|
2
|
-
|
|
3
2
|
export declare const ASTUtils: {
|
|
4
3
|
isNodeOfOneOf<T extends AST_NODE_TYPES>(node: TSESTree.Node, types: readonly T[]): node is TSESTree.Node & {
|
|
5
4
|
type: T;
|
|
@@ -33,6 +32,6 @@ export declare const ASTUtils: {
|
|
|
33
32
|
node: TSESTree.Node;
|
|
34
33
|
context: Readonly<TSESLint.RuleContext<string, ReadonlyArray<unknown>>>;
|
|
35
34
|
}): TSESTree.Expression | null;
|
|
36
|
-
getClosestVariableDeclarator(node: TSESTree.Node): TSESTree.
|
|
35
|
+
getClosestVariableDeclarator(node: TSESTree.Node): TSESTree.VariableDeclaratorDefiniteAssignment | TSESTree.VariableDeclaratorMaybeInit | undefined;
|
|
37
36
|
getNestedReturnStatements(node: TSESTree.Node): Array<TSESTree.ReturnStatement>;
|
|
38
37
|
};
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { rules } from './rules.js';
|
|
2
2
|
import { ESLint, Linter } from 'eslint';
|
|
3
3
|
import { RuleModule } from '@typescript-eslint/utils/ts-eslint';
|
|
4
|
-
|
|
5
4
|
type RuleKey = keyof typeof rules;
|
|
6
5
|
interface Plugin extends Omit<ESLint.Plugin, 'rules'> {
|
|
7
6
|
rules: Record<RuleKey, RuleModule<any, any, any>>;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
2
|
import { ExtraRuleDocs } from '../../types';
|
|
3
|
-
|
|
4
3
|
export declare const name = "exhaustive-deps";
|
|
5
4
|
export declare const rule: ESLintUtils.RuleModule<string, readonly unknown[], ExtraRuleDocs, ESLintUtils.RuleListener>;
|
|
@@ -60,7 +60,6 @@ const rule = createRule({
|
|
|
60
60
|
queryKeyNode = expression;
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
|
-
const queryKeyValue = queryKeyNode;
|
|
64
63
|
const externalRefs = ASTUtils.getExternalRefs({
|
|
65
64
|
scopeManager,
|
|
66
65
|
sourceCode: context.sourceCode,
|
|
@@ -74,7 +73,7 @@ const rule = createRule({
|
|
|
74
73
|
node: getQueryFnRelevantNode(queryFn)
|
|
75
74
|
})
|
|
76
75
|
);
|
|
77
|
-
const existingKeys = ASTUtils.getNestedIdentifiers(
|
|
76
|
+
const existingKeys = ASTUtils.getNestedIdentifiers(queryKeyNode).map(
|
|
78
77
|
(identifier) => ASTUtils.mapKeyNodeToText(identifier, context.sourceCode)
|
|
79
78
|
);
|
|
80
79
|
const missingRefs = relevantRefs.map((ref) => ({
|
|
@@ -91,14 +90,15 @@ const rule = createRule({
|
|
|
91
90
|
const missingAsText = uniqueMissingRefs.map(
|
|
92
91
|
(ref) => ASTUtils.mapKeyNodeToText(ref.identifier, context.sourceCode)
|
|
93
92
|
).join(", ");
|
|
94
|
-
const
|
|
93
|
+
const queryKeyValue = context.sourceCode.getText(queryKeyNode);
|
|
94
|
+
const existingWithMissing = queryKeyValue === "[]" ? `[${missingAsText}]` : queryKeyValue.replace(/\]$/, `, ${missingAsText}]`);
|
|
95
95
|
const suggestions = [];
|
|
96
96
|
if (queryKeyNode.type === AST_NODE_TYPES.ArrayExpression) {
|
|
97
97
|
suggestions.push({
|
|
98
98
|
messageId: "fixTo",
|
|
99
99
|
data: { result: existingWithMissing },
|
|
100
100
|
fix(fixer) {
|
|
101
|
-
return fixer.replaceText(
|
|
101
|
+
return fixer.replaceText(queryKeyNode, existingWithMissing);
|
|
102
102
|
}
|
|
103
103
|
});
|
|
104
104
|
}
|
|
@@ -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, TSESTree } from '@typescript-eslint/utils'\nimport type { ExtraRuleDocs } from '../../types'\n\nconst QUERY_KEY = 'queryKey'\nconst QUERY_FN = 'queryFn'\n\nexport const name = 'exhaustive-deps'\n\nconst createRule = ESLintUtils.RuleCreator<ExtraRuleDocs>(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',\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 AST_NODE_TYPES.ConditionalExpression,\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
|
|
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, TSESTree } from '@typescript-eslint/utils'\nimport type { ExtraRuleDocs } from '../../types'\n\nconst QUERY_KEY = 'queryKey'\nconst QUERY_FN = 'queryFn'\n\nexport const name = 'exhaustive-deps'\n\nconst createRule = ESLintUtils.RuleCreator<ExtraRuleDocs>(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',\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 AST_NODE_TYPES.ConditionalExpression,\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 externalRefs = ASTUtils.getExternalRefs({\n scopeManager,\n sourceCode: context.sourceCode,\n node: getQueryFnRelevantNode(queryFn),\n })\n\n const relevantRefs = externalRefs.filter((reference) =>\n ExhaustiveDepsUtils.isRelevantReference({\n sourceCode: context.sourceCode,\n reference,\n scopeManager,\n node: getQueryFnRelevantNode(queryFn),\n }),\n )\n\n const existingKeys = ASTUtils.getNestedIdentifiers(queryKeyNode).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 queryKeyValue = context.sourceCode.getText(queryKeyNode)\n\n const existingWithMissing =\n queryKeyValue === '[]'\n ? `[${missingAsText}]`\n : queryKeyValue.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(queryKeyNode, 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\nfunction getQueryFnRelevantNode(queryFn: TSESTree.Property) {\n if (queryFn.value.type !== AST_NODE_TYPES.ConditionalExpression) {\n return queryFn.value\n }\n\n if (\n queryFn.value.consequent.type === AST_NODE_TYPES.Identifier &&\n queryFn.value.consequent.name === 'skipToken'\n ) {\n return queryFn.value.alternate\n }\n\n return queryFn.value.consequent\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
2
|
import { ExtraRuleDocs } from '../../types';
|
|
3
|
-
|
|
4
3
|
export declare const name = "no-rest-destructuring";
|
|
5
4
|
export declare const rule: ESLintUtils.RuleModule<string, readonly unknown[], ExtraRuleDocs, ESLintUtils.RuleListener>;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
2
|
import { ExtraRuleDocs } from '../../types';
|
|
3
|
-
|
|
4
3
|
export declare const name = "stable-query-client";
|
|
5
4
|
export declare const rule: ESLintUtils.RuleModule<string, readonly unknown[], ExtraRuleDocs, ESLintUtils.RuleListener>;
|
package/dist/esm/rules.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { AST_NODE_TYPES, TSESLint, TSESTree } from '@typescript-eslint/utils';
|
|
2
|
-
|
|
3
2
|
export declare const ASTUtils: {
|
|
4
3
|
isNodeOfOneOf<T extends AST_NODE_TYPES>(node: TSESTree.Node, types: readonly T[]): node is TSESTree.Node & {
|
|
5
4
|
type: T;
|
|
@@ -33,6 +32,6 @@ export declare const ASTUtils: {
|
|
|
33
32
|
node: TSESTree.Node;
|
|
34
33
|
context: Readonly<TSESLint.RuleContext<string, ReadonlyArray<unknown>>>;
|
|
35
34
|
}): TSESTree.Expression | null;
|
|
36
|
-
getClosestVariableDeclarator(node: TSESTree.Node): TSESTree.
|
|
35
|
+
getClosestVariableDeclarator(node: TSESTree.Node): TSESTree.VariableDeclaratorDefiniteAssignment | TSESTree.VariableDeclaratorMaybeInit | undefined;
|
|
37
36
|
getNestedReturnStatements(node: TSESTree.Node): Array<TSESTree.ReturnStatement>;
|
|
38
37
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/eslint-plugin-query",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.56.1",
|
|
4
4
|
"description": "ESLint plugin for TanStack Query",
|
|
5
5
|
"author": "Eliya Cohen",
|
|
6
6
|
"license": "MIT",
|
|
@@ -37,14 +37,14 @@
|
|
|
37
37
|
"src"
|
|
38
38
|
],
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@typescript-eslint/utils": "8.
|
|
40
|
+
"@typescript-eslint/utils": "^8.3.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"@typescript-eslint/rule-tester": "8.
|
|
44
|
-
"eslint": "^9.
|
|
43
|
+
"@typescript-eslint/rule-tester": "^8.3.0",
|
|
44
|
+
"eslint": "^9.9.1"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
|
-
"eslint": "^8 || ^9"
|
|
47
|
+
"eslint": "^8.57.0 || ^9.0.0"
|
|
48
48
|
},
|
|
49
49
|
"scripts": {}
|
|
50
50
|
}
|
|
@@ -2,10 +2,7 @@ import { RuleTester } from '@typescript-eslint/rule-tester'
|
|
|
2
2
|
import { rule } from '../rules/exhaustive-deps/exhaustive-deps.rule'
|
|
3
3
|
import { normalizeIndent } from './test-utils'
|
|
4
4
|
|
|
5
|
-
const ruleTester = new RuleTester(
|
|
6
|
-
parser: '@typescript-eslint/parser',
|
|
7
|
-
settings: {},
|
|
8
|
-
})
|
|
5
|
+
const ruleTester = new RuleTester()
|
|
9
6
|
|
|
10
7
|
ruleTester.run('exhaustive-deps', rule, {
|
|
11
8
|
valid: [
|
|
@@ -146,7 +143,7 @@ ruleTester.run('exhaustive-deps', rule, {
|
|
|
146
143
|
foo: () => ['foo'] as const,
|
|
147
144
|
num: (num: number) => [...fooQueryKeyFactory.foo(), num] as const,
|
|
148
145
|
}
|
|
149
|
-
|
|
146
|
+
|
|
150
147
|
const useFoo = (num: number) =>
|
|
151
148
|
useQuery({
|
|
152
149
|
queryKey: fooQueryKeyFactory.foo(num),
|
|
@@ -161,7 +158,7 @@ ruleTester.run('exhaustive-deps', rule, {
|
|
|
161
158
|
foo: () => ['foo'] as const,
|
|
162
159
|
num: (num: number) => [...fooQueryKeyFactory.foo(), num] as const,
|
|
163
160
|
}
|
|
164
|
-
|
|
161
|
+
|
|
165
162
|
const useFoo = (num: number) =>
|
|
166
163
|
useQuery({
|
|
167
164
|
queryKey: fooQueryKeyFactory.foo({ x: num }),
|
|
@@ -176,7 +173,7 @@ ruleTester.run('exhaustive-deps', rule, {
|
|
|
176
173
|
foo: () => ['foo'] as const,
|
|
177
174
|
num: (num: number) => [...fooQueryKeyFactory.foo(), num] as const,
|
|
178
175
|
}
|
|
179
|
-
|
|
176
|
+
|
|
180
177
|
const useFoo = (num: number) =>
|
|
181
178
|
useQuery({
|
|
182
179
|
queryKey: fooQueryKeyFactory.foo({ num }),
|
|
@@ -191,7 +188,7 @@ ruleTester.run('exhaustive-deps', rule, {
|
|
|
191
188
|
foo: () => ['foo'] as const,
|
|
192
189
|
num: (num: number) => [...fooQueryKeyFactory.foo(), num] as const,
|
|
193
190
|
}
|
|
194
|
-
|
|
191
|
+
|
|
195
192
|
const useFoo = (num: number) =>
|
|
196
193
|
useQuery({
|
|
197
194
|
queryKey: fooQueryKeyFactory.foo([num]),
|
|
@@ -206,7 +203,7 @@ ruleTester.run('exhaustive-deps', rule, {
|
|
|
206
203
|
foo: () => ['foo'] as const,
|
|
207
204
|
num: (num: number) => [...fooQueryKeyFactory.foo(), num] as const,
|
|
208
205
|
}
|
|
209
|
-
|
|
206
|
+
|
|
210
207
|
const useFoo = (num: number) =>
|
|
211
208
|
useQuery({
|
|
212
209
|
queryKey: fooQueryKeyFactory.foo(1, num),
|
|
@@ -221,7 +218,7 @@ ruleTester.run('exhaustive-deps', rule, {
|
|
|
221
218
|
foo: () => ['foo'] as const,
|
|
222
219
|
num: (num: number) => [...fooQueryKeyFactory.foo(), num] as const,
|
|
223
220
|
}
|
|
224
|
-
|
|
221
|
+
|
|
225
222
|
const useFoo = (obj: { num: number }) =>
|
|
226
223
|
useQuery({
|
|
227
224
|
queryKey: fooQueryKeyFactory.foo(obj.num),
|
|
@@ -253,7 +250,7 @@ ruleTester.run('exhaustive-deps', rule, {
|
|
|
253
250
|
name: 'should not fail if queryKey is having the whole object while queryFn uses some props of it',
|
|
254
251
|
code: normalizeIndent`
|
|
255
252
|
const state = { foo: 'foo', bar: 'bar' }
|
|
256
|
-
|
|
253
|
+
|
|
257
254
|
useQuery({
|
|
258
255
|
queryKey: ['state', state],
|
|
259
256
|
queryFn: () => Promise.resolve({ foo: state.foo, bar: state.bar })
|
|
@@ -407,7 +404,7 @@ ruleTester.run('exhaustive-deps', rule, {
|
|
|
407
404
|
code: normalizeIndent`
|
|
408
405
|
import { useQuery, skipToken } from "@tanstack/react-query";
|
|
409
406
|
const fetch = true
|
|
410
|
-
|
|
407
|
+
|
|
411
408
|
function Component({ id }) {
|
|
412
409
|
useQuery({
|
|
413
410
|
queryKey: [id],
|
|
@@ -715,7 +712,7 @@ ruleTester.run('exhaustive-deps', rule, {
|
|
|
715
712
|
name: 'should fail if queryFn is using multiple object props when only one of them is in the queryKey',
|
|
716
713
|
code: normalizeIndent`
|
|
717
714
|
const state = { foo: 'foo', bar: 'bar' }
|
|
718
|
-
|
|
715
|
+
|
|
719
716
|
useQuery({
|
|
720
717
|
queryKey: ['state', state.foo],
|
|
721
718
|
queryFn: () => Promise.resolve({ foo: state.foo, bar: state.bar })
|
|
@@ -723,6 +720,19 @@ ruleTester.run('exhaustive-deps', rule, {
|
|
|
723
720
|
`,
|
|
724
721
|
errors: [
|
|
725
722
|
{
|
|
723
|
+
suggestions: [
|
|
724
|
+
{
|
|
725
|
+
messageId: 'fixTo',
|
|
726
|
+
output: normalizeIndent`
|
|
727
|
+
const state = { foo: 'foo', bar: 'bar' }
|
|
728
|
+
|
|
729
|
+
useQuery({
|
|
730
|
+
queryKey: ['state', state.foo, state.bar],
|
|
731
|
+
queryFn: () => Promise.resolve({ foo: state.foo, bar: state.bar })
|
|
732
|
+
})
|
|
733
|
+
`,
|
|
734
|
+
},
|
|
735
|
+
],
|
|
726
736
|
messageId: 'missingDeps',
|
|
727
737
|
data: { deps: 'state.bar' },
|
|
728
738
|
},
|
|
@@ -732,7 +742,7 @@ ruleTester.run('exhaustive-deps', rule, {
|
|
|
732
742
|
name: 'should fail if queryFn is invalid while using FunctionExpression syntax',
|
|
733
743
|
code: normalizeIndent`
|
|
734
744
|
const id = 1;
|
|
735
|
-
|
|
745
|
+
|
|
736
746
|
useQuery({
|
|
737
747
|
queryKey: [],
|
|
738
748
|
queryFn() {
|
|
@@ -742,6 +752,21 @@ ruleTester.run('exhaustive-deps', rule, {
|
|
|
742
752
|
`,
|
|
743
753
|
errors: [
|
|
744
754
|
{
|
|
755
|
+
suggestions: [
|
|
756
|
+
{
|
|
757
|
+
messageId: 'fixTo',
|
|
758
|
+
output: normalizeIndent`
|
|
759
|
+
const id = 1;
|
|
760
|
+
|
|
761
|
+
useQuery({
|
|
762
|
+
queryKey: [id],
|
|
763
|
+
queryFn() {
|
|
764
|
+
Promise.resolve(id)
|
|
765
|
+
}
|
|
766
|
+
})
|
|
767
|
+
`,
|
|
768
|
+
},
|
|
769
|
+
],
|
|
745
770
|
messageId: 'missingDeps',
|
|
746
771
|
data: { deps: 'id' },
|
|
747
772
|
},
|
|
@@ -752,7 +777,7 @@ ruleTester.run('exhaustive-deps', rule, {
|
|
|
752
777
|
code: normalizeIndent`
|
|
753
778
|
import { useQuery, skipToken } from "@tanstack/react-query";
|
|
754
779
|
const fetch = true
|
|
755
|
-
|
|
780
|
+
|
|
756
781
|
function Component({ id }) {
|
|
757
782
|
useQuery({
|
|
758
783
|
queryKey: [],
|
|
@@ -762,6 +787,22 @@ ruleTester.run('exhaustive-deps', rule, {
|
|
|
762
787
|
`,
|
|
763
788
|
errors: [
|
|
764
789
|
{
|
|
790
|
+
suggestions: [
|
|
791
|
+
{
|
|
792
|
+
messageId: 'fixTo',
|
|
793
|
+
output: normalizeIndent`
|
|
794
|
+
import { useQuery, skipToken } from "@tanstack/react-query";
|
|
795
|
+
const fetch = true
|
|
796
|
+
|
|
797
|
+
function Component({ id }) {
|
|
798
|
+
useQuery({
|
|
799
|
+
queryKey: [id],
|
|
800
|
+
queryFn: fetch ? () => Promise.resolve(id) : skipToken
|
|
801
|
+
})
|
|
802
|
+
}
|
|
803
|
+
`,
|
|
804
|
+
},
|
|
805
|
+
],
|
|
765
806
|
messageId: 'missingDeps',
|
|
766
807
|
data: { deps: 'id' },
|
|
767
808
|
},
|
|
@@ -2,10 +2,7 @@ import { RuleTester } from '@typescript-eslint/rule-tester'
|
|
|
2
2
|
import { rule } from '../rules/no-rest-destructuring/no-rest-destructuring.rule'
|
|
3
3
|
import { normalizeIndent } from './test-utils'
|
|
4
4
|
|
|
5
|
-
const ruleTester = new RuleTester(
|
|
6
|
-
parser: '@typescript-eslint/parser',
|
|
7
|
-
settings: {},
|
|
8
|
-
})
|
|
5
|
+
const ruleTester = new RuleTester()
|
|
9
6
|
|
|
10
7
|
ruleTester.run('no-rest-destructuring', rule, {
|
|
11
8
|
valid: [
|
|
@@ -5,10 +5,7 @@ import {
|
|
|
5
5
|
useQueryHookNames,
|
|
6
6
|
} from '../rules/no-unstable-deps/no-unstable-deps.rule'
|
|
7
7
|
|
|
8
|
-
const ruleTester = new RuleTester(
|
|
9
|
-
parser: '@typescript-eslint/parser',
|
|
10
|
-
settings: {},
|
|
11
|
-
})
|
|
8
|
+
const ruleTester = new RuleTester()
|
|
12
9
|
|
|
13
10
|
interface TestCase {
|
|
14
11
|
reactHookImport: string
|
|
@@ -37,7 +34,7 @@ const baseTestCases = {
|
|
|
37
34
|
code: `
|
|
38
35
|
${reactHookImport}
|
|
39
36
|
import { ${queryHook} } from "@tanstack/react-query";
|
|
40
|
-
|
|
37
|
+
|
|
41
38
|
function Component() {
|
|
42
39
|
const { refetch } = ${queryHook}({ queryFn: (value: string) => value });
|
|
43
40
|
const callback = ${reactHookInvocation}(() => { query.refetch() }, [refetch]);
|
|
@@ -57,7 +54,7 @@ const baseTestCases = {
|
|
|
57
54
|
code: `
|
|
58
55
|
${reactHookImport}
|
|
59
56
|
import { useMutation } from "@tanstack/react-query";
|
|
60
|
-
|
|
57
|
+
|
|
61
58
|
function Component() {
|
|
62
59
|
const mutation = useMutation({ mutationFn: (value: string) => value });
|
|
63
60
|
const callback = ${reactHookInvocation}(() => { mutation.mutate('hello') }, [mutation]);
|
|
@@ -77,7 +74,7 @@ const baseTestCases = {
|
|
|
77
74
|
code: `
|
|
78
75
|
${reactHookImport}
|
|
79
76
|
import { ${queryHook} } from "@tanstack/react-query";
|
|
80
|
-
|
|
77
|
+
|
|
81
78
|
function Component() {
|
|
82
79
|
const query = ${queryHook}({ queryFn: (value: string) => value });
|
|
83
80
|
const callback = ${reactHookInvocation}(() => { query.refetch() }, [query]);
|
|
@@ -2,10 +2,7 @@ import { RuleTester } from '@typescript-eslint/rule-tester'
|
|
|
2
2
|
import { rule } from '../rules/stable-query-client/stable-query-client.rule'
|
|
3
3
|
import { normalizeIndent } from './test-utils'
|
|
4
4
|
|
|
5
|
-
const ruleTester = new RuleTester(
|
|
6
|
-
parser: '@typescript-eslint/parser',
|
|
7
|
-
settings: {},
|
|
8
|
-
})
|
|
5
|
+
const ruleTester = new RuleTester()
|
|
9
6
|
|
|
10
7
|
ruleTester.run('stable-query-client', rule, {
|
|
11
8
|
valid: [
|
|
@@ -35,7 +32,7 @@ ruleTester.run('stable-query-client', rule, {
|
|
|
35
32
|
name: 'QueryClient is stable when wrapped in React.useMemo',
|
|
36
33
|
code: normalizeIndent`
|
|
37
34
|
import { QueryClient } from "@tanstack/react-query";
|
|
38
|
-
|
|
35
|
+
|
|
39
36
|
function Component() {
|
|
40
37
|
const [queryClient] = React.useMemo(() => new QueryClient(), []);
|
|
41
38
|
return;
|
|
@@ -46,7 +43,7 @@ ruleTester.run('stable-query-client', rule, {
|
|
|
46
43
|
name: 'QueryClient is stable when wrapped in useAnything',
|
|
47
44
|
code: normalizeIndent`
|
|
48
45
|
import { QueryClient } from "@tanstack/react-query";
|
|
49
|
-
|
|
46
|
+
|
|
50
47
|
function Component() {
|
|
51
48
|
const [queryClient] = useAnything(() => new QueryClient());
|
|
52
49
|
return;
|
|
@@ -85,7 +85,6 @@ export const rule = createRule({
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
const queryKeyValue = queryKeyNode
|
|
89
88
|
const externalRefs = ASTUtils.getExternalRefs({
|
|
90
89
|
scopeManager,
|
|
91
90
|
sourceCode: context.sourceCode,
|
|
@@ -101,7 +100,7 @@ export const rule = createRule({
|
|
|
101
100
|
}),
|
|
102
101
|
)
|
|
103
102
|
|
|
104
|
-
const existingKeys = ASTUtils.getNestedIdentifiers(
|
|
103
|
+
const existingKeys = ASTUtils.getNestedIdentifiers(queryKeyNode).map(
|
|
105
104
|
(identifier) =>
|
|
106
105
|
ASTUtils.mapKeyNodeToText(identifier, context.sourceCode),
|
|
107
106
|
)
|
|
@@ -133,9 +132,12 @@ export const rule = createRule({
|
|
|
133
132
|
)
|
|
134
133
|
.join(', ')
|
|
135
134
|
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
135
|
+
const queryKeyValue = context.sourceCode.getText(queryKeyNode)
|
|
136
|
+
|
|
137
|
+
const existingWithMissing =
|
|
138
|
+
queryKeyValue === '[]'
|
|
139
|
+
? `[${missingAsText}]`
|
|
140
|
+
: queryKeyValue.replace(/\]$/, `, ${missingAsText}]`)
|
|
139
141
|
|
|
140
142
|
const suggestions: TSESLint.ReportSuggestionArray<string> = []
|
|
141
143
|
|
|
@@ -144,7 +146,7 @@ export const rule = createRule({
|
|
|
144
146
|
messageId: 'fixTo',
|
|
145
147
|
data: { result: existingWithMissing },
|
|
146
148
|
fix(fixer) {
|
|
147
|
-
return fixer.replaceText(
|
|
149
|
+
return fixer.replaceText(queryKeyNode, existingWithMissing)
|
|
148
150
|
},
|
|
149
151
|
})
|
|
150
152
|
}
|