diff --git a/.changeset/lucky-eels-count.md b/.changeset/lucky-eels-count.md new file mode 100644 index 00000000000..ed64ae2c370 --- /dev/null +++ b/.changeset/lucky-eels-count.md @@ -0,0 +1,5 @@ +--- +'@tanstack/eslint-plugin-query': patch +--- + +Stop `no-unstable-deps` from resolving identifiers such as `toString` or `constructor` through `Object.prototype`, which reported unrelated code diff --git a/packages/eslint-plugin-query/src/__tests__/no-unstable-deps.test.ts b/packages/eslint-plugin-query/src/__tests__/no-unstable-deps.test.ts index fca18c41cd2..d9b66da12f8 100644 --- a/packages/eslint-plugin-query/src/__tests__/no-unstable-deps.test.ts +++ b/packages/eslint-plugin-query/src/__tests__/no-unstable-deps.test.ts @@ -405,3 +405,54 @@ reactHookNames.forEach((reactHookName) => { }, ) }) + +// Identifiers that collide with properties inherited from Object.prototype must +// not be mistaken for tracked hooks, tracked variables or React hook aliases. +ruleTester.run('no-unstable-deps', rule, { + valid: [ + { + name: 'should pass when a dependency is named after an inherited Object.prototype property', + code: ` + import { useCallback } from "React"; + import { useQuery } from "@tanstack/react-query"; + import { toString } from "lodash"; + + function Component() { + const { data } = useQuery({ queryFn: () => 'data' }); + const label = toString(data); + const callback = useCallback(() => label, [label, toString, constructor, valueOf]); + return callback; + } + `, + }, + { + name: 'should pass when a call is named after an inherited Object.prototype property', + code: ` + import { useQuery } from "@tanstack/react-query"; + import { constructor, valueOf } from "some-library"; + + function Component() { + const query = useQuery({ queryFn: () => 'data' }); + constructor(() => {}, [query]); + valueOf(() => {}, [query]); + return null; + } + `, + }, + { + name: 'should pass when a custom hook is named after an inherited Object.prototype property', + code: ` + import { useCallback } from "React"; + import { useQuery } from "@tanstack/react-query"; + import { toString } from "some-library"; + + function Component() { + const value = toString(); + const callback = useCallback(() => value, [value]); + return callback; + } + `, + }, + ], + invalid: [], +}) diff --git a/packages/eslint-plugin-query/src/rules/no-unstable-deps/no-unstable-deps.rule.ts b/packages/eslint-plugin-query/src/rules/no-unstable-deps/no-unstable-deps.rule.ts index 934773faa0f..193041c11bc 100644 --- a/packages/eslint-plugin-query/src/rules/no-unstable-deps/no-unstable-deps.rule.ts +++ b/packages/eslint-plugin-query/src/rules/no-unstable-deps/no-unstable-deps.rule.ts @@ -48,7 +48,10 @@ export const rule = createRule({ if (node.callee.type === 'Identifier') { const calleeName = node.callee.name // Check if the identifier is a known React hook or an alias - if (reactHookNames.includes(calleeName) || calleeName in hookAliasMap) { + if ( + reactHookNames.includes(calleeName) || + Object.hasOwn(hookAliasMap, calleeName) + ) { return calleeName } } else if ( @@ -138,7 +141,10 @@ export const rule = createRule({ return directQueryHook } - if (callExpression.callee.type === AST_NODE_TYPES.Identifier) { + if ( + callExpression.callee.type === AST_NODE_TYPES.Identifier && + Object.hasOwn(trackedCustomHooks, callExpression.callee.name) + ) { return trackedCustomHooks[callExpression.callee.name] } @@ -182,7 +188,7 @@ export const rule = createRule({ if ( dep !== null && dep.type === AST_NODE_TYPES.Identifier && - trackedVariables[dep.name] !== undefined + Object.hasOwn(trackedVariables, dep.name) ) { const queryHook = trackedVariables[dep.name] context.report({