Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/lucky-eels-count.md
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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: [],
})
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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]
}

Expand Down Expand Up @@ -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({
Expand Down