@tanstack/eslint-plugin-query 5.94.5 → 5.95.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.
@@ -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
- isInstanceOfKind(node: TSESTree.Node): boolean;
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
- }): Set<string>;
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
- isInstanceOfKind(node: TSESTree.Node): boolean;
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
- }): Set<string>;
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
- isInstanceOfKind(node) {
379
- return node.type === AST_NODE_TYPES2.BinaryExpression && node.operator === "instanceof";
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 deps = /* @__PURE__ */ new Set();
395
+ const roots = /* @__PURE__ */ new Set();
396
+ const paths = /* @__PURE__ */ new Set();
384
397
  const visitorKeys = sourceCode.visitorKeys;
385
- function add(identifier) {
386
- deps.add(ASTUtils.mapKeyNodeToBaseText(identifier, sourceCode));
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
- add(node);
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 === AST_NODE_TYPES2.Identifier) {
420
- add(reference.identifier);
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
- deps.add(node.object.name);
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
- if (node.callee.type === AST_NODE_TYPES2.MemberExpression || node.callee.type === AST_NODE_TYPES2.ChainExpression || node.callee.type === AST_NODE_TYPES2.TSNonNullExpression) {
437
- visit(node.callee);
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 deps;
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
- Property: (node) => {
508
- if (!ASTUtils.isObjectExpression(node.parent) || !ASTUtils.isIdentifierWithName(node.key, QUERY_KEY)) {
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.parent.properties,
681
+ node.properties,
514
682
  QUERY_KEY
515
683
  );
516
684
  const queryFn = ASTUtils.findPropertyWithIdentifierKey(
517
- node.parent.properties,
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 externalRefs = ASTUtils.getExternalRefs({
532
- scopeManager,
533
- sourceCode: context.sourceCode,
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
- node: getQueryFnRelevantNode(queryFn),
542
- filename: context.filename
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 missingRefs = relevantRefs.map((ref) => ({
551
- ref,
552
- text: ASTUtils.isAncestorIsCallee(ref.identifier) ? ref.identifier.name : ASTUtils.mapKeyNodeToBaseText(
553
- ref.identifier,
554
- context.sourceCode
555
- )
556
- })).filter(({ ref, text }) => {
557
- return !ref.isTypeReference && !queryKeyDeps.has(text) && !queryKeyDeps.has(text.split(/[?.]/)[0] ?? "");
558
- }).map(({ ref, text }) => ({
559
- identifier: ref.identifier,
560
- text
561
- }));
562
- const uniqueMissingRefs = uniqueBy(missingRefs, (x) => x.text);
563
- if (uniqueMissingRefs.length > 0) {
564
- const missingAsText = uniqueMissingRefs.map((ref) => ref.text).join(", ");
565
- const queryKeyValue = context.sourceCode.getText(queryKeyNode);
566
- const existingWithMissing = queryKeyValue === "[]" ? `[${missingAsText}]` : queryKeyValue.replace(/\]$/, `, ${missingAsText}]`);
567
- const suggestions = [];
568
- if (queryKeyNode.type === AST_NODE_TYPES3.ArrayExpression) {
569
- suggestions.push({
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 getQueryFnRelevantNode(queryFn) {
591
- if (queryFn.value.type !== AST_NODE_TYPES3.ConditionalExpression) {
592
- return queryFn.value;
770
+ function buildSuggestions(params) {
771
+ const { queryKeyNode, missingPaths, missingAsText, sourceCode } = params;
772
+ if (queryKeyNode.type !== AST_NODE_TYPES3.ArrayExpression) {
773
+ return [];
593
774
  }
594
- if (queryFn.value.consequent.type === AST_NODE_TYPES3.Identifier && queryFn.value.consequent.name === "skipToken") {
595
- return queryFn.value.alternate;
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
- return queryFn.value.consequent;
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-HD2KSEKJ.js.map
1344
+ //# sourceMappingURL=chunk-T44AUBX4.js.map