From c402a2b815c97292bd76b7516242c30806c7efef Mon Sep 17 00:00:00 2001 From: Eliya Cohen Date: Thu, 28 Nov 2024 10:39:33 +0200 Subject: [PATCH] fix(eslint-plugin-query): handle optional and non-null chaining in exhaustive-deps (#8365) * fix: handle optional and non-null chaining in exhaustive-deps Fixed a false positive in the exhaustive-deps rule when using optional chaining and non-null assertions in query keys and functions. * ci: apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .../src/__tests__/exhaustive-deps.test.ts | 24 +++++++++++++++++++ .../exhaustive-deps/exhaustive-deps.rule.ts | 7 ++++-- .../src/utils/ast-utils.ts | 10 ++++++++ 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin-query/src/__tests__/exhaustive-deps.test.ts b/packages/eslint-plugin-query/src/__tests__/exhaustive-deps.test.ts index c7611775b5..32db49f1c4 100644 --- a/packages/eslint-plugin-query/src/__tests__/exhaustive-deps.test.ts +++ b/packages/eslint-plugin-query/src/__tests__/exhaustive-deps.test.ts @@ -473,6 +473,30 @@ ruleTester.run('exhaustive-deps', rule, { }; `, }, + { + name: 'should pass with optional chaining as key', + code: ` + function useTest(data?: any) { + return useQuery({ + queryKey: ['query-name', data?.address], + queryFn: async () => sendQuery(data.address), + enabled: !!data?.address, + }) + } + `, + }, + { + name: 'should pass with optional chaining as key and non-null assertion in queryFn', + code: ` + function useTest(data?: any) { + return useQuery({ + queryKey: ['query-name', data?.address], + queryFn: async () => sendQuery(data!.address), + enabled: !!data?.address, + }) + } + `, + }, ], invalid: [ { diff --git a/packages/eslint-plugin-query/src/rules/exhaustive-deps/exhaustive-deps.rule.ts b/packages/eslint-plugin-query/src/rules/exhaustive-deps/exhaustive-deps.rule.ts index 7fead1b6ff..bb87b04552 100644 --- a/packages/eslint-plugin-query/src/rules/exhaustive-deps/exhaustive-deps.rule.ts +++ b/packages/eslint-plugin-query/src/rules/exhaustive-deps/exhaustive-deps.rule.ts @@ -102,13 +102,16 @@ export const rule = createRule({ const existingKeys = ASTUtils.getNestedIdentifiers(queryKeyNode).map( (identifier) => - ASTUtils.mapKeyNodeToText(identifier, context.sourceCode), + ASTUtils.mapKeyNodeToBaseText(identifier, context.sourceCode), ) const missingRefs = relevantRefs .map((ref) => ({ ref: ref, - text: ASTUtils.mapKeyNodeToText(ref.identifier, context.sourceCode), + text: ASTUtils.mapKeyNodeToBaseText( + ref.identifier, + context.sourceCode, + ), })) .filter(({ ref, text }) => { return ( diff --git a/packages/eslint-plugin-query/src/utils/ast-utils.ts b/packages/eslint-plugin-query/src/utils/ast-utils.ts index 051b336402..a31640cb43 100644 --- a/packages/eslint-plugin-query/src/utils/ast-utils.ts +++ b/packages/eslint-plugin-query/src/utils/ast-utils.ts @@ -224,10 +224,20 @@ export const ASTUtils = { return sourceCode.getText( ASTUtils.traverseUpOnly(node, [ AST_NODE_TYPES.MemberExpression, + AST_NODE_TYPES.TSNonNullExpression, AST_NODE_TYPES.Identifier, ]), ) }, + mapKeyNodeToBaseText( + node: TSESTree.Node, + sourceCode: Readonly, + ) { + return ASTUtils.mapKeyNodeToText(node, sourceCode).replace( + /(\?\.|!\.)/g, + '.', + ) + }, isValidReactComponentOrHookName( identifier: TSESTree.Identifier | null | undefined, ) {