@tanstack/eslint-plugin-query
Version:
ESLint plugin for TanStack Query
89 lines (78 loc) • 2.74 kB
text/typescript
import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils'
import { ASTUtils } from '../../utils/ast-utils'
import { getDocsUrl } from '../../utils/get-docs-url'
import { detectTanstackQueryImports } from '../../utils/detect-react-query-imports'
import type { TSESLint } from '@typescript-eslint/utils'
import type { ExtraRuleDocs } from '../../types'
export const name = 'stable-query-client'
const createRule = ESLintUtils.RuleCreator<ExtraRuleDocs>(getDocsUrl)
export const rule = createRule({
name,
meta: {
type: 'problem',
docs: {
description: 'Makes sure that QueryClient is stable',
recommended: 'error',
},
messages: {
unstable: [
'QueryClient is not stable. It should be either extracted from the component or wrapped in React.useState.',
'See https://tkdodo.eu/blog/react-query-fa-qs#2-the-queryclient-is-not-stable',
].join('\n'),
fixTo: 'Fix to {{result}}',
},
hasSuggestions: true,
fixable: 'code',
schema: [],
},
defaultOptions: [],
create: detectTanstackQueryImports((context, _, helpers) => {
return {
NewExpression: (node) => {
if (
node.callee.type !== AST_NODE_TYPES.Identifier ||
node.callee.name !== 'QueryClient' ||
node.parent.type !== AST_NODE_TYPES.VariableDeclarator ||
!helpers.isSpecificTanstackQueryImport(
node.callee,
'@tanstack/react-query',
)
) {
return
}
const fnAncestor = ASTUtils.getFunctionAncestor(
context.sourceCode,
node,
)
const isReactServerComponent = fnAncestor?.async === true
if (
!ASTUtils.isValidReactComponentOrHookName(fnAncestor?.id) ||
isReactServerComponent
) {
return
}
context.report({
node: node.parent,
messageId: 'unstable',
fix: (() => {
const { parent } = node
if (parent.id.type !== AST_NODE_TYPES.Identifier) {
return
}
// we need the fallbacks for backwards compat with eslint < 8.37.0
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const sourceCode = context.sourceCode ?? context.getSourceCode()
const nodeText = sourceCode.getText(node)
const variableName = parent.id.name
return (fixer: TSESLint.RuleFixer) => {
return fixer.replaceTextRange(
[parent.range[0], parent.range[1]],
`[${variableName}] = React.useState(() => ${nodeText})`,
)
}
})(),
})
},
}
}),
})