@tanstack/eslint-plugin-query 5.94.5 → 5.95.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/legacy/_tsup-dts-rollup.d.cts +57 -3
- package/build/legacy/_tsup-dts-rollup.d.ts +57 -3
- package/build/legacy/{chunk-HD2KSEKJ.js → chunk-T44AUBX4.js} +289 -90
- package/build/legacy/chunk-T44AUBX4.js.map +1 -0
- package/build/legacy/index.cjs +288 -89
- package/build/legacy/index.cjs.map +1 -1
- package/build/legacy/index.js +1 -1
- package/build/legacy/rules.cjs +288 -89
- package/build/legacy/rules.cjs.map +1 -1
- package/build/legacy/rules.js +1 -1
- package/build/modern/_tsup-dts-rollup.d.cts +57 -3
- package/build/modern/_tsup-dts-rollup.d.ts +57 -3
- package/build/modern/{chunk-XO2SGL7P.js → chunk-XH4ABCYA.js} +288 -90
- package/build/modern/chunk-XH4ABCYA.js.map +1 -0
- package/build/modern/index.cjs +287 -89
- package/build/modern/index.cjs.map +1 -1
- package/build/modern/index.js +1 -1
- package/build/modern/rules.cjs +287 -89
- package/build/modern/rules.cjs.map +1 -1
- package/build/modern/rules.js +1 -1
- package/package.json +1 -1
- package/src/rules/exhaustive-deps/exhaustive-deps.rule.ts +136 -90
- package/src/rules/exhaustive-deps/exhaustive-deps.utils.ts +248 -22
- package/src/utils/ast-utils.ts +2 -25
- package/build/legacy/chunk-HD2KSEKJ.js.map +0 -1
- package/build/modern/chunk-XO2SGL7P.js.map +0 -1
|
@@ -22,7 +22,6 @@ export declare const ASTUtils: {
|
|
|
22
22
|
isPropertyWithIdentifierKey(node: TSESTree.Node, key: string): node is TSESTree.Property;
|
|
23
23
|
findPropertyWithIdentifierKey(properties: Array<TSESTree.ObjectLiteralElement>, key: string): TSESTree.Property | undefined;
|
|
24
24
|
getNestedIdentifiers(node: TSESTree.Node): Array<TSESTree.Identifier>;
|
|
25
|
-
isAncestorIsCallee(identifier: TSESTree.Node): boolean;
|
|
26
25
|
traverseUpOnly(identifier: TSESTree.Node, allowedNodeTypes: Array<AST_NODE_TYPES>): TSESTree.Node;
|
|
27
26
|
isDeclaredInNode(params: {
|
|
28
27
|
functionNode: TSESTree.Node;
|
|
@@ -86,17 +85,72 @@ export declare const ExhaustiveDepsUtils: {
|
|
|
86
85
|
node: TSESTree.Node;
|
|
87
86
|
filename: string;
|
|
88
87
|
}): boolean;
|
|
89
|
-
|
|
88
|
+
/**
|
|
89
|
+
* Given required refs and existing queryKey entries, compute missing dependency paths
|
|
90
|
+
* respecting allowlisted variables and types.
|
|
91
|
+
*/
|
|
92
|
+
computeFilteredMissingPaths(params: {
|
|
93
|
+
requiredRefs: Array<{
|
|
94
|
+
path: string;
|
|
95
|
+
root: string;
|
|
96
|
+
allowlistedByType: boolean;
|
|
97
|
+
}>;
|
|
98
|
+
allowlistedVariables: Set<string>;
|
|
99
|
+
existingRootIdentifiers: Set<string>;
|
|
100
|
+
existingFullPaths: Set<string>;
|
|
101
|
+
}): Array<string>;
|
|
102
|
+
/**
|
|
103
|
+
* Extract existing queryKey deps as root identifiers and full member paths.
|
|
104
|
+
*/
|
|
90
105
|
collectQueryKeyDeps(params: {
|
|
91
106
|
sourceCode: Readonly<TSESLint.SourceCode>;
|
|
92
107
|
scopeManager: TSESLint.Scope.ScopeManager;
|
|
93
108
|
queryKeyNode: TSESTree.Node;
|
|
94
|
-
}):
|
|
109
|
+
}): {
|
|
110
|
+
roots: Set<string>;
|
|
111
|
+
paths: Set<string>;
|
|
112
|
+
};
|
|
95
113
|
isNode(value: unknown): value is TSESTree.Node;
|
|
114
|
+
/**
|
|
115
|
+
* Checks whether the resolved variable is allowlisted by its type annotation
|
|
116
|
+
*/
|
|
117
|
+
variableIsAllowlistedByType(params: {
|
|
118
|
+
allowlistedTypes: Set<string>;
|
|
119
|
+
variable: TSESLint.Scope.Variable | null;
|
|
120
|
+
}): boolean;
|
|
121
|
+
isInstanceOfKind(node: TSESTree.Node): boolean;
|
|
122
|
+
/**
|
|
123
|
+
* Normalizes a chain by removing optional chaining operators
|
|
124
|
+
*
|
|
125
|
+
* Example: `a?.b.c!` -> `a.b.c`
|
|
126
|
+
*/
|
|
127
|
+
normalizeChain(text: string): string;
|
|
128
|
+
/**
|
|
129
|
+
* Computes the reference path for an identifier
|
|
130
|
+
*
|
|
131
|
+
* Example: `a.b.c!` -> `{ path: 'a.b.c', root: 'a' }`
|
|
132
|
+
*/
|
|
133
|
+
computeRefPath(params: {
|
|
134
|
+
identifier: TSESTree.Identifier;
|
|
135
|
+
sourceCode: Readonly<TSESLint.SourceCode>;
|
|
136
|
+
}): {
|
|
137
|
+
path: string;
|
|
138
|
+
root: string;
|
|
139
|
+
coversRootMembers: boolean;
|
|
140
|
+
} | null;
|
|
96
141
|
collectExternalRefsInFunction(params: {
|
|
97
142
|
functionNode: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression;
|
|
98
143
|
scopeManager: TSESLint.Scope.ScopeManager;
|
|
99
144
|
}): Array<TSESLint.Scope.Reference>;
|
|
145
|
+
/**
|
|
146
|
+
* Recursively collects type identifiers from a type annotation
|
|
147
|
+
*/
|
|
148
|
+
collectTypeIdentifiers(typeNode: TSESTree.TypeNode, out: Set<string>): void;
|
|
149
|
+
/**
|
|
150
|
+
* Gets the function expression nodes from a queryFn property, handling conditional expressions.
|
|
151
|
+
* When neither branch is skipToken, returns both branches so all deps are scanned.
|
|
152
|
+
*/
|
|
153
|
+
getQueryFnNodes(queryFn: TSESTree.Property): Array<TSESTree.Node>;
|
|
100
154
|
};
|
|
101
155
|
|
|
102
156
|
export declare function expectArrayEqualIgnoreOrder<T>(a: Array<T>, b: Array<T>): void;
|
|
@@ -22,7 +22,6 @@ export declare const ASTUtils: {
|
|
|
22
22
|
isPropertyWithIdentifierKey(node: TSESTree.Node, key: string): node is TSESTree.Property;
|
|
23
23
|
findPropertyWithIdentifierKey(properties: Array<TSESTree.ObjectLiteralElement>, key: string): TSESTree.Property | undefined;
|
|
24
24
|
getNestedIdentifiers(node: TSESTree.Node): Array<TSESTree.Identifier>;
|
|
25
|
-
isAncestorIsCallee(identifier: TSESTree.Node): boolean;
|
|
26
25
|
traverseUpOnly(identifier: TSESTree.Node, allowedNodeTypes: Array<AST_NODE_TYPES>): TSESTree.Node;
|
|
27
26
|
isDeclaredInNode(params: {
|
|
28
27
|
functionNode: TSESTree.Node;
|
|
@@ -86,17 +85,72 @@ export declare const ExhaustiveDepsUtils: {
|
|
|
86
85
|
node: TSESTree.Node;
|
|
87
86
|
filename: string;
|
|
88
87
|
}): boolean;
|
|
89
|
-
|
|
88
|
+
/**
|
|
89
|
+
* Given required refs and existing queryKey entries, compute missing dependency paths
|
|
90
|
+
* respecting allowlisted variables and types.
|
|
91
|
+
*/
|
|
92
|
+
computeFilteredMissingPaths(params: {
|
|
93
|
+
requiredRefs: Array<{
|
|
94
|
+
path: string;
|
|
95
|
+
root: string;
|
|
96
|
+
allowlistedByType: boolean;
|
|
97
|
+
}>;
|
|
98
|
+
allowlistedVariables: Set<string>;
|
|
99
|
+
existingRootIdentifiers: Set<string>;
|
|
100
|
+
existingFullPaths: Set<string>;
|
|
101
|
+
}): Array<string>;
|
|
102
|
+
/**
|
|
103
|
+
* Extract existing queryKey deps as root identifiers and full member paths.
|
|
104
|
+
*/
|
|
90
105
|
collectQueryKeyDeps(params: {
|
|
91
106
|
sourceCode: Readonly<TSESLint.SourceCode>;
|
|
92
107
|
scopeManager: TSESLint.Scope.ScopeManager;
|
|
93
108
|
queryKeyNode: TSESTree.Node;
|
|
94
|
-
}):
|
|
109
|
+
}): {
|
|
110
|
+
roots: Set<string>;
|
|
111
|
+
paths: Set<string>;
|
|
112
|
+
};
|
|
95
113
|
isNode(value: unknown): value is TSESTree.Node;
|
|
114
|
+
/**
|
|
115
|
+
* Checks whether the resolved variable is allowlisted by its type annotation
|
|
116
|
+
*/
|
|
117
|
+
variableIsAllowlistedByType(params: {
|
|
118
|
+
allowlistedTypes: Set<string>;
|
|
119
|
+
variable: TSESLint.Scope.Variable | null;
|
|
120
|
+
}): boolean;
|
|
121
|
+
isInstanceOfKind(node: TSESTree.Node): boolean;
|
|
122
|
+
/**
|
|
123
|
+
* Normalizes a chain by removing optional chaining operators
|
|
124
|
+
*
|
|
125
|
+
* Example: `a?.b.c!` -> `a.b.c`
|
|
126
|
+
*/
|
|
127
|
+
normalizeChain(text: string): string;
|
|
128
|
+
/**
|
|
129
|
+
* Computes the reference path for an identifier
|
|
130
|
+
*
|
|
131
|
+
* Example: `a.b.c!` -> `{ path: 'a.b.c', root: 'a' }`
|
|
132
|
+
*/
|
|
133
|
+
computeRefPath(params: {
|
|
134
|
+
identifier: TSESTree.Identifier;
|
|
135
|
+
sourceCode: Readonly<TSESLint.SourceCode>;
|
|
136
|
+
}): {
|
|
137
|
+
path: string;
|
|
138
|
+
root: string;
|
|
139
|
+
coversRootMembers: boolean;
|
|
140
|
+
} | null;
|
|
96
141
|
collectExternalRefsInFunction(params: {
|
|
97
142
|
functionNode: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression;
|
|
98
143
|
scopeManager: TSESLint.Scope.ScopeManager;
|
|
99
144
|
}): Array<TSESLint.Scope.Reference>;
|
|
145
|
+
/**
|
|
146
|
+
* Recursively collects type identifiers from a type annotation
|
|
147
|
+
*/
|
|
148
|
+
collectTypeIdentifiers(typeNode: TSESTree.TypeNode, out: Set<string>): void;
|
|
149
|
+
/**
|
|
150
|
+
* Gets the function expression nodes from a queryFn property, handling conditional expressions.
|
|
151
|
+
* When neither branch is skipToken, returns both branches so all deps are scanned.
|
|
152
|
+
*/
|
|
153
|
+
getQueryFnNodes(queryFn: TSESTree.Property): Array<TSESTree.Node>;
|
|
100
154
|
};
|
|
101
155
|
|
|
102
156
|
export declare function expectArrayEqualIgnoreOrder<T>(a: Array<T>, b: Array<T>): void;
|
|
@@ -104,21 +104,6 @@ var ASTUtils = {
|
|
|
104
104
|
}
|
|
105
105
|
return identifiers;
|
|
106
106
|
},
|
|
107
|
-
isAncestorIsCallee(identifier) {
|
|
108
|
-
let previousNode = identifier;
|
|
109
|
-
let currentNode = identifier.parent;
|
|
110
|
-
while (currentNode !== void 0) {
|
|
111
|
-
if (currentNode.type === AST_NODE_TYPES.CallExpression && currentNode.callee === previousNode) {
|
|
112
|
-
return true;
|
|
113
|
-
}
|
|
114
|
-
if (currentNode.type !== AST_NODE_TYPES.MemberExpression) {
|
|
115
|
-
return false;
|
|
116
|
-
}
|
|
117
|
-
previousNode = currentNode;
|
|
118
|
-
currentNode = currentNode.parent;
|
|
119
|
-
}
|
|
120
|
-
return false;
|
|
121
|
-
},
|
|
122
107
|
traverseUpOnly(identifier, allowedNodeTypes) {
|
|
123
108
|
const parent = identifier.parent;
|
|
124
109
|
if (parent !== void 0 && allowedNodeTypes.includes(parent.type)) {
|
|
@@ -343,7 +328,7 @@ var ExhaustiveDepsUtils = {
|
|
|
343
328
|
const { sourceCode, reference, scopeManager, node, filename } = params;
|
|
344
329
|
const component = ASTUtils.getFunctionAncestor(sourceCode, node);
|
|
345
330
|
const queryFnScope = scopeManager.acquire(node);
|
|
346
|
-
if (queryFnScope === null) {
|
|
331
|
+
if (queryFnScope === null || reference.isValueReference === false) {
|
|
347
332
|
return false;
|
|
348
333
|
}
|
|
349
334
|
let currentScope = ((_a = reference.resolved) == null ? void 0 : _a.scope) ?? null;
|
|
@@ -375,15 +360,57 @@ var ExhaustiveDepsUtils = {
|
|
|
375
360
|
}
|
|
376
361
|
return reference.identifier.name !== "undefined" && reference.identifier.parent.type !== AST_NODE_TYPES2.NewExpression && !ExhaustiveDepsUtils.isInstanceOfKind(reference.identifier.parent);
|
|
377
362
|
},
|
|
378
|
-
|
|
379
|
-
|
|
363
|
+
/**
|
|
364
|
+
* Given required refs and existing queryKey entries, compute missing dependency paths
|
|
365
|
+
* respecting allowlisted variables and types.
|
|
366
|
+
*/
|
|
367
|
+
computeFilteredMissingPaths(params) {
|
|
368
|
+
const {
|
|
369
|
+
requiredRefs,
|
|
370
|
+
allowlistedVariables,
|
|
371
|
+
existingRootIdentifiers,
|
|
372
|
+
existingFullPaths
|
|
373
|
+
} = params;
|
|
374
|
+
const missingPaths = /* @__PURE__ */ new Set();
|
|
375
|
+
for (const { root, path, allowlistedByType } of requiredRefs) {
|
|
376
|
+
if (existingRootIdentifiers.has(root)) continue;
|
|
377
|
+
if (allowlistedVariables.has(root)) continue;
|
|
378
|
+
if (existingFullPaths.has(path)) continue;
|
|
379
|
+
if (allowlistedByType) continue;
|
|
380
|
+
missingPaths.add(path);
|
|
381
|
+
}
|
|
382
|
+
for (const path of missingPaths) {
|
|
383
|
+
const root = path.split(".")[0];
|
|
384
|
+
if (root !== path && root !== void 0 && missingPaths.has(root)) {
|
|
385
|
+
missingPaths.delete(path);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return Array.from(missingPaths);
|
|
380
389
|
},
|
|
390
|
+
/**
|
|
391
|
+
* Extract existing queryKey deps as root identifiers and full member paths.
|
|
392
|
+
*/
|
|
381
393
|
collectQueryKeyDeps(params) {
|
|
382
394
|
const { sourceCode, scopeManager, queryKeyNode } = params;
|
|
383
|
-
const
|
|
395
|
+
const roots = /* @__PURE__ */ new Set();
|
|
396
|
+
const paths = /* @__PURE__ */ new Set();
|
|
384
397
|
const visitorKeys = sourceCode.visitorKeys;
|
|
385
|
-
function
|
|
386
|
-
|
|
398
|
+
function addRoot(name8) {
|
|
399
|
+
const cleaned = ExhaustiveDepsUtils.normalizeChain(name8);
|
|
400
|
+
roots.add(cleaned);
|
|
401
|
+
paths.add(cleaned);
|
|
402
|
+
}
|
|
403
|
+
function addFull(text) {
|
|
404
|
+
const cleaned = ExhaustiveDepsUtils.normalizeChain(text);
|
|
405
|
+
paths.add(cleaned);
|
|
406
|
+
}
|
|
407
|
+
function addRefPath(refPath) {
|
|
408
|
+
if (!refPath) return;
|
|
409
|
+
if (refPath.coversRootMembers) {
|
|
410
|
+
addRoot(refPath.root);
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
addFull(refPath.path);
|
|
387
414
|
}
|
|
388
415
|
function visitChildren(node) {
|
|
389
416
|
const keys = visitorKeys[node.type] ?? [];
|
|
@@ -405,9 +432,15 @@ var ExhaustiveDepsUtils = {
|
|
|
405
432
|
function visit(node) {
|
|
406
433
|
if (!node) return;
|
|
407
434
|
switch (node.type) {
|
|
408
|
-
case AST_NODE_TYPES2.Identifier:
|
|
409
|
-
|
|
435
|
+
case AST_NODE_TYPES2.Identifier: {
|
|
436
|
+
addRefPath(
|
|
437
|
+
ExhaustiveDepsUtils.computeRefPath({
|
|
438
|
+
identifier: node,
|
|
439
|
+
sourceCode
|
|
440
|
+
})
|
|
441
|
+
);
|
|
410
442
|
return;
|
|
443
|
+
}
|
|
411
444
|
case AST_NODE_TYPES2.ArrowFunctionExpression:
|
|
412
445
|
case AST_NODE_TYPES2.FunctionExpression:
|
|
413
446
|
for (const reference of ExhaustiveDepsUtils.collectExternalRefsInFunction(
|
|
@@ -416,9 +449,15 @@ var ExhaustiveDepsUtils = {
|
|
|
416
449
|
scopeManager
|
|
417
450
|
}
|
|
418
451
|
)) {
|
|
419
|
-
if (reference.identifier.type
|
|
420
|
-
|
|
452
|
+
if (reference.identifier.type !== AST_NODE_TYPES2.Identifier) {
|
|
453
|
+
continue;
|
|
421
454
|
}
|
|
455
|
+
addRefPath(
|
|
456
|
+
ExhaustiveDepsUtils.computeRefPath({
|
|
457
|
+
identifier: reference.identifier,
|
|
458
|
+
sourceCode
|
|
459
|
+
})
|
|
460
|
+
);
|
|
422
461
|
}
|
|
423
462
|
return;
|
|
424
463
|
case AST_NODE_TYPES2.Property:
|
|
@@ -426,26 +465,96 @@ var ExhaustiveDepsUtils = {
|
|
|
426
465
|
return;
|
|
427
466
|
case AST_NODE_TYPES2.MemberExpression:
|
|
428
467
|
if (node.parent.type === AST_NODE_TYPES2.CallExpression && node.parent.callee === node && node.object.type === AST_NODE_TYPES2.Identifier) {
|
|
429
|
-
|
|
468
|
+
addRoot(node.object.name);
|
|
430
469
|
} else {
|
|
431
470
|
visit(node.object);
|
|
432
471
|
}
|
|
433
472
|
return;
|
|
434
473
|
case AST_NODE_TYPES2.CallExpression:
|
|
435
474
|
node.arguments.forEach((argument) => visit(argument));
|
|
436
|
-
|
|
437
|
-
|
|
475
|
+
switch (node.callee.type) {
|
|
476
|
+
case AST_NODE_TYPES2.Identifier:
|
|
477
|
+
case AST_NODE_TYPES2.MemberExpression:
|
|
478
|
+
case AST_NODE_TYPES2.ChainExpression:
|
|
479
|
+
case AST_NODE_TYPES2.TSNonNullExpression:
|
|
480
|
+
visit(node.callee);
|
|
481
|
+
break;
|
|
438
482
|
}
|
|
439
483
|
return;
|
|
440
484
|
}
|
|
441
485
|
visitChildren(node);
|
|
442
486
|
}
|
|
443
487
|
visit(queryKeyNode);
|
|
444
|
-
return
|
|
488
|
+
return { roots, paths };
|
|
445
489
|
},
|
|
446
490
|
isNode(value) {
|
|
447
491
|
return typeof value === "object" && value !== null && "type" in value && typeof value.type === "string";
|
|
448
492
|
},
|
|
493
|
+
/**
|
|
494
|
+
* Checks whether the resolved variable is allowlisted by its type annotation
|
|
495
|
+
*/
|
|
496
|
+
variableIsAllowlistedByType(params) {
|
|
497
|
+
const { allowlistedTypes, variable } = params;
|
|
498
|
+
if (allowlistedTypes.size === 0) return false;
|
|
499
|
+
if (!variable) return false;
|
|
500
|
+
for (const id of variable.identifiers) {
|
|
501
|
+
if (id.typeAnnotation) {
|
|
502
|
+
const typeIdentifiers = /* @__PURE__ */ new Set();
|
|
503
|
+
ExhaustiveDepsUtils.collectTypeIdentifiers(
|
|
504
|
+
id.typeAnnotation.typeAnnotation,
|
|
505
|
+
typeIdentifiers
|
|
506
|
+
);
|
|
507
|
+
for (const typeIdentifier of typeIdentifiers) {
|
|
508
|
+
if (allowlistedTypes.has(typeIdentifier)) return true;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
return false;
|
|
513
|
+
},
|
|
514
|
+
isInstanceOfKind(node) {
|
|
515
|
+
return node.type === AST_NODE_TYPES2.BinaryExpression && node.operator === "instanceof";
|
|
516
|
+
},
|
|
517
|
+
/**
|
|
518
|
+
* Normalizes a chain by removing optional chaining operators
|
|
519
|
+
*
|
|
520
|
+
* Example: `a?.b.c!` -> `a.b.c`
|
|
521
|
+
*/
|
|
522
|
+
normalizeChain(text) {
|
|
523
|
+
return text.replace(/(?:\?(\.)|!)/g, "$1");
|
|
524
|
+
},
|
|
525
|
+
/**
|
|
526
|
+
* Computes the reference path for an identifier
|
|
527
|
+
*
|
|
528
|
+
* Example: `a.b.c!` -> `{ path: 'a.b.c', root: 'a' }`
|
|
529
|
+
*/
|
|
530
|
+
computeRefPath(params) {
|
|
531
|
+
const { identifier, sourceCode } = params;
|
|
532
|
+
const fullChainNode = ASTUtils.traverseUpOnly(identifier, [
|
|
533
|
+
AST_NODE_TYPES2.MemberExpression,
|
|
534
|
+
AST_NODE_TYPES2.TSNonNullExpression,
|
|
535
|
+
AST_NODE_TYPES2.Identifier
|
|
536
|
+
]);
|
|
537
|
+
const fullText = ExhaustiveDepsUtils.normalizeChain(
|
|
538
|
+
sourceCode.getText(fullChainNode)
|
|
539
|
+
);
|
|
540
|
+
const parent = fullChainNode.parent;
|
|
541
|
+
let dependencyPath = fullText;
|
|
542
|
+
let coversRootMembers = fullText === identifier.name;
|
|
543
|
+
if (parent && parent.type === AST_NODE_TYPES2.CallExpression && parent.callee === fullChainNode) {
|
|
544
|
+
const segments = fullText.split(".");
|
|
545
|
+
if (segments.length > 1) {
|
|
546
|
+
dependencyPath = segments.slice(0, -1).join(".");
|
|
547
|
+
}
|
|
548
|
+
coversRootMembers = false;
|
|
549
|
+
}
|
|
550
|
+
dependencyPath = dependencyPath.split(".")[0] === "" ? identifier.name : dependencyPath;
|
|
551
|
+
const root = dependencyPath.split(".")[0];
|
|
552
|
+
return {
|
|
553
|
+
path: dependencyPath,
|
|
554
|
+
root: root ?? identifier.name,
|
|
555
|
+
coversRootMembers: coversRootMembers && dependencyPath === root
|
|
556
|
+
};
|
|
557
|
+
},
|
|
449
558
|
collectExternalRefsInFunction(params) {
|
|
450
559
|
const { functionNode, scopeManager } = params;
|
|
451
560
|
const functionScope = scopeManager.acquire(functionNode);
|
|
@@ -477,6 +586,52 @@ var ExhaustiveDepsUtils = {
|
|
|
477
586
|
}
|
|
478
587
|
collect(functionScope);
|
|
479
588
|
return externalRefs;
|
|
589
|
+
},
|
|
590
|
+
/**
|
|
591
|
+
* Recursively collects type identifiers from a type annotation
|
|
592
|
+
*/
|
|
593
|
+
collectTypeIdentifiers(typeNode, out) {
|
|
594
|
+
switch (typeNode.type) {
|
|
595
|
+
case AST_NODE_TYPES2.TSTypeReference: {
|
|
596
|
+
if (typeNode.typeName.type === AST_NODE_TYPES2.Identifier) {
|
|
597
|
+
out.add(typeNode.typeName.name);
|
|
598
|
+
}
|
|
599
|
+
break;
|
|
600
|
+
}
|
|
601
|
+
case AST_NODE_TYPES2.TSUnionType:
|
|
602
|
+
case AST_NODE_TYPES2.TSIntersectionType: {
|
|
603
|
+
typeNode.types.forEach(
|
|
604
|
+
(t) => ExhaustiveDepsUtils.collectTypeIdentifiers(t, out)
|
|
605
|
+
);
|
|
606
|
+
break;
|
|
607
|
+
}
|
|
608
|
+
case AST_NODE_TYPES2.TSArrayType: {
|
|
609
|
+
ExhaustiveDepsUtils.collectTypeIdentifiers(typeNode.elementType, out);
|
|
610
|
+
break;
|
|
611
|
+
}
|
|
612
|
+
case AST_NODE_TYPES2.TSTupleType: {
|
|
613
|
+
typeNode.elementTypes.forEach(
|
|
614
|
+
(et) => ExhaustiveDepsUtils.collectTypeIdentifiers(et, out)
|
|
615
|
+
);
|
|
616
|
+
break;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
},
|
|
620
|
+
/**
|
|
621
|
+
* Gets the function expression nodes from a queryFn property, handling conditional expressions.
|
|
622
|
+
* When neither branch is skipToken, returns both branches so all deps are scanned.
|
|
623
|
+
*/
|
|
624
|
+
getQueryFnNodes(queryFn) {
|
|
625
|
+
if (queryFn.value.type !== AST_NODE_TYPES2.ConditionalExpression) {
|
|
626
|
+
return [queryFn.value];
|
|
627
|
+
}
|
|
628
|
+
if (queryFn.value.consequent.type === AST_NODE_TYPES2.Identifier && queryFn.value.consequent.name === "skipToken") {
|
|
629
|
+
return [queryFn.value.alternate];
|
|
630
|
+
}
|
|
631
|
+
if (queryFn.value.alternate.type === AST_NODE_TYPES2.Identifier && queryFn.value.alternate.name === "skipToken") {
|
|
632
|
+
return [queryFn.value.consequent];
|
|
633
|
+
}
|
|
634
|
+
return [queryFn.value.consequent, queryFn.value.alternate];
|
|
480
635
|
}
|
|
481
636
|
};
|
|
482
637
|
|
|
@@ -499,22 +654,35 @@ var rule = createRule({
|
|
|
499
654
|
},
|
|
500
655
|
hasSuggestions: true,
|
|
501
656
|
fixable: "code",
|
|
502
|
-
schema: [
|
|
657
|
+
schema: [
|
|
658
|
+
{
|
|
659
|
+
type: "object",
|
|
660
|
+
properties: {
|
|
661
|
+
allowlist: {
|
|
662
|
+
type: "object",
|
|
663
|
+
properties: {
|
|
664
|
+
variables: { type: "array", items: { type: "string" } },
|
|
665
|
+
types: { type: "array", items: { type: "string" } }
|
|
666
|
+
},
|
|
667
|
+
additionalProperties: false
|
|
668
|
+
}
|
|
669
|
+
},
|
|
670
|
+
additionalProperties: false
|
|
671
|
+
}
|
|
672
|
+
]
|
|
503
673
|
},
|
|
504
674
|
defaultOptions: [],
|
|
505
675
|
create: detectTanstackQueryImports((context) => {
|
|
506
676
|
return {
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
return;
|
|
510
|
-
}
|
|
677
|
+
ObjectExpression: (node) => {
|
|
678
|
+
var _a, _b;
|
|
511
679
|
const scopeManager = context.sourceCode.scopeManager;
|
|
512
680
|
const queryKey = ASTUtils.findPropertyWithIdentifierKey(
|
|
513
|
-
node.
|
|
681
|
+
node.properties,
|
|
514
682
|
QUERY_KEY
|
|
515
683
|
);
|
|
516
684
|
const queryFn = ASTUtils.findPropertyWithIdentifierKey(
|
|
517
|
-
node.
|
|
685
|
+
node.properties,
|
|
518
686
|
QUERY_FN
|
|
519
687
|
);
|
|
520
688
|
if (scopeManager === null || queryKey === void 0 || queryFn === void 0 || !ASTUtils.isNodeOfOneOf(queryFn.value, [
|
|
@@ -528,73 +696,104 @@ var rule = createRule({
|
|
|
528
696
|
queryKey.value,
|
|
529
697
|
context
|
|
530
698
|
);
|
|
531
|
-
const
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
node: getQueryFnRelevantNode(queryFn)
|
|
535
|
-
});
|
|
536
|
-
const relevantRefs = externalRefs.filter(
|
|
537
|
-
(reference) => ExhaustiveDepsUtils.isRelevantReference({
|
|
538
|
-
sourceCode: context.sourceCode,
|
|
539
|
-
reference,
|
|
699
|
+
const queryFnNodes = ExhaustiveDepsUtils.getQueryFnNodes(queryFn);
|
|
700
|
+
const externalRefs = queryFnNodes.flatMap(
|
|
701
|
+
(fnNode) => ASTUtils.getExternalRefs({
|
|
540
702
|
scopeManager,
|
|
541
|
-
|
|
542
|
-
|
|
703
|
+
sourceCode: context.sourceCode,
|
|
704
|
+
node: fnNode
|
|
543
705
|
})
|
|
544
706
|
);
|
|
707
|
+
const relevantRefs = externalRefs.filter(
|
|
708
|
+
(reference) => queryFnNodes.some(
|
|
709
|
+
(fnNode) => ExhaustiveDepsUtils.isRelevantReference({
|
|
710
|
+
sourceCode: context.sourceCode,
|
|
711
|
+
reference,
|
|
712
|
+
scopeManager,
|
|
713
|
+
node: fnNode,
|
|
714
|
+
filename: context.filename
|
|
715
|
+
})
|
|
716
|
+
)
|
|
717
|
+
);
|
|
718
|
+
const ruleOptions = context.options.at(0);
|
|
719
|
+
const allowlistedVariables = new Set(
|
|
720
|
+
((_a = ruleOptions == null ? void 0 : ruleOptions.allowlist) == null ? void 0 : _a.variables) ?? []
|
|
721
|
+
);
|
|
722
|
+
const allowlistedTypes = new Set(((_b = ruleOptions == null ? void 0 : ruleOptions.allowlist) == null ? void 0 : _b.types) ?? []);
|
|
723
|
+
const requiredRefs = relevantRefs.flatMap((ref) => {
|
|
724
|
+
if (ref.identifier.type !== AST_NODE_TYPES3.Identifier) return [];
|
|
725
|
+
const refPath = ExhaustiveDepsUtils.computeRefPath({
|
|
726
|
+
identifier: ref.identifier,
|
|
727
|
+
sourceCode: context.sourceCode
|
|
728
|
+
});
|
|
729
|
+
if (refPath === null) return [];
|
|
730
|
+
return [
|
|
731
|
+
{
|
|
732
|
+
...refPath,
|
|
733
|
+
allowlistedByType: ExhaustiveDepsUtils.variableIsAllowlistedByType({
|
|
734
|
+
allowlistedTypes,
|
|
735
|
+
variable: ref.resolved ?? null
|
|
736
|
+
})
|
|
737
|
+
}
|
|
738
|
+
];
|
|
739
|
+
});
|
|
740
|
+
if (requiredRefs.length === 0) return;
|
|
545
741
|
const queryKeyDeps = ExhaustiveDepsUtils.collectQueryKeyDeps({
|
|
546
742
|
sourceCode: context.sourceCode,
|
|
547
743
|
scopeManager,
|
|
548
744
|
queryKeyNode
|
|
549
745
|
});
|
|
550
|
-
const
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
messageId: "fixTo",
|
|
571
|
-
data: { result: existingWithMissing },
|
|
572
|
-
fix(fixer) {
|
|
573
|
-
return fixer.replaceText(queryKeyNode, existingWithMissing);
|
|
574
|
-
}
|
|
575
|
-
});
|
|
576
|
-
}
|
|
577
|
-
context.report({
|
|
578
|
-
node,
|
|
579
|
-
messageId: "missingDeps",
|
|
580
|
-
data: {
|
|
581
|
-
deps: uniqueMissingRefs.map((ref) => ref.text).join(", ")
|
|
582
|
-
},
|
|
583
|
-
suggest: suggestions
|
|
584
|
-
});
|
|
585
|
-
}
|
|
746
|
+
const missingPaths = ExhaustiveDepsUtils.computeFilteredMissingPaths({
|
|
747
|
+
requiredRefs,
|
|
748
|
+
allowlistedVariables,
|
|
749
|
+
existingRootIdentifiers: queryKeyDeps.roots,
|
|
750
|
+
existingFullPaths: queryKeyDeps.paths
|
|
751
|
+
});
|
|
752
|
+
if (missingPaths.length === 0) return;
|
|
753
|
+
const missingAsText = missingPaths.join(", ");
|
|
754
|
+
const suggestions = buildSuggestions({
|
|
755
|
+
queryKeyNode,
|
|
756
|
+
missingPaths,
|
|
757
|
+
missingAsText,
|
|
758
|
+
sourceCode: context.sourceCode
|
|
759
|
+
});
|
|
760
|
+
context.report({
|
|
761
|
+
node,
|
|
762
|
+
messageId: "missingDeps",
|
|
763
|
+
data: { deps: missingAsText },
|
|
764
|
+
suggest: suggestions
|
|
765
|
+
});
|
|
586
766
|
}
|
|
587
767
|
};
|
|
588
768
|
})
|
|
589
769
|
});
|
|
590
|
-
function
|
|
591
|
-
|
|
592
|
-
|
|
770
|
+
function buildSuggestions(params) {
|
|
771
|
+
const { queryKeyNode, missingPaths, missingAsText, sourceCode } = params;
|
|
772
|
+
if (queryKeyNode.type !== AST_NODE_TYPES3.ArrayExpression) {
|
|
773
|
+
return [];
|
|
593
774
|
}
|
|
594
|
-
|
|
595
|
-
|
|
775
|
+
const closingBracket = sourceCode.getLastToken(queryKeyNode);
|
|
776
|
+
if (!closingBracket) return [];
|
|
777
|
+
const existingElements = queryKeyNode.elements.filter((el) => el !== null).map((el) => sourceCode.getText(el));
|
|
778
|
+
const resultText = `[${[...existingElements, ...missingPaths].join(", ")}]`;
|
|
779
|
+
if (queryKeyNode.elements.length === 0) {
|
|
780
|
+
return [
|
|
781
|
+
{
|
|
782
|
+
messageId: "fixTo",
|
|
783
|
+
data: { result: resultText },
|
|
784
|
+
fix: (fixer) => fixer.replaceText(queryKeyNode, resultText)
|
|
785
|
+
}
|
|
786
|
+
];
|
|
596
787
|
}
|
|
597
|
-
|
|
788
|
+
const tokenBefore = sourceCode.getTokenBefore(closingBracket);
|
|
789
|
+
const separator = (tokenBefore == null ? void 0 : tokenBefore.value) === "," ? " " : ", ";
|
|
790
|
+
return [
|
|
791
|
+
{
|
|
792
|
+
messageId: "fixTo",
|
|
793
|
+
data: { result: resultText },
|
|
794
|
+
fix: (fixer) => fixer.insertTextBefore(closingBracket, `${separator}${missingAsText}`)
|
|
795
|
+
}
|
|
796
|
+
];
|
|
598
797
|
}
|
|
599
798
|
function dereferenceVariablesAndTypeAssertions(queryKeyNode, context) {
|
|
600
799
|
const visitedNodes = /* @__PURE__ */ new Set();
|
|
@@ -1142,4 +1341,4 @@ var rules = {
|
|
|
1142
1341
|
export {
|
|
1143
1342
|
rules
|
|
1144
1343
|
};
|
|
1145
|
-
//# sourceMappingURL=chunk-
|
|
1344
|
+
//# sourceMappingURL=chunk-T44AUBX4.js.map
|