Skip to content

Commit 80a49ff

Browse files
fix(eslint-plugin-query): improve external reference relevance (#8334)
* fix: handle external variable in queryFn without failure Adjusted tests to ensure that the rule does not fail when a queryFn inside queryOptions contains a reference to an external variable. Also updated other tests to wrap query calls in a function component for consistency. fix #8326 * ci: apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 102b6a8 commit 80a49ff

File tree

3 files changed

+102
-48
lines changed

3 files changed

+102
-48
lines changed

packages/eslint-plugin-query/src/__tests__/exhaustive-deps.test.ts

Lines changed: 91 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,19 @@ ruleTester.run('exhaustive-deps', rule, {
460460
});
461461
`,
462462
},
463+
{
464+
name: 'should not fail when queryFn inside queryOptions contains a reference to an external variable',
465+
code: normalizeIndent`
466+
const EXTERNAL = 1;
467+
468+
export const queries = {
469+
foo: queryOptions({
470+
queryKey: ['foo'],
471+
queryFn: () => Promise.resolve(EXTERNAL),
472+
}),
473+
};
474+
`,
475+
},
463476
],
464477
invalid: [
465478
{
@@ -492,8 +505,10 @@ ruleTester.run('exhaustive-deps', rule, {
492505
{
493506
name: 'should fail when no deps are passed (react)',
494507
code: normalizeIndent`
495-
const id = 1;
496-
useQuery({ queryKey: ["entity"], queryFn: () => api.getEntity(id) });
508+
function Component() {
509+
const id = 1;
510+
useQuery({ queryKey: ["entity"], queryFn: () => api.getEntity(id) });
511+
}
497512
`,
498513
errors: [
499514
{
@@ -504,8 +519,10 @@ ruleTester.run('exhaustive-deps', rule, {
504519
messageId: 'fixTo',
505520
data: { result: '["entity", id]' },
506521
output: normalizeIndent`
507-
const id = 1;
508-
useQuery({ queryKey: ["entity", id], queryFn: () => api.getEntity(id) });
522+
function Component() {
523+
const id = 1;
524+
useQuery({ queryKey: ["entity", id], queryFn: () => api.getEntity(id) });
525+
}
509526
`,
510527
},
511528
],
@@ -515,8 +532,10 @@ ruleTester.run('exhaustive-deps', rule, {
515532
{
516533
name: 'should fail when no deps are passed (solid)',
517534
code: normalizeIndent`
518-
const id = 1;
519-
createQuery({ queryKey: ["entity"], queryFn: () => api.getEntity(id) });
535+
function Component() {
536+
const id = 1;
537+
createQuery({ queryKey: ["entity"], queryFn: () => api.getEntity(id) });
538+
}
520539
`,
521540
errors: [
522541
{
@@ -527,8 +546,10 @@ ruleTester.run('exhaustive-deps', rule, {
527546
messageId: 'fixTo',
528547
data: { result: '["entity", id]' },
529548
output: normalizeIndent`
530-
const id = 1;
531-
createQuery({ queryKey: ["entity", id], queryFn: () => api.getEntity(id) });
549+
function Component() {
550+
const id = 1;
551+
createQuery({ queryKey: ["entity", id], queryFn: () => api.getEntity(id) });
552+
}
532553
`,
533554
},
534555
],
@@ -538,8 +559,10 @@ ruleTester.run('exhaustive-deps', rule, {
538559
{
539560
name: 'should fail when deps are passed incorrectly',
540561
code: normalizeIndent`
541-
const id = 1;
542-
useQuery({ queryKey: ["entity/\${id}"], queryFn: () => api.getEntity(id) });
562+
function Component() {
563+
const id = 1;
564+
useQuery({ queryKey: ["entity/\${id}"], queryFn: () => api.getEntity(id) });
565+
}
543566
`,
544567
errors: [
545568
{
@@ -550,8 +573,10 @@ ruleTester.run('exhaustive-deps', rule, {
550573
messageId: 'fixTo',
551574
data: { result: '["entity/${id}", id]' },
552575
output: normalizeIndent`
553-
const id = 1;
554-
useQuery({ queryKey: ["entity/\${id}", id], queryFn: () => api.getEntity(id) });
576+
function Component() {
577+
const id = 1;
578+
useQuery({ queryKey: ["entity/\${id}", id], queryFn: () => api.getEntity(id) });
579+
}
555580
`,
556581
},
557582
],
@@ -561,9 +586,11 @@ ruleTester.run('exhaustive-deps', rule, {
561586
{
562587
name: 'should pass missing dep while key has a template literal',
563588
code: normalizeIndent`
564-
const a = 1;
565-
const b = 2;
566-
useQuery({ queryKey: [\`entity/\${a}\`], queryFn: () => api.getEntity(a, b) });
589+
function Component() {
590+
const a = 1;
591+
const b = 2;
592+
useQuery({ queryKey: [\`entity/\${a}\`], queryFn: () => api.getEntity(a, b) });
593+
}
567594
`,
568595
errors: [
569596
{
@@ -574,9 +601,11 @@ ruleTester.run('exhaustive-deps', rule, {
574601
messageId: 'fixTo',
575602
data: { result: '[`entity/${a}`, b]' },
576603
output: normalizeIndent`
577-
const a = 1;
578-
const b = 2;
579-
useQuery({ queryKey: [\`entity/\${a}\`, b], queryFn: () => api.getEntity(a, b) });
604+
function Component() {
605+
const a = 1;
606+
const b = 2;
607+
useQuery({ queryKey: [\`entity/\${a}\`, b], queryFn: () => api.getEntity(a, b) });
608+
}
580609
`,
581610
},
582611
],
@@ -586,14 +615,16 @@ ruleTester.run('exhaustive-deps', rule, {
586615
{
587616
name: 'should fail when dep exists inside setter and missing in queryKey',
588617
code: normalizeIndent`
589-
const [id] = React.useState(1);
590-
useQuery({
618+
function Component() {
619+
const [id] = React.useState(1);
620+
useQuery({
591621
queryKey: ["entity"],
592622
queryFn: () => {
593-
const { data } = axios.get(\`.../\${id}\`);
594-
return data;
623+
const { data } = axios.get(\`.../\${id}\`);
624+
return data;
595625
}
596-
});
626+
});
627+
}
597628
`,
598629
errors: [
599630
{
@@ -604,14 +635,16 @@ ruleTester.run('exhaustive-deps', rule, {
604635
messageId: 'fixTo',
605636
data: { result: '["entity", id]' },
606637
output: normalizeIndent`
607-
const [id] = React.useState(1);
608-
useQuery({
638+
function Component() {
639+
const [id] = React.useState(1);
640+
useQuery({
609641
queryKey: ["entity", id],
610642
queryFn: () => {
611-
const { data } = axios.get(\`.../\${id}\`);
612-
return data;
643+
const { data } = axios.get(\`.../\${id}\`);
644+
return data;
613645
}
614-
});
646+
});
647+
}
615648
`,
616649
},
617650
],
@@ -713,9 +746,11 @@ ruleTester.run('exhaustive-deps', rule, {
713746
{
714747
name: 'should fail when a queryKey is a reference of an array expression with a missing dep',
715748
code: normalizeIndent`
716-
const x = 5;
717-
const queryKey = ['foo']
718-
useQuery({ queryKey, queryFn: () => x })
749+
function Component() {
750+
const x = 5;
751+
const queryKey = ['foo']
752+
useQuery({ queryKey, queryFn: () => x })
753+
}
719754
`,
720755
errors: [
721756
{
@@ -728,9 +763,11 @@ ruleTester.run('exhaustive-deps', rule, {
728763
result: "['foo', x]",
729764
},
730765
output: normalizeIndent`
731-
const x = 5;
732-
const queryKey = ['foo', x]
733-
useQuery({ queryKey, queryFn: () => x })
766+
function Component() {
767+
const x = 5;
768+
const queryKey = ['foo', x]
769+
useQuery({ queryKey, queryFn: () => x })
770+
}
734771
`,
735772
},
736773
],
@@ -758,25 +795,29 @@ ruleTester.run('exhaustive-deps', rule, {
758795
{
759796
name: 'should fail if queryFn is using multiple object props when only one of them is in the queryKey',
760797
code: normalizeIndent`
761-
const state = { foo: 'foo', bar: 'bar' }
798+
function Component() {
799+
const state = { foo: 'foo', bar: 'bar' }
762800
763-
useQuery({
801+
useQuery({
764802
queryKey: ['state', state.foo],
765803
queryFn: () => Promise.resolve({ foo: state.foo, bar: state.bar })
766-
})
804+
})
805+
}
767806
`,
768807
errors: [
769808
{
770809
suggestions: [
771810
{
772811
messageId: 'fixTo',
773812
output: normalizeIndent`
774-
const state = { foo: 'foo', bar: 'bar' }
813+
function Component() {
814+
const state = { foo: 'foo', bar: 'bar' }
775815
776-
useQuery({
816+
useQuery({
777817
queryKey: ['state', state.foo, state.bar],
778818
queryFn: () => Promise.resolve({ foo: state.foo, bar: state.bar })
779-
})
819+
})
820+
}
780821
`,
781822
},
782823
],
@@ -788,29 +829,33 @@ ruleTester.run('exhaustive-deps', rule, {
788829
{
789830
name: 'should fail if queryFn is invalid while using FunctionExpression syntax',
790831
code: normalizeIndent`
791-
const id = 1;
832+
function Component() {
833+
const id = 1;
792834
793-
useQuery({
835+
useQuery({
794836
queryKey: [],
795837
queryFn() {
796838
Promise.resolve(id)
797839
}
798-
})
840+
});
841+
}
799842
`,
800843
errors: [
801844
{
802845
suggestions: [
803846
{
804847
messageId: 'fixTo',
805848
output: normalizeIndent`
806-
const id = 1;
849+
function Component() {
850+
const id = 1;
807851
808-
useQuery({
852+
useQuery({
809853
queryKey: [id],
810854
queryFn() {
811855
Promise.resolve(id)
812856
}
813-
})
857+
});
858+
}
814859
`,
815860
},
816861
],

packages/eslint-plugin-query/src/rules/exhaustive-deps/exhaustive-deps.utils.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@ export const ExhaustiveDepsUtils = {
1212
const { sourceCode, reference, scopeManager, node } = params
1313
const component = ASTUtils.getFunctionAncestor(sourceCode, node)
1414

15+
if (component === undefined) {
16+
return false
17+
}
18+
1519
if (
16-
component !== undefined &&
1720
!ASTUtils.isDeclaredInNode({
1821
scopeManager,
1922
reference,

packages/eslint-plugin-query/src/utils/ast-utils.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,13 @@ export const ASTUtils = {
241241
node: TSESTree.Node,
242242
) {
243243
for (const ancestor of sourceCode.getAncestors(node)) {
244-
if (ancestor.type === AST_NODE_TYPES.FunctionDeclaration) {
244+
if (
245+
ASTUtils.isNodeOfOneOf(ancestor, [
246+
AST_NODE_TYPES.FunctionDeclaration,
247+
AST_NODE_TYPES.FunctionExpression,
248+
AST_NODE_TYPES.ArrowFunctionExpression,
249+
])
250+
) {
245251
return ancestor
246252
}
247253

0 commit comments

Comments
 (0)