From a341931c1f9f9511c52c86e9717d4569c581aee0 Mon Sep 17 00:00:00 2001 From: Newbie012 Date: Sun, 23 Oct 2022 20:37:20 +0300 Subject: [PATCH 01/40] add @tanstack/eslint-plugin-query --- examples/react/basic-eslint-plugin/.eslintrc | 8 + examples/react/basic-eslint-plugin/.gitignore | 27 + .../react/basic-eslint-plugin/.prettierrc | 1 + examples/react/basic-eslint-plugin/README.md | 6 + examples/react/basic-eslint-plugin/index.html | 16 + .../react/basic-eslint-plugin/package.json | 35 ++ .../public/emblem-light.svg | 13 + .../react/basic-eslint-plugin/src/index.jsx | 140 +++++ packages/eslint-plugin-query/jest.config.ts | 4 + packages/eslint-plugin-query/package.json | 45 ++ .../eslint-plugin-query/src/extra-utils.ts | 56 ++ packages/eslint-plugin-query/src/index.ts | 5 + .../src/rules/exhaustive-deps.rule.ts | 173 ++++++ .../src/rules/exhaustive-deps.test.ts | 83 +++ packages/eslint-plugin-query/tsconfig.json | 10 + pnpm-lock.yaml | 503 +++++++++++++++++- 16 files changed, 1114 insertions(+), 11 deletions(-) create mode 100644 examples/react/basic-eslint-plugin/.eslintrc create mode 100644 examples/react/basic-eslint-plugin/.gitignore create mode 100644 examples/react/basic-eslint-plugin/.prettierrc create mode 100644 examples/react/basic-eslint-plugin/README.md create mode 100644 examples/react/basic-eslint-plugin/index.html create mode 100644 examples/react/basic-eslint-plugin/package.json create mode 100644 examples/react/basic-eslint-plugin/public/emblem-light.svg create mode 100644 examples/react/basic-eslint-plugin/src/index.jsx create mode 100644 packages/eslint-plugin-query/jest.config.ts create mode 100644 packages/eslint-plugin-query/package.json create mode 100644 packages/eslint-plugin-query/src/extra-utils.ts create mode 100644 packages/eslint-plugin-query/src/index.ts create mode 100644 packages/eslint-plugin-query/src/rules/exhaustive-deps.rule.ts create mode 100644 packages/eslint-plugin-query/src/rules/exhaustive-deps.test.ts create mode 100644 packages/eslint-plugin-query/tsconfig.json diff --git a/examples/react/basic-eslint-plugin/.eslintrc b/examples/react/basic-eslint-plugin/.eslintrc new file mode 100644 index 0000000000..360fdf4ec1 --- /dev/null +++ b/examples/react/basic-eslint-plugin/.eslintrc @@ -0,0 +1,8 @@ +{ + "extends": ["react-app", "prettier"], + "parser": "@typescript-eslint/parser", + "plugins": ["@tanstack/eslint-plugin-query"], + "rules": { + "@tanstack/exhaustive-deps": "error" + } +} diff --git a/examples/react/basic-eslint-plugin/.gitignore b/examples/react/basic-eslint-plugin/.gitignore new file mode 100644 index 0000000000..4673b022e5 --- /dev/null +++ b/examples/react/basic-eslint-plugin/.gitignore @@ -0,0 +1,27 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +pnpm-lock.yaml +yarn.lock +package-lock.json + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/examples/react/basic-eslint-plugin/.prettierrc b/examples/react/basic-eslint-plugin/.prettierrc new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/examples/react/basic-eslint-plugin/.prettierrc @@ -0,0 +1 @@ +{} diff --git a/examples/react/basic-eslint-plugin/README.md b/examples/react/basic-eslint-plugin/README.md new file mode 100644 index 0000000000..1cf8892652 --- /dev/null +++ b/examples/react/basic-eslint-plugin/README.md @@ -0,0 +1,6 @@ +# Example + +To run this example: + +- `npm install` +- `npm run dev` diff --git a/examples/react/basic-eslint-plugin/index.html b/examples/react/basic-eslint-plugin/index.html new file mode 100644 index 0000000000..fb8b862df9 --- /dev/null +++ b/examples/react/basic-eslint-plugin/index.html @@ -0,0 +1,16 @@ + + + + + + + + + Tanstack Query React Basic Example App + + + +
+ + + diff --git a/examples/react/basic-eslint-plugin/package.json b/examples/react/basic-eslint-plugin/package.json new file mode 100644 index 0000000000..730a59f56e --- /dev/null +++ b/examples/react/basic-eslint-plugin/package.json @@ -0,0 +1,35 @@ +{ + "name": "@tanstack/query-example-react-basic", + "version": "0.0.1", + "main": "src/index.jsx", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "axios": "^0.21.1", + "react": "^18.0.0", + "react-dom": "^18.0.0", + "@tanstack/react-query": "^4.7.1", + "@tanstack/react-query-devtools": "^4.7.1" + }, + "devDependencies": { + "@tanstack/eslint-plugin-query": "^1.0.0", + "@vitejs/plugin-react": "^2.0.0", + "eslint": "^8.26.0", + "vite": "^3.0.0" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} diff --git a/examples/react/basic-eslint-plugin/public/emblem-light.svg b/examples/react/basic-eslint-plugin/public/emblem-light.svg new file mode 100644 index 0000000000..a58e69ad5e --- /dev/null +++ b/examples/react/basic-eslint-plugin/public/emblem-light.svg @@ -0,0 +1,13 @@ + + + + emblem-light + Created with Sketch. + + + + + + + + \ No newline at end of file diff --git a/examples/react/basic-eslint-plugin/src/index.jsx b/examples/react/basic-eslint-plugin/src/index.jsx new file mode 100644 index 0000000000..90ef699aea --- /dev/null +++ b/examples/react/basic-eslint-plugin/src/index.jsx @@ -0,0 +1,140 @@ +/* eslint-disable jsx-a11y/anchor-is-valid */ +import React from "react"; +import ReactDOM from "react-dom/client"; +import axios from "axios"; +import { + useQuery, + useQueryClient, + QueryClient, + QueryClientProvider, +} from "@tanstack/react-query"; +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; + +const queryClient = new QueryClient(); + +function App() { + const [postId, setPostId] = React.useState(-1); + + return ( + +

+ As you visit the posts below, you will notice them in a loading state + the first time you load them. However, after you return to this list and + click on any posts you have already visited again, you will see them + load instantly and background refresh right before your eyes!{" "} + + (You may need to throttle your network speed to simulate longer + loading sequences) + +

+ {postId > -1 ? ( + + ) : ( + + )} + +
+ ); +} + +function usePosts() { + const [id] = React.useState(1); + + return useQuery({ + queryKey: ["posts", id], + queryFn: async () => { + const { data } = await axios.get( + `https://jsonplaceholder.typicode.com/posts/${id}` + ); + + return data; + }, + }); +} + +function Posts({ setPostId }) { + const queryClient = useQueryClient(); + const { status, data, error, isFetching } = usePosts(); + + return ( +
+

Posts

+
+ {status === "loading" ? ( + "Loading..." + ) : status === "error" ? ( + Error: {error.message} + ) : ( + <> +
+ {data.map((post) => ( +

+ setPostId(post.id)} + href="#" + style={ + // We can access the query data here to show bold links for + // ones that are cached + queryClient.getQueryData(["post", post.id]) + ? { + fontWeight: "bold", + color: "green", + } + : {} + } + > + {post.title} + +

+ ))} +
+
{isFetching ? "Background Updating..." : " "}
+ + )} +
+
+ ); +} + +const getPostById = async (id) => { + const { data } = await axios.get( + `https://jsonplaceholder.typicode.com/posts/${id}` + ); + return data; +}; + +function usePost(postId) { + return useQuery(["post", postId], () => getPostById(postId), { + enabled: !!postId, + }); +} + +function Post({ postId, setPostId }) { + const { status, data, error, isFetching } = usePost(postId); + + return ( +
+
+ setPostId(-1)} href="#"> + Back + +
+ {!postId || status === "loading" ? ( + "Loading..." + ) : status === "error" ? ( + Error: {error.message} + ) : ( + <> +

{data.title}

+
+

{data.body}

+
+
{isFetching ? "Background Updating..." : " "}
+ + )} +
+ ); +} + +const rootElement = document.getElementById("root"); +ReactDOM.createRoot(rootElement).render(); diff --git a/packages/eslint-plugin-query/jest.config.ts b/packages/eslint-plugin-query/jest.config.ts new file mode 100644 index 0000000000..a6563d822f --- /dev/null +++ b/packages/eslint-plugin-query/jest.config.ts @@ -0,0 +1,4 @@ +export default { + displayName: 'query-core', + preset: '../../jest-preset.js', +} diff --git a/packages/eslint-plugin-query/package.json b/packages/eslint-plugin-query/package.json new file mode 100644 index 0000000000..8dae914731 --- /dev/null +++ b/packages/eslint-plugin-query/package.json @@ -0,0 +1,45 @@ +{ + "name": "@tanstack/eslint-plugin-query", + "version": "1.0.0", + "description": "ESLint plugin for React Query", + "author": "Eliya Cohen", + "license": "MIT", + "repository": "tanstack/query", + "homepage": "https://tanstack.com/query", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "main": "build/lib/index.js", + "scripts": { + "typecheck": "tsc", + "build": "tsup --minify", + "dev": "tsup --watch --sourcemap", + "test:jest": "../../node_modules/.bin/jest --config ./jest.config.ts", + "test:dev": "pnpm run test:jest --watch", + "lint:eslint": "eslint --ext .js,.jsx,.ts,.tsx src" + }, + "files": [ + "build" + ], + "tsup": { + "entry": [ + "src/index.ts" + ], + "external": [ + "eslint" + ], + "clean": true, + "bundle": true, + "outDir": "build/lib" + }, + "devDependencies": { + "@typescript-eslint/eslint-plugin": "^5.40.1", + "@typescript-eslint/parser": "^5.40.1", + "@typescript-eslint/types": "^5.40.1", + "@typescript-eslint/utils": "^5.40.1", + "eslint": "^8.26.0", + "tsup": "^6.3.0", + "typescript": "4.8.4" + } +} diff --git a/packages/eslint-plugin-query/src/extra-utils.ts b/packages/eslint-plugin-query/src/extra-utils.ts new file mode 100644 index 0000000000..08c1b889a6 --- /dev/null +++ b/packages/eslint-plugin-query/src/extra-utils.ts @@ -0,0 +1,56 @@ +import type { TSESTree } from '@typescript-eslint/types' +import { AST_NODE_TYPES } from '@typescript-eslint/types' + +export const ExtraUtils = { + isIdentifier(node: TSESTree.Node): node is TSESTree.Identifier { + return node.type === AST_NODE_TYPES.Identifier + }, + isLiteral(node: TSESTree.Node): node is TSESTree.Literal { + return node.type === AST_NODE_TYPES.Literal + }, + isTemplateLiteral(node: TSESTree.Node): node is TSESTree.TemplateLiteral { + return node.type === AST_NODE_TYPES.TemplateLiteral + }, + isProperty(node: TSESTree.Node): node is TSESTree.Property { + return node.type === AST_NODE_TYPES.Property + }, + isPropertyWithIdentifierKey( + node: TSESTree.Node, + key: string, + ): node is TSESTree.Property { + return ( + ExtraUtils.isProperty(node) && + ExtraUtils.isIdentifier(node.key) && + node.key.name === key + ) + }, + findPropertyWithIdentifierKey( + properties: TSESTree.ObjectLiteralElement[], + key: string, + ): TSESTree.Property | undefined { + return properties.find((x) => + ExtraUtils.isPropertyWithIdentifierKey(x, key), + ) as TSESTree.Property | undefined + }, + getIdentifiersFromArrayExpression( + node: TSESTree.ArrayExpression, + ): TSESTree.Identifier[] { + const identifiers: TSESTree.Identifier[] = [] + + for (const element of node.elements) { + if (ExtraUtils.isIdentifier(element)) { + identifiers.push(element) + } + + if (ExtraUtils.isTemplateLiteral(element)) { + element.expressions.forEach((expression) => { + if (ExtraUtils.isIdentifier(expression)) { + identifiers.push(expression) + } + }) + } + } + + return identifiers + }, +} diff --git a/packages/eslint-plugin-query/src/index.ts b/packages/eslint-plugin-query/src/index.ts new file mode 100644 index 0000000000..9bbeb41360 --- /dev/null +++ b/packages/eslint-plugin-query/src/index.ts @@ -0,0 +1,5 @@ +import { exhaustiveDepsRule } from './rules/exhaustive-deps.rule' + +export const rules = { + 'exhaustive-deps': exhaustiveDepsRule, +} diff --git a/packages/eslint-plugin-query/src/rules/exhaustive-deps.rule.ts b/packages/eslint-plugin-query/src/rules/exhaustive-deps.rule.ts new file mode 100644 index 0000000000..af721c7ddb --- /dev/null +++ b/packages/eslint-plugin-query/src/rules/exhaustive-deps.rule.ts @@ -0,0 +1,173 @@ +import type { JSONSchema, TSESLint, TSESTree } from '@typescript-eslint/utils' +import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils' +import { ExtraUtils } from '../extra-utils' + +const messages = { + missingDeps: + 'The following dependencies are missing in your queryKey: {{deps}}', +} + +const ruleSchema: JSONSchema.JSONSchema4 = { + type: 'array', + minItems: 0, + maxItems: 1, + items: { + type: 'object', + properties: { + whitelist: { + type: 'array', + items: { + type: 'string', + }, + }, + }, + }, +} + +const defaultRuleOptions = { whitelist: ['api', 'fetch', 'axios'] } + +type RuleMessage = keyof typeof messages +type RuleOptions = { whitelist?: string[] } +type RuleContext = Readonly> + +const createRule = ESLintUtils.RuleCreator( + () => 'https://github.com/tanstack/query', +) + +export const exhaustiveDepsRule = createRule<[RuleOptions], RuleMessage>({ + name: 'exhausitive-deps', + meta: { + docs: { + description: + 'This rule ensures that all dependencies are passed to the useQuery queryKey parameter', + recommended: 'warn', + }, + messages: messages, + type: 'problem', + fixable: 'code', + schema: ruleSchema, + }, + defaultOptions: [defaultRuleOptions], + create(context) { + return { + CallExpression(node) { + if ( + ExtraUtils.isIdentifier(node.callee) && + node.callee.name === 'useQuery' + ) { + runCheck({ node, context }) + } + }, + } + }, +}) + +function runCheck(params: { + node: TSESTree.CallExpression + context: RuleContext +}) { + const { node, context } = params + const queryOptions = node.arguments[0] + const ruleOptions = { ...defaultRuleOptions, ...context.options[0] } + + if (queryOptions?.type !== AST_NODE_TYPES.ObjectExpression) { + return + } + + const scopeManager = context.getSourceCode().scopeManager + const queryKey = ExtraUtils.findPropertyWithIdentifierKey( + queryOptions.properties, + 'queryKey', + ) + const queryFn = ExtraUtils.findPropertyWithIdentifierKey( + queryOptions.properties, + 'queryFn', + ) + + if ( + scopeManager === null || + queryKey === undefined || + queryFn === undefined || + queryFn.value.type !== AST_NODE_TYPES.ArrowFunctionExpression + ) { + return + } + + if (queryKey.value.type !== AST_NODE_TYPES.ArrayExpression) { + // TODO support query key factory + return + } + + const queryKeyValue = queryKey.value + const queryKeyDeps = + ExtraUtils.getIdentifiersFromArrayExpression(queryKeyValue) + const refs = getExternalRefs({ scopeManager, node: queryFn.value }) + const missingRefs = refs + .map((ref) => ref.identifier) + .filter((ref) => !ruleOptions.whitelist.includes(ref.name)) + .filter((ref) => !queryKeyDeps.some((y) => y.name === ref.name)) + + if (missingRefs.length > 0) { + context.report({ + node: node.callee, + messageId: 'missingDeps', + data: { + deps: missingRefs.map(nodeToText).join(', '), + }, + fix(fixer) { + const relevantQueryKeyDeps = queryKeyValue.elements.filter( + (element) => + ExtraUtils.isIdentifier(element) || ExtraUtils.isLiteral(element), + ) + const newQueryKeyDeps = [...relevantQueryKeyDeps, ...missingRefs] + + return fixer.replaceText( + queryKeyValue, + `[${newQueryKeyDeps.map((x) => nodeToText(x)).join(', ')}]`, + ) + }, + }) + } +} + +function nodeToText(node: TSESTree.Node): string | null { + if (node.type === AST_NODE_TYPES.Identifier) { + return node.name + } + + if (node.type === AST_NODE_TYPES.Literal) { + return node.raw + } + + if (node.type === AST_NODE_TYPES.TemplateLiteral) { + const expressions = node.expressions.map((x) => nodeToText(x)) + const quasis = node.quasis.map((x) => x.value.raw) + return quasis.reduce((acc, x, i) => acc + x + (expressions[i] ?? ''), '') + } + + return null +} + +function getExternalRefs(params: { + scopeManager: TSESLint.Scope.ScopeManager + node: TSESTree.Node +}): TSESLint.Scope.Reference[] { + const { scopeManager, node } = params + const scope = scopeManager.acquire(node) + + if (scope === null) { + return [] + } + + const readOnlyRefs = scope.references.filter((x) => x.isRead()) + const localRefIds = new Set([...scope.set.values()].map((x) => x.$id)) + const externalRefs = readOnlyRefs.filter( + (x) => x.resolved === null || !localRefIds.has(x.resolved.$id), + ) + + return uniqueBy(externalRefs, (x) => x.resolved) +} + +function uniqueBy(arr: T[], fn: (x: T) => unknown): T[] { + return arr.filter((x, i, a) => a.findIndex((y) => fn(x) === fn(y)) === i) +} diff --git a/packages/eslint-plugin-query/src/rules/exhaustive-deps.test.ts b/packages/eslint-plugin-query/src/rules/exhaustive-deps.test.ts new file mode 100644 index 0000000000..9a4e186c15 --- /dev/null +++ b/packages/eslint-plugin-query/src/rules/exhaustive-deps.test.ts @@ -0,0 +1,83 @@ +import { ESLintUtils } from '@typescript-eslint/utils' +import { exhaustiveDepsRule } from './exhaustive-deps.rule' + +const ruleTester = new ESLintUtils.RuleTester({ + parser: '@typescript-eslint/parser', + settings: {}, +}) + +ruleTester.run('exhaustive-deps', exhaustiveDepsRule, { + valid: [ + { + name: 'should pass when deps are passed in array', + code: 'useQuery({ queryKey: ["entity", id], queryFn: () => api.getEntity(id) });', + }, + { + name: 'should pass when deps are passed in template literal', + // eslint-disable-next-line no-template-curly-in-string + code: 'useQuery({ queryKey: [`entity/${id}`], queryFn: () => api.getEntity(id) });', + }, + { + name: 'should not pass fetch', + code: 'useQuery({ queryKey: ["entity", id], queryFn: () => fetch("...") });', + }, + { + name: 'should not pass axios', + code: 'useQuery({ queryKey: ["entity", id], queryFn: () => axios.get("...") });', + }, + { + name: 'should not pass api', + code: 'useQuery({ queryKey: ["entity", id], queryFn: () => api.get("...") });', + }, + ], + invalid: [ + { + name: 'should fail when no deps are passed', + code: ` + const id = 1; + useQuery({ queryKey: ["entity"], queryFn: () => api.getEntity(id) }); + `, + output: ` + const id = 1; + useQuery({ queryKey: ["entity", id], queryFn: () => api.getEntity(id) }); + `, + errors: [{ messageId: 'missingDeps', data: { deps: 'id' } }], + }, + { + name: 'should fail when deps are passed incorrectly', + code: ` + const id = 1; + useQuery({ queryKey: ["entity/\${id}"], queryFn: () => api.getEntity(id) }); + `, + output: ` + const id = 1; + useQuery({ queryKey: ["entity/\${id}", id], queryFn: () => api.getEntity(id) }); + `, + errors: [{ messageId: 'missingDeps', data: { deps: 'id' } }], + }, + { + name: 'should fail when dep exists inside setter and missing in queryKey', + code: ` + const [id] = React.useState(1); + useQuery({ + queryKey: ["entity"], + queryFn: () => { + const { data } = axios.get(\`.../\${id}\`); + return data; + } + }); + `, + output: ` + const [id] = React.useState(1); + useQuery({ + queryKey: ["entity", id], + queryFn: () => { + const { data } = axios.get(\`.../\${id}\`); + return data; + } + }); + `, + errors: [{ messageId: 'missingDeps', data: { deps: 'id' } }], + }, + ], +}) diff --git a/packages/eslint-plugin-query/tsconfig.json b/packages/eslint-plugin-query/tsconfig.json new file mode 100644 index 0000000000..9ee9f26e25 --- /dev/null +++ b/packages/eslint-plugin-query/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "rootDir": "./src", + "outDir": "./build/lib", + "tsBuildInfoFile": "./build/.tsbuildinfo" + }, + "include": ["src"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bf06b989e4..7ca5bd928a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -179,6 +179,29 @@ importers: '@vitejs/plugin-react': 2.1.0_vite@3.1.3 vite: 3.1.3 + examples/react/basic-eslint-plugin: + specifiers: + '@tanstack/eslint-plugin-query': ^1.0.0 + '@tanstack/react-query': ^4.7.1 + '@tanstack/react-query-devtools': ^4.7.1 + '@vitejs/plugin-react': ^2.0.0 + axios: ^0.21.1 + eslint: ^8.26.0 + react: ^18.0.0 + react-dom: ^18.0.0 + vite: ^3.0.0 + dependencies: + '@tanstack/react-query': link:../../../packages/react-query + '@tanstack/react-query-devtools': link:../../../packages/react-query-devtools + axios: 0.21.4 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + devDependencies: + '@tanstack/eslint-plugin-query': link:../../../packages/eslint-plugin-query + '@vitejs/plugin-react': 2.1.0_vite@3.1.8 + eslint: 8.26.0 + vite: 3.1.8 + examples/react/basic-graphql-request: specifiers: '@tanstack/react-query': ^4.7.1 @@ -651,6 +674,24 @@ importers: typescript: 4.8.4 vite: 3.1.4 + packages/eslint-plugin-query: + specifiers: + '@typescript-eslint/eslint-plugin': ^5.40.1 + '@typescript-eslint/parser': ^5.40.1 + '@typescript-eslint/types': ^5.40.1 + '@typescript-eslint/utils': ^5.40.1 + eslint: ^8.26.0 + tsup: ^6.3.0 + typescript: 4.8.4 + devDependencies: + '@typescript-eslint/eslint-plugin': 5.40.1_c4zyna56jjjrggqkyejnaxjxfu + '@typescript-eslint/parser': 5.40.1_wyqvi574yv7oiwfeinomdzmc3m + '@typescript-eslint/types': 5.40.1 + '@typescript-eslint/utils': 5.40.1_wyqvi574yv7oiwfeinomdzmc3m + eslint: 8.26.0 + tsup: 6.3.0_typescript@4.8.4 + typescript: 4.8.4 + packages/query-async-storage-persister: specifiers: '@tanstack/query-persist-client-core': workspace:* @@ -3910,6 +3951,23 @@ packages: - supports-color dev: true + /@eslint/eslintrc/1.3.3: + resolution: {integrity: sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + ajv: 6.12.6 + debug: 4.3.4 + espree: 9.4.0 + globals: 13.16.0 + ignore: 5.2.0 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + dev: true + /@expo/config-plugins/1.0.33: resolution: {integrity: sha512-YQJop0c69LKD/6ZJJto7klS7TDmzgs44TI0Z5RBqesOjYlDwNFcQk2Rl2BaA1wlAYkH+rRrhN2+WjjSyD9HiPg==} dependencies: @@ -4164,6 +4222,17 @@ packages: '@hapi/hoek': 9.3.0 dev: false + /@humanwhocodes/config-array/0.11.6: + resolution: {integrity: sha512-jJr+hPTJYKyDILJfhNSHsjiwXYf26Flsz8DvNndOsHs5pwSnpGUEy8yzF0JYhCEvTDdV2vuOK5tt8BVhwO5/hg==} + engines: {node: '>=10.10.0'} + dependencies: + '@humanwhocodes/object-schema': 1.2.1 + debug: 4.3.4 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + dev: true + /@humanwhocodes/config-array/0.5.0: resolution: {integrity: sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==} engines: {node: '>=10.10.0'} @@ -4175,6 +4244,11 @@ packages: - supports-color dev: true + /@humanwhocodes/module-importer/1.0.1: + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + dev: true + /@humanwhocodes/object-schema/1.2.1: resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} dev: true @@ -5750,6 +5824,10 @@ packages: /@types/scheduler/0.16.2: resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==} + /@types/semver/7.3.12: + resolution: {integrity: sha512-WwA1MW0++RfXmCr12xeYOOC5baSC9mSb0ZqCquFzKhcoF4TvHu5MKOuXsncgZcpVFhB1pXd5hZmM0ryAoCp12A==} + dev: true + /@types/set-cookie-parser/2.4.2: resolution: {integrity: sha512-fBZgytwhYAUkj/jC/FAV4RQ5EerRup1YQsXQCh8rZfiHkc4UahC192oH0smGwsXol3cL3A5oETuAHeQHmhXM4w==} dependencies: @@ -5837,6 +5915,32 @@ packages: - supports-color dev: true + /@typescript-eslint/eslint-plugin/5.40.1_c4zyna56jjjrggqkyejnaxjxfu: + resolution: {integrity: sha512-FsWboKkWdytGiXT5O1/R9j37YgcjO8MKHSUmWnIEjVaz0krHkplPnYi7mwdb+5+cs0toFNQb0HIrN7zONdIEWg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + '@typescript-eslint/parser': ^5.0.0 + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/parser': 5.40.1_wyqvi574yv7oiwfeinomdzmc3m + '@typescript-eslint/scope-manager': 5.40.1 + '@typescript-eslint/type-utils': 5.40.1_wyqvi574yv7oiwfeinomdzmc3m + '@typescript-eslint/utils': 5.40.1_wyqvi574yv7oiwfeinomdzmc3m + debug: 4.3.4 + eslint: 8.26.0 + ignore: 5.2.0 + regexpp: 3.2.0 + semver: 7.3.7 + tsutils: 3.21.0_typescript@4.8.4 + typescript: 4.8.4 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/experimental-utils/2.34.0_wnilx7boviscikmvsfkd6ljepe: resolution: {integrity: sha512-eS6FTkq+wuMJ+sgtuNTtcqavWXqsflWcfBnlYhg/nS4aZ1leewkXGbvBhaapn1q6qf4M71bsR1tez5JTRMuqwA==} engines: {node: ^8.10.0 || ^10.13.0 || >=11.10.1} @@ -5911,6 +6015,26 @@ packages: - supports-color dev: true + /@typescript-eslint/parser/5.40.1_wyqvi574yv7oiwfeinomdzmc3m: + resolution: {integrity: sha512-IK6x55va5w4YvXd4b3VrXQPldV9vQTxi5ov+g4pMANsXPTXOcfjx08CRR1Dfrcc51syPtXHF5bgLlMHYFrvQtg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/scope-manager': 5.40.1 + '@typescript-eslint/types': 5.40.1 + '@typescript-eslint/typescript-estree': 5.40.1_typescript@4.8.4 + debug: 4.3.4 + eslint: 8.26.0 + typescript: 4.8.4 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/scope-manager/4.33.0: resolution: {integrity: sha512-5IfJHpgTsTZuONKbODctL4kKuQje/bzBRkwHE8UOZ4f89Zeddg+EGZs8PD8NcN4LdM3ygHWYB3ukPAYjvl/qbQ==} engines: {node: ^8.10.0 || ^10.13.0 || >=11.10.1} @@ -5927,6 +6051,14 @@ packages: '@typescript-eslint/visitor-keys': 5.30.5 dev: true + /@typescript-eslint/scope-manager/5.40.1: + resolution: {integrity: sha512-jkn4xsJiUQucI16OLCXrLRXDZ3afKhOIqXs4R3O+M00hdQLKR58WuyXPZZjhKLFCEP2g+TXdBRtLQ33UfAdRUg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + '@typescript-eslint/types': 5.40.1 + '@typescript-eslint/visitor-keys': 5.40.1 + dev: true + /@typescript-eslint/type-utils/5.30.5_dyxdave6dwjbccc5dgiifcmuza: resolution: {integrity: sha512-k9+ejlv1GgwN1nN7XjVtyCgE0BTzhzT1YsQF0rv4Vfj2U9xnslBgMYYvcEYAFVdvhuEscELJsB7lDkN7WusErw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -5946,6 +6078,26 @@ packages: - supports-color dev: true + /@typescript-eslint/type-utils/5.40.1_wyqvi574yv7oiwfeinomdzmc3m: + resolution: {integrity: sha512-DLAs+AHQOe6n5LRraXiv27IYPhleF0ldEmx6yBqBgBLaNRKTkffhV1RPsjoJBhVup2zHxfaRtan8/YRBgYhU9Q==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '*' + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/typescript-estree': 5.40.1_typescript@4.8.4 + '@typescript-eslint/utils': 5.40.1_wyqvi574yv7oiwfeinomdzmc3m + debug: 4.3.4 + eslint: 8.26.0 + tsutils: 3.21.0_typescript@4.8.4 + typescript: 4.8.4 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/types/4.33.0: resolution: {integrity: sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ==} engines: {node: ^8.10.0 || ^10.13.0 || >=11.10.1} @@ -5956,6 +6108,11 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true + /@typescript-eslint/types/5.40.1: + resolution: {integrity: sha512-Icg9kiuVJSwdzSQvtdGspOlWNjVDnF3qVIKXdJ103o36yRprdl3Ge5cABQx+csx960nuMF21v8qvO31v9t3OHw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + /@typescript-eslint/typescript-estree/2.34.0_typescript@4.4.4: resolution: {integrity: sha512-OMAr+nJWKdlVM9LOqCqh3pQQPwxHAN7Du8DR6dmwCrAmxtiXQnhHJ6tBNtf+cggqfo51SG/FCwnKhXCIM7hnVg==} engines: {node: ^8.10.0 || ^10.13.0 || >=11.10.1} @@ -6019,6 +6176,27 @@ packages: - supports-color dev: true + /@typescript-eslint/typescript-estree/5.40.1_typescript@4.8.4: + resolution: {integrity: sha512-5QTP/nW5+60jBcEPfXy/EZL01qrl9GZtbgDZtDPlfW5zj/zjNrdI2B5zMUHmOsfvOr2cWqwVdWjobCiHcedmQA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 5.40.1 + '@typescript-eslint/visitor-keys': 5.40.1 + debug: 4.3.4 + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.3.7 + tsutils: 3.21.0_typescript@4.8.4 + typescript: 4.8.4 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/utils/5.30.5_dyxdave6dwjbccc5dgiifcmuza: resolution: {integrity: sha512-o4SSUH9IkuA7AYIfAvatldovurqTAHrfzPApOZvdUq01hHojZojCFXx06D/aFpKCgWbMPRdJBWAC3sWp3itwTA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -6037,6 +6215,26 @@ packages: - typescript dev: true + /@typescript-eslint/utils/5.40.1_wyqvi574yv7oiwfeinomdzmc3m: + resolution: {integrity: sha512-a2TAVScoX9fjryNrW6BZRnreDUszxqm9eQ9Esv8n5nXApMW0zeANUYlwh/DED04SC/ifuBvXgZpIK5xeJHQ3aw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + '@types/json-schema': 7.0.11 + '@types/semver': 7.3.12 + '@typescript-eslint/scope-manager': 5.40.1 + '@typescript-eslint/types': 5.40.1 + '@typescript-eslint/typescript-estree': 5.40.1_typescript@4.8.4 + eslint: 8.26.0 + eslint-scope: 5.1.1 + eslint-utils: 3.0.0_eslint@8.26.0 + semver: 7.3.7 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + /@typescript-eslint/visitor-keys/4.33.0: resolution: {integrity: sha512-uqi/2aSz9g2ftcHWf8uLPJA70rUv6yuMW5Bohw+bwcuzaxQIHaKFZCKGoGXIrc9vkTJ3+0txM73K0Hq3d5wgIg==} engines: {node: ^8.10.0 || ^10.13.0 || >=11.10.1} @@ -6053,6 +6251,14 @@ packages: eslint-visitor-keys: 3.3.0 dev: true + /@typescript-eslint/visitor-keys/5.40.1: + resolution: {integrity: sha512-A2DGmeZ+FMja0geX5rww+DpvILpwo1OsiQs0M+joPWJYsiEFBLsH0y1oFymPNul6Z5okSmHpP4ivkc2N0Cgfkw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + '@typescript-eslint/types': 5.40.1 + eslint-visitor-keys: 3.3.0 + dev: true + /@vitejs/plugin-react/2.1.0_vite@3.1.3: resolution: {integrity: sha512-am6rPyyU3LzUYne3Gd9oj9c4Rzbq5hQnuGXSMT6Gujq45Il/+bunwq3lrB7wghLkiF45ygMwft37vgJ/NE8IAA==} engines: {node: ^14.18.0 || >=16.0.0} @@ -6089,6 +6295,24 @@ packages: - supports-color dev: true + /@vitejs/plugin-react/2.1.0_vite@3.1.8: + resolution: {integrity: sha512-am6rPyyU3LzUYne3Gd9oj9c4Rzbq5hQnuGXSMT6Gujq45Il/+bunwq3lrB7wghLkiF45ygMwft37vgJ/NE8IAA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^3.0.0 + dependencies: + '@babel/core': 7.19.1 + '@babel/plugin-transform-react-jsx': 7.19.0_@babel+core@7.19.1 + '@babel/plugin-transform-react-jsx-development': 7.18.6_@babel+core@7.19.1 + '@babel/plugin-transform-react-jsx-self': 7.18.6_@babel+core@7.19.1 + '@babel/plugin-transform-react-jsx-source': 7.18.6_@babel+core@7.19.1 + magic-string: 0.26.4 + react-refresh: 0.14.0 + vite: 3.1.8 + transitivePeerDependencies: + - supports-color + dev: true + /@vitejs/plugin-vue/3.1.0_vite@3.1.4+vue@3.2.39: resolution: {integrity: sha512-fmxtHPjSOEIRg6vHYDaem+97iwCUg/uSIaTzp98lhELt2ISOQuDo2hbkBdXod0g15IhfPMQmAxh4heUks2zvDA==} engines: {node: ^14.18.0 || >=16.0.0} @@ -6399,6 +6623,14 @@ packages: acorn: 7.4.1 dev: true + /acorn-jsx/5.3.2_acorn@8.8.0: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + acorn: 8.8.0 + dev: true + /acorn-walk/7.2.0: resolution: {integrity: sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==} engines: {node: '>=0.4.0'} @@ -6539,6 +6771,10 @@ packages: dependencies: sprintf-js: 1.0.3 + /argparse/2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + dev: true + /argv-formatter/1.0.0: resolution: {integrity: sha512-F2+Hkm9xFaRg+GkaNnbwXNDV5O6pnCFEmqyhvfC/Ic5LbgOWjJh3L+mN/s91rxVL3znE7DYVpW0GJFT+4YBgWw==} dev: true @@ -7070,7 +7306,6 @@ packages: /binary-extensions/2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} - dev: false /bl/4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} @@ -7207,6 +7442,16 @@ packages: engines: {node: '>=6'} dev: true + /bundle-require/3.1.0_esbuild@0.15.9: + resolution: {integrity: sha512-IIXtAO7fKcwPHNPt9kY/WNVJqy7NDy6YqJvv6ENH0TOZoJ+yjpEsn1w40WKZbR2ibfu5g1rfgJTvmFHpm5aOMA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + peerDependencies: + esbuild: '>=0.13' + dependencies: + esbuild: 0.15.9 + load-tsconfig: 0.2.3 + dev: true + /bundlewatch/0.3.3: resolution: {integrity: sha512-qzSVWrZyyWXa546JpAPRPTFmnXms9YNVnfzB05DRJKmN6wRRa7SkxE4OgKQmbAY74Z6CM2mKAc6vwvd2R+1lUQ==} engines: {node: '>=10'} @@ -7236,6 +7481,11 @@ packages: engines: {node: '>= 0.8'} dev: true + /cac/6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + dev: true + /cache-base/1.0.1: resolution: {integrity: sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==} engines: {node: '>=0.10.0'} @@ -7359,7 +7609,6 @@ packages: readdirp: 3.6.0 optionalDependencies: fsevents: 2.3.2 - dev: false /ci-env/1.17.0: resolution: {integrity: sha512-NtTjhgSEqv4Aj90TUYHQLxHdnCPXnjdtuGG1X8lTfp/JqeXTdw0FTWl/vUAPuvbWZTF8QVpv6ASe/XacE+7R2A==} @@ -7526,7 +7775,6 @@ packages: /commander/4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} - dev: false /commander/5.1.0: resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==} @@ -7578,7 +7826,7 @@ packages: dev: false /concat-map/0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} /concurrently/7.2.2: resolution: {integrity: sha512-DcQkI0ruil5BA/g7Xy3EWySGrFJovF5RYAYxwGvv9Jf9q9B1v3jPFP2tl6axExNf1qgF30kjoNYrangZ0ey4Aw==} @@ -8855,6 +9103,14 @@ packages: estraverse: 4.3.0 dev: true + /eslint-scope/7.1.1: + resolution: {integrity: sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + dev: true + /eslint-utils/2.1.0: resolution: {integrity: sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==} engines: {node: '>=6'} @@ -8872,6 +9128,16 @@ packages: eslint-visitor-keys: 2.1.0 dev: true + /eslint-utils/3.0.0_eslint@8.26.0: + resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} + engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} + peerDependencies: + eslint: '>=5' + dependencies: + eslint: 8.26.0 + eslint-visitor-keys: 2.1.0 + dev: true + /eslint-visitor-keys/1.3.0: resolution: {integrity: sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==} engines: {node: '>=4'} @@ -8936,6 +9202,54 @@ packages: - supports-color dev: true + /eslint/8.26.0: + resolution: {integrity: sha512-kzJkpaw1Bfwheq4VXUezFriD1GxszX6dUekM7Z3aC2o4hju+tsR/XyTC3RcoSD7jmy9VkPU3+N6YjVU2e96Oyg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true + dependencies: + '@eslint/eslintrc': 1.3.3 + '@humanwhocodes/config-array': 0.11.6 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.4 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.1.1 + eslint-utils: 3.0.0_eslint@8.26.0 + eslint-visitor-keys: 3.3.0 + espree: 9.4.0 + esquery: 1.4.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.16.0 + grapheme-splitter: 1.0.4 + ignore: 5.2.0 + import-fresh: 3.3.0 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-sdsl: 4.1.5 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.1 + regexpp: 3.2.0 + strip-ansi: 6.0.1 + strip-json-comments: 3.1.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + dev: true + /espree/7.3.1: resolution: {integrity: sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==} engines: {node: ^10.12.0 || >=12.0.0} @@ -8945,6 +9259,15 @@ packages: eslint-visitor-keys: 1.3.0 dev: true + /espree/9.4.0: + resolution: {integrity: sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + acorn: 8.8.0 + acorn-jsx: 5.3.2_acorn@8.8.0 + eslint-visitor-keys: 3.3.0 + dev: true + /esprima/4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} @@ -9622,6 +9945,13 @@ packages: dependencies: is-glob: 4.0.3 + /glob-parent/6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + dependencies: + is-glob: 4.0.3 + dev: true + /glob/7.1.6: resolution: {integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==} dependencies: @@ -9681,6 +10011,10 @@ packages: /graceful-fs/4.2.10: resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} + /grapheme-splitter/1.0.4: + resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} + dev: true + /graphql-request/3.7.0_graphql@15.8.0: resolution: {integrity: sha512-dw5PxHCgBneN2DDNqpWu8QkbbJ07oOziy8z+bK/TAXufsOLaETuVO4GkXrbs0WjhdKhBMN3BkpN/RIvUHkmNUQ==} peerDependencies: @@ -10063,7 +10397,6 @@ packages: engines: {node: '>=8'} dependencies: binary-extensions: 2.2.0 - dev: false /is-boolean-object/1.1.2: resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} @@ -10235,6 +10568,11 @@ packages: engines: {node: '>=8'} dev: true + /is-path-inside/3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + dev: true + /is-plain-obj/1.1.0: resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} engines: {node: '>=0.10.0'} @@ -10991,6 +11329,11 @@ packages: '@sideway/pinpoint': 2.0.0 dev: false + /joycon/3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + dev: true + /jpeg-js/0.4.4: resolution: {integrity: sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==} dev: true @@ -11000,6 +11343,10 @@ packages: engines: {node: '>=0.10.0'} dev: false + /js-sdsl/4.1.5: + resolution: {integrity: sha512-08bOAKweV2NUC1wqTtf3qZlnpOX/R2DU9ikpjOHs0H+ibQv3zpncVQg6um4uYtRtrwIX8M4Nh3ytK4HGlYAq7Q==} + dev: true + /js-tokens/4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -11010,6 +11357,13 @@ packages: argparse: 1.0.10 esprima: 4.0.1 + /js-yaml/4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + dependencies: + argparse: 2.0.1 + dev: true + /jsc-android/245459.0.0: resolution: {integrity: sha512-wkjURqwaB1daNkDi2OYYbsLnIdC/lUM2nPXQKRs5pqEU9chDg435bjvo+LSaHotDENygHQDHe+ntUkkw2gwMtg==} dev: false @@ -11361,6 +11715,11 @@ packages: immediate: 3.0.6 dev: false + /lilconfig/2.0.6: + resolution: {integrity: sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==} + engines: {node: '>=10'} + dev: true + /lines-and-columns/1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -11377,6 +11736,11 @@ packages: xtend: 4.0.2 dev: true + /load-tsconfig/0.2.3: + resolution: {integrity: sha512-iyT2MXws+dc2Wi6o3grCFtGXpeMvHmJqS27sMPGtV2eUu4PeFnG+33I8BlFK1t1NWMjOpcx9bridn5yxLDX2gQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true + /localforage/1.10.0: resolution: {integrity: sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==} dependencies: @@ -11445,6 +11809,10 @@ packages: resolution: {integrity: sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==} dev: false + /lodash.sortby/4.7.0: + resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} + dev: true + /lodash.template/4.5.0: resolution: {integrity: sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==} dependencies: @@ -12191,7 +12559,6 @@ packages: any-promise: 1.3.0 object-assign: 4.1.1 thenify-all: 1.6.0 - dev: false /nanoid/3.3.4: resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==} @@ -12864,6 +13231,22 @@ packages: resolution: {integrity: sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==} engines: {node: '>=0.10.0'} + /postcss-load-config/3.1.4: + resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} + engines: {node: '>= 10'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + dependencies: + lilconfig: 2.0.6 + yaml: 1.10.2 + dev: true + /postcss/8.4.16: resolution: {integrity: sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==} engines: {node: ^10 || ^12 || >=14} @@ -13421,7 +13804,6 @@ packages: engines: {node: '>=8.10.0'} dependencies: picomatch: 2.3.1 - dev: false /recast/0.20.5: resolution: {integrity: sha512-E5qICoPoNL4yU0H0NoBDntNB0Q5oMSNh9usFctYniLBluTthi3RsQVBXIJNbApOlvSwW/RGxIuokPcAc59J5fQ==} @@ -14127,6 +14509,13 @@ packages: resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} engines: {node: '>= 8'} + /source-map/0.8.0-beta.0: + resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} + engines: {node: '>= 8'} + dependencies: + whatwg-url: 7.1.0 + dev: true + /sourcemap-codec/1.4.8: resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} @@ -14383,7 +14772,6 @@ packages: mz: 2.7.0 pirates: 4.0.5 ts-interface-checker: 0.1.13 - dev: false /sudo-prompt/9.2.1: resolution: {integrity: sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==} @@ -14522,13 +14910,11 @@ packages: engines: {node: '>=0.8'} dependencies: thenify: 3.3.1 - dev: false /thenify/3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} dependencies: any-promise: 1.3.0 - dev: false /throat/5.0.0: resolution: {integrity: sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==} @@ -14628,6 +15014,12 @@ packages: /tr46/0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + /tr46/1.0.1: + resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} + dependencies: + punycode: 2.1.1 + dev: true + /tr46/2.1.0: resolution: {integrity: sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==} engines: {node: '>=8'} @@ -14651,7 +15043,6 @@ packages: /ts-interface-checker/0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - dev: false /ts-jest/27.1.5_kv3z5ukkp6hjlassekz7vtidta: resolution: {integrity: sha512-Xv6jBQPoBEvBq/5i2TeSG9tt/nqkbpcurrEG1b+2yfBrcJelOZF9Ml6dmyMh7bcW9JyFbRYpR5rxROSlBLTZHA==} @@ -14740,6 +15131,42 @@ packages: /tslib/2.4.0: resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==} + /tsup/6.3.0_typescript@4.8.4: + resolution: {integrity: sha512-IaNQO/o1rFgadLhNonVKNCT2cks+vvnWX3DnL8sB87lBDqRvJXHENr5lSPJlqwplUlDxSwZK8dSg87rgBu6Emw==} + engines: {node: '>=14'} + hasBin: true + peerDependencies: + '@swc/core': ^1 + postcss: ^8.4.12 + typescript: ^4.1.0 + peerDependenciesMeta: + '@swc/core': + optional: true + postcss: + optional: true + typescript: + optional: true + dependencies: + bundle-require: 3.1.0_esbuild@0.15.9 + cac: 6.7.14 + chokidar: 3.5.3 + debug: 4.3.4 + esbuild: 0.15.9 + execa: 5.1.1 + globby: 11.1.0 + joycon: 3.1.1 + postcss-load-config: 3.1.4 + resolve-from: 5.0.0 + rollup: 2.78.1 + source-map: 0.8.0-beta.0 + sucrase: 3.23.0 + tree-kill: 1.2.2 + typescript: 4.8.4 + transitivePeerDependencies: + - supports-color + - ts-node + dev: true + /tsutils/3.21.0_typescript@4.4.4: resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} @@ -14760,6 +15187,16 @@ packages: typescript: 4.8.3 dev: true + /tsutils/3.21.0_typescript@4.8.4: + resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} + engines: {node: '>= 6'} + peerDependencies: + typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' + dependencies: + tslib: 1.14.1 + typescript: 4.8.4 + dev: true + /type-check/0.3.2: resolution: {integrity: sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==} engines: {node: '>= 0.8.0'} @@ -15128,6 +15565,33 @@ packages: fsevents: 2.3.2 dev: true + /vite/3.1.8: + resolution: {integrity: sha512-m7jJe3nufUbuOfotkntGFupinL/fmuTNuQmiVE7cH2IZMuf4UbfbGYMUT3jVWgGYuRVLY9j8NnrRqgw5rr5QTg==} + engines: {node: ^14.18.0 || >=16.0.0} + hasBin: true + peerDependencies: + less: '*' + sass: '*' + stylus: '*' + terser: ^5.4.0 + peerDependenciesMeta: + less: + optional: true + sass: + optional: true + stylus: + optional: true + terser: + optional: true + dependencies: + esbuild: 0.15.9 + postcss: 8.4.16 + resolve: 1.22.1 + rollup: 2.78.1 + optionalDependencies: + fsevents: 2.3.2 + dev: true + /vlq/1.0.1: resolution: {integrity: sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==} dev: false @@ -15219,6 +15683,10 @@ packages: /webidl-conversions/3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + /webidl-conversions/4.0.2: + resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} + dev: true + /webidl-conversions/5.0.0: resolution: {integrity: sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==} engines: {node: '>=8'} @@ -15249,6 +15717,14 @@ packages: tr46: 0.0.3 webidl-conversions: 3.0.1 + /whatwg-url/7.1.0: + resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} + dependencies: + lodash.sortby: 4.7.0 + tr46: 1.0.1 + webidl-conversions: 4.0.2 + dev: true + /whatwg-url/8.7.0: resolution: {integrity: sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==} engines: {node: '>=10'} @@ -15466,6 +15942,11 @@ packages: /yallist/4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + /yaml/1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + dev: true + /yargs-parser/18.1.3: resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} engines: {node: '>=6'} From b0a6732a93bc4d48368ed86020a359f1a50527d0 Mon Sep 17 00:00:00 2001 From: Newbie012 Date: Sun, 23 Oct 2022 21:07:16 +0300 Subject: [PATCH 02/40] fix .eslintrc rule name --- examples/react/basic-eslint-plugin/.eslintrc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/react/basic-eslint-plugin/.eslintrc b/examples/react/basic-eslint-plugin/.eslintrc index 360fdf4ec1..59df28de5d 100644 --- a/examples/react/basic-eslint-plugin/.eslintrc +++ b/examples/react/basic-eslint-plugin/.eslintrc @@ -1,8 +1,7 @@ { "extends": ["react-app", "prettier"], - "parser": "@typescript-eslint/parser", "plugins": ["@tanstack/eslint-plugin-query"], "rules": { - "@tanstack/exhaustive-deps": "error" + "@tanstack/query/exhaustive-deps": "error" } } From 0b3d91c065c96b9b45a1f37b0570e2dfd863359a Mon Sep 17 00:00:00 2001 From: Newbie012 Date: Mon, 24 Oct 2022 00:10:11 +0300 Subject: [PATCH 03/40] make rule compatible with babel parser --- .../eslint-plugin-query/src/rules/exhaustive-deps.rule.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin-query/src/rules/exhaustive-deps.rule.ts b/packages/eslint-plugin-query/src/rules/exhaustive-deps.rule.ts index af721c7ddb..00b2f4ac55 100644 --- a/packages/eslint-plugin-query/src/rules/exhaustive-deps.rule.ts +++ b/packages/eslint-plugin-query/src/rules/exhaustive-deps.rule.ts @@ -160,9 +160,9 @@ function getExternalRefs(params: { } const readOnlyRefs = scope.references.filter((x) => x.isRead()) - const localRefIds = new Set([...scope.set.values()].map((x) => x.$id)) + const localRefIds = new Set([...scope.set.values()].map((x) => x.identifiers[0])) const externalRefs = readOnlyRefs.filter( - (x) => x.resolved === null || !localRefIds.has(x.resolved.$id), + (x) => x.resolved === null || !localRefIds.has(x.resolved.identifiers[0]), ) return uniqueBy(externalRefs, (x) => x.resolved) From b30209fd76c0b71d79163c2bb616e4a8c975691b Mon Sep 17 00:00:00 2001 From: Newbie012 Date: Mon, 24 Oct 2022 00:10:54 +0300 Subject: [PATCH 04/40] fix script typo --- packages/eslint-plugin-query/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin-query/package.json b/packages/eslint-plugin-query/package.json index 8dae914731..4d153437c9 100644 --- a/packages/eslint-plugin-query/package.json +++ b/packages/eslint-plugin-query/package.json @@ -17,7 +17,7 @@ "dev": "tsup --watch --sourcemap", "test:jest": "../../node_modules/.bin/jest --config ./jest.config.ts", "test:dev": "pnpm run test:jest --watch", - "lint:eslint": "eslint --ext .js,.jsx,.ts,.tsx src" + "test:eslint": "eslint --ext .js,.jsx,.ts,.tsx src" }, "files": [ "build" From 7a22b6fe70a75e599314b36005a9f957645324a4 Mon Sep 17 00:00:00 2001 From: Newbie012 Date: Mon, 24 Oct 2022 00:14:19 +0300 Subject: [PATCH 05/40] remove dedicated example in favor of the basic example --- examples/react/basic-eslint-plugin/.eslintrc | 7 - examples/react/basic-eslint-plugin/.gitignore | 27 ---- .../react/basic-eslint-plugin/.prettierrc | 1 - examples/react/basic-eslint-plugin/README.md | 6 - examples/react/basic-eslint-plugin/index.html | 16 -- .../react/basic-eslint-plugin/package.json | 35 ----- .../public/emblem-light.svg | 13 -- .../react/basic-eslint-plugin/src/index.jsx | 140 ------------------ examples/react/basic/.eslintrc | 4 +- examples/react/basic/package.json | 1 + pnpm-lock.yaml | 101 +++++-------- 11 files changed, 38 insertions(+), 313 deletions(-) delete mode 100644 examples/react/basic-eslint-plugin/.eslintrc delete mode 100644 examples/react/basic-eslint-plugin/.gitignore delete mode 100644 examples/react/basic-eslint-plugin/.prettierrc delete mode 100644 examples/react/basic-eslint-plugin/README.md delete mode 100644 examples/react/basic-eslint-plugin/index.html delete mode 100644 examples/react/basic-eslint-plugin/package.json delete mode 100644 examples/react/basic-eslint-plugin/public/emblem-light.svg delete mode 100644 examples/react/basic-eslint-plugin/src/index.jsx diff --git a/examples/react/basic-eslint-plugin/.eslintrc b/examples/react/basic-eslint-plugin/.eslintrc deleted file mode 100644 index 59df28de5d..0000000000 --- a/examples/react/basic-eslint-plugin/.eslintrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": ["react-app", "prettier"], - "plugins": ["@tanstack/eslint-plugin-query"], - "rules": { - "@tanstack/query/exhaustive-deps": "error" - } -} diff --git a/examples/react/basic-eslint-plugin/.gitignore b/examples/react/basic-eslint-plugin/.gitignore deleted file mode 100644 index 4673b022e5..0000000000 --- a/examples/react/basic-eslint-plugin/.gitignore +++ /dev/null @@ -1,27 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js - -# testing -/coverage - -# production -/build - -pnpm-lock.yaml -yarn.lock -package-lock.json - -# misc -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local - -npm-debug.log* -yarn-debug.log* -yarn-error.log* diff --git a/examples/react/basic-eslint-plugin/.prettierrc b/examples/react/basic-eslint-plugin/.prettierrc deleted file mode 100644 index 0967ef424b..0000000000 --- a/examples/react/basic-eslint-plugin/.prettierrc +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/examples/react/basic-eslint-plugin/README.md b/examples/react/basic-eslint-plugin/README.md deleted file mode 100644 index 1cf8892652..0000000000 --- a/examples/react/basic-eslint-plugin/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# Example - -To run this example: - -- `npm install` -- `npm run dev` diff --git a/examples/react/basic-eslint-plugin/index.html b/examples/react/basic-eslint-plugin/index.html deleted file mode 100644 index fb8b862df9..0000000000 --- a/examples/react/basic-eslint-plugin/index.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - Tanstack Query React Basic Example App - - - -
- - - diff --git a/examples/react/basic-eslint-plugin/package.json b/examples/react/basic-eslint-plugin/package.json deleted file mode 100644 index 730a59f56e..0000000000 --- a/examples/react/basic-eslint-plugin/package.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "@tanstack/query-example-react-basic", - "version": "0.0.1", - "main": "src/index.jsx", - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "vite preview" - }, - "dependencies": { - "axios": "^0.21.1", - "react": "^18.0.0", - "react-dom": "^18.0.0", - "@tanstack/react-query": "^4.7.1", - "@tanstack/react-query-devtools": "^4.7.1" - }, - "devDependencies": { - "@tanstack/eslint-plugin-query": "^1.0.0", - "@vitejs/plugin-react": "^2.0.0", - "eslint": "^8.26.0", - "vite": "^3.0.0" - }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - } -} diff --git a/examples/react/basic-eslint-plugin/public/emblem-light.svg b/examples/react/basic-eslint-plugin/public/emblem-light.svg deleted file mode 100644 index a58e69ad5e..0000000000 --- a/examples/react/basic-eslint-plugin/public/emblem-light.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - emblem-light - Created with Sketch. - - - - - - - - \ No newline at end of file diff --git a/examples/react/basic-eslint-plugin/src/index.jsx b/examples/react/basic-eslint-plugin/src/index.jsx deleted file mode 100644 index 90ef699aea..0000000000 --- a/examples/react/basic-eslint-plugin/src/index.jsx +++ /dev/null @@ -1,140 +0,0 @@ -/* eslint-disable jsx-a11y/anchor-is-valid */ -import React from "react"; -import ReactDOM from "react-dom/client"; -import axios from "axios"; -import { - useQuery, - useQueryClient, - QueryClient, - QueryClientProvider, -} from "@tanstack/react-query"; -import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; - -const queryClient = new QueryClient(); - -function App() { - const [postId, setPostId] = React.useState(-1); - - return ( - -

- As you visit the posts below, you will notice them in a loading state - the first time you load them. However, after you return to this list and - click on any posts you have already visited again, you will see them - load instantly and background refresh right before your eyes!{" "} - - (You may need to throttle your network speed to simulate longer - loading sequences) - -

- {postId > -1 ? ( - - ) : ( - - )} - -
- ); -} - -function usePosts() { - const [id] = React.useState(1); - - return useQuery({ - queryKey: ["posts", id], - queryFn: async () => { - const { data } = await axios.get( - `https://jsonplaceholder.typicode.com/posts/${id}` - ); - - return data; - }, - }); -} - -function Posts({ setPostId }) { - const queryClient = useQueryClient(); - const { status, data, error, isFetching } = usePosts(); - - return ( -
-

Posts

-
- {status === "loading" ? ( - "Loading..." - ) : status === "error" ? ( - Error: {error.message} - ) : ( - <> - -
{isFetching ? "Background Updating..." : " "}
- - )} -
-
- ); -} - -const getPostById = async (id) => { - const { data } = await axios.get( - `https://jsonplaceholder.typicode.com/posts/${id}` - ); - return data; -}; - -function usePost(postId) { - return useQuery(["post", postId], () => getPostById(postId), { - enabled: !!postId, - }); -} - -function Post({ postId, setPostId }) { - const { status, data, error, isFetching } = usePost(postId); - - return ( -
- - {!postId || status === "loading" ? ( - "Loading..." - ) : status === "error" ? ( - Error: {error.message} - ) : ( - <> -

{data.title}

-
-

{data.body}

-
-
{isFetching ? "Background Updating..." : " "}
- - )} -
- ); -} - -const rootElement = document.getElementById("root"); -ReactDOM.createRoot(rootElement).render(); diff --git a/examples/react/basic/.eslintrc b/examples/react/basic/.eslintrc index 404725ad66..59df28de5d 100644 --- a/examples/react/basic/.eslintrc +++ b/examples/react/basic/.eslintrc @@ -1,7 +1,7 @@ { "extends": ["react-app", "prettier"], + "plugins": ["@tanstack/eslint-plugin-query"], "rules": { - // "eqeqeq": 0, - // "jsx-a11y/anchor-is-valid": 0 + "@tanstack/query/exhaustive-deps": "error" } } diff --git a/examples/react/basic/package.json b/examples/react/basic/package.json index 5d4d55b8b1..354afc5b04 100644 --- a/examples/react/basic/package.json +++ b/examples/react/basic/package.json @@ -15,6 +15,7 @@ "@tanstack/react-query-devtools": "^4.7.1" }, "devDependencies": { + "@tanstack/eslint-plugin-query": "^1.0.0", "@vitejs/plugin-react": "^2.0.0", "vite": "^3.0.0" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7ca5bd928a..67a0c5d269 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -161,32 +161,12 @@ importers: react-dom: 18.2.0_react@18.2.0 examples/react/basic: - specifiers: - '@tanstack/react-query': ^4.7.1 - '@tanstack/react-query-devtools': ^4.7.1 - '@vitejs/plugin-react': ^2.0.0 - axios: ^0.21.1 - react: ^18.0.0 - react-dom: ^18.0.0 - vite: ^3.0.0 - dependencies: - '@tanstack/react-query': link:../../../packages/react-query - '@tanstack/react-query-devtools': link:../../../packages/react-query-devtools - axios: 0.21.4 - react: 18.2.0 - react-dom: 18.2.0_react@18.2.0 - devDependencies: - '@vitejs/plugin-react': 2.1.0_vite@3.1.3 - vite: 3.1.3 - - examples/react/basic-eslint-plugin: specifiers: '@tanstack/eslint-plugin-query': ^1.0.0 '@tanstack/react-query': ^4.7.1 '@tanstack/react-query-devtools': ^4.7.1 '@vitejs/plugin-react': ^2.0.0 axios: ^0.21.1 - eslint: ^8.26.0 react: ^18.0.0 react-dom: ^18.0.0 vite: ^3.0.0 @@ -198,9 +178,8 @@ importers: react-dom: 18.2.0_react@18.2.0 devDependencies: '@tanstack/eslint-plugin-query': link:../../../packages/eslint-plugin-query - '@vitejs/plugin-react': 2.1.0_vite@3.1.8 - eslint: 8.26.0 - vite: 3.1.8 + '@vitejs/plugin-react': 2.1.0_vite@3.1.3 + vite: 3.1.3 examples/react/basic-graphql-request: specifiers: @@ -667,7 +646,7 @@ importers: vite: 3.1.4 vue: 3.2.39 dependencies: - '@tanstack/vue-query': link:../../../packages/vue-query + '@tanstack/vue-query': 4.13.2_vue@3.2.39 vue: 3.2.39 devDependencies: '@vitejs/plugin-vue': 3.1.0_vite@3.1.4+vue@3.2.39 @@ -5510,6 +5489,10 @@ packages: remove-accents: 0.4.2 dev: false + /@tanstack/query-core/4.13.0: + resolution: {integrity: sha512-PzmLQcEgC4rl2OzkiPHYPC9O79DFcMGaKsOzDEP+U4PJ+tbkcEP+Z+FQDlfvX8mCwYC7UNH7hXrQ5EdkGlJjVg==} + dev: false + /@tanstack/react-location/3.7.4_biqbaboplfbrettd7655fr4n2y: resolution: {integrity: sha512-6rH2vNHGr0uyeUz5ZHvWMYjeYKGgIKFzvs5749QtnS9f+FU7t7fQE0hKZAzltBZk82LT7iYbcHBRyUg2lW13VA==} engines: {node: '>=12'} @@ -5523,6 +5506,22 @@ packages: react-dom: 18.2.0_react@18.2.0 dev: false + /@tanstack/vue-query/4.13.2_vue@3.2.39: + resolution: {integrity: sha512-VwEwoiuUzLfeM+wQ9wgz+SHH24RvAy0AZ+C1MXBE6U6RswkcDI73OgXGtrWxP1EuTv+kac6aErfDMLWZyvuomg==} + peerDependencies: + '@vue/composition-api': ^1.1.2 + vue: ^2.5.0 || ^3.0.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + dependencies: + '@tanstack/match-sorter-utils': 8.1.1 + '@tanstack/query-core': 4.13.0 + '@vue/devtools-api': 6.4.2 + vue: 3.2.39 + vue-demi: 0.13.11_vue@3.2.39 + dev: false + /@testing-library/dom/7.31.2: resolution: {integrity: sha512-3UqjCpey6HiTZT92vODYLPxTBWlM8ZOOjr3LX5F37/VRipW2M1kX6I/Cm4VXzteZqfGfagg8yXywpcOgQBlNsQ==} engines: {node: '>=10'} @@ -6295,24 +6294,6 @@ packages: - supports-color dev: true - /@vitejs/plugin-react/2.1.0_vite@3.1.8: - resolution: {integrity: sha512-am6rPyyU3LzUYne3Gd9oj9c4Rzbq5hQnuGXSMT6Gujq45Il/+bunwq3lrB7wghLkiF45ygMwft37vgJ/NE8IAA==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - vite: ^3.0.0 - dependencies: - '@babel/core': 7.19.1 - '@babel/plugin-transform-react-jsx': 7.19.0_@babel+core@7.19.1 - '@babel/plugin-transform-react-jsx-development': 7.18.6_@babel+core@7.19.1 - '@babel/plugin-transform-react-jsx-self': 7.18.6_@babel+core@7.19.1 - '@babel/plugin-transform-react-jsx-source': 7.18.6_@babel+core@7.19.1 - magic-string: 0.26.4 - react-refresh: 0.14.0 - vite: 3.1.8 - transitivePeerDependencies: - - supports-color - dev: true - /@vitejs/plugin-vue/3.1.0_vite@3.1.4+vue@3.2.39: resolution: {integrity: sha512-fmxtHPjSOEIRg6vHYDaem+97iwCUg/uSIaTzp98lhELt2ISOQuDo2hbkBdXod0g15IhfPMQmAxh4heUks2zvDA==} engines: {node: ^14.18.0 || >=16.0.0} @@ -15565,35 +15546,23 @@ packages: fsevents: 2.3.2 dev: true - /vite/3.1.8: - resolution: {integrity: sha512-m7jJe3nufUbuOfotkntGFupinL/fmuTNuQmiVE7cH2IZMuf4UbfbGYMUT3jVWgGYuRVLY9j8NnrRqgw5rr5QTg==} - engines: {node: ^14.18.0 || >=16.0.0} + /vlq/1.0.1: + resolution: {integrity: sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==} + dev: false + + /vue-demi/0.13.11_vue@3.2.39: + resolution: {integrity: sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==} + engines: {node: '>=12'} hasBin: true + requiresBuild: true peerDependencies: - less: '*' - sass: '*' - stylus: '*' - terser: ^5.4.0 + '@vue/composition-api': ^1.0.0-rc.1 + vue: ^3.0.0-0 || ^2.6.0 peerDependenciesMeta: - less: - optional: true - sass: - optional: true - stylus: - optional: true - terser: + '@vue/composition-api': optional: true dependencies: - esbuild: 0.15.9 - postcss: 8.4.16 - resolve: 1.22.1 - rollup: 2.78.1 - optionalDependencies: - fsevents: 2.3.2 - dev: true - - /vlq/1.0.1: - resolution: {integrity: sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==} + vue: 3.2.39 dev: false /vue-demi/0.13.11_zv57lmwz3lpne326jxcwg2uc6q: From 23a447c6692a799ecc7c9b57b0ede7966a0c13b0 Mon Sep 17 00:00:00 2001 From: Newbie012 Date: Mon, 24 Oct 2022 00:15:42 +0300 Subject: [PATCH 06/40] align package version with the other packages --- packages/eslint-plugin-query/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin-query/package.json b/packages/eslint-plugin-query/package.json index 4d153437c9..5b8b57ee08 100644 --- a/packages/eslint-plugin-query/package.json +++ b/packages/eslint-plugin-query/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/eslint-plugin-query", - "version": "1.0.0", + "version": "4.13.0", "description": "ESLint plugin for React Query", "author": "Eliya Cohen", "license": "MIT", From 8f41031b9580725617f577ce4967a1da8010815b Mon Sep 17 00:00:00 2001 From: Newbie012 Date: Mon, 24 Oct 2022 02:14:47 +0300 Subject: [PATCH 07/40] align eslint-plugin-query version --- examples/react/basic/package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/react/basic/package.json b/examples/react/basic/package.json index 354afc5b04..c61ce45c4b 100644 --- a/examples/react/basic/package.json +++ b/examples/react/basic/package.json @@ -8,14 +8,14 @@ "preview": "vite preview" }, "dependencies": { + "@tanstack/react-query": "^4.7.1", + "@tanstack/react-query-devtools": "^4.7.1", "axios": "^0.21.1", "react": "^18.0.0", - "react-dom": "^18.0.0", - "@tanstack/react-query": "^4.7.1", - "@tanstack/react-query-devtools": "^4.7.1" + "react-dom": "^18.0.0" }, "devDependencies": { - "@tanstack/eslint-plugin-query": "^1.0.0", + "@tanstack/eslint-plugin-query": "^4.13.0", "@vitejs/plugin-react": "^2.0.0", "vite": "^3.0.0" }, From a6af4a9a4346d4d675c17b275820c11f4effe65b Mon Sep 17 00:00:00 2001 From: Newbie012 Date: Mon, 24 Oct 2022 02:15:00 +0300 Subject: [PATCH 08/40] fix displayName --- packages/eslint-plugin-query/jest.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin-query/jest.config.ts b/packages/eslint-plugin-query/jest.config.ts index a6563d822f..17a8c2c914 100644 --- a/packages/eslint-plugin-query/jest.config.ts +++ b/packages/eslint-plugin-query/jest.config.ts @@ -1,4 +1,4 @@ export default { - displayName: 'query-core', + displayName: 'eslint-plugin-query', preset: '../../jest-preset.js', } From f2f2c0aaa538ffe730973c5b41af4fecc25ddadf Mon Sep 17 00:00:00 2001 From: Newbie012 Date: Mon, 24 Oct 2022 02:15:11 +0300 Subject: [PATCH 09/40] remove typescript from package --- packages/eslint-plugin-query/package.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/eslint-plugin-query/package.json b/packages/eslint-plugin-query/package.json index 5b8b57ee08..58f982d1ac 100644 --- a/packages/eslint-plugin-query/package.json +++ b/packages/eslint-plugin-query/package.json @@ -36,10 +36,8 @@ "devDependencies": { "@typescript-eslint/eslint-plugin": "^5.40.1", "@typescript-eslint/parser": "^5.40.1", - "@typescript-eslint/types": "^5.40.1", "@typescript-eslint/utils": "^5.40.1", "eslint": "^8.26.0", - "tsup": "^6.3.0", - "typescript": "4.8.4" + "tsup": "^6.3.0" } } From 4da87d9bec5bde52948fd64e5a4f29b9fdff99db Mon Sep 17 00:00:00 2001 From: Newbie012 Date: Mon, 24 Oct 2022 02:16:44 +0300 Subject: [PATCH 10/40] omit call expressions from keys without the need of whitelist --- .../eslint-plugin-query/src/extra-utils.ts | 22 +++++++++++++++++++ .../src/rules/exhaustive-deps.rule.ts | 9 ++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin-query/src/extra-utils.ts b/packages/eslint-plugin-query/src/extra-utils.ts index 08c1b889a6..59ce66e0cd 100644 --- a/packages/eslint-plugin-query/src/extra-utils.ts +++ b/packages/eslint-plugin-query/src/extra-utils.ts @@ -53,4 +53,26 @@ export const ExtraUtils = { return identifiers }, + isAncestorIsCallee(identifier: TSESTree.Node) { + let previousNode = identifier + let currentNode = identifier.parent + + while (currentNode !== undefined) { + if ( + currentNode.type === AST_NODE_TYPES.CallExpression && + currentNode.callee === previousNode + ) { + return true + } + + if (currentNode.type !== AST_NODE_TYPES.MemberExpression) { + return false + } + + previousNode = currentNode + currentNode = currentNode.parent + } + + return false + }, } diff --git a/packages/eslint-plugin-query/src/rules/exhaustive-deps.rule.ts b/packages/eslint-plugin-query/src/rules/exhaustive-deps.rule.ts index 00b2f4ac55..5e2f717abc 100644 --- a/packages/eslint-plugin-query/src/rules/exhaustive-deps.rule.ts +++ b/packages/eslint-plugin-query/src/rules/exhaustive-deps.rule.ts @@ -103,9 +103,14 @@ function runCheck(params: { ExtraUtils.getIdentifiersFromArrayExpression(queryKeyValue) const refs = getExternalRefs({ scopeManager, node: queryFn.value }) const missingRefs = refs + .filter((reference) => { + return ( + !ExtraUtils.isAncestorIsCallee(reference.identifier) && + !ruleOptions.whitelist.includes(reference.identifier.name) && + !queryKeyDeps.some((y) => y.name === reference.identifier.name) + ) + }) .map((ref) => ref.identifier) - .filter((ref) => !ruleOptions.whitelist.includes(ref.name)) - .filter((ref) => !queryKeyDeps.some((y) => y.name === ref.name)) if (missingRefs.length > 0) { context.report({ From 4d571ee2931673908308b93143aefee422035cce Mon Sep 17 00:00:00 2001 From: Newbie012 Date: Mon, 24 Oct 2022 02:22:39 +0300 Subject: [PATCH 11/40] lint queryKey instead of useQuery --- .../eslint-plugin-query/src/extra-utils.ts | 16 +++-- .../src/rules/exhaustive-deps.rule.ts | 30 ++++----- .../src/rules/exhaustive-deps.test.ts | 66 ++++++++++++++----- 3 files changed, 76 insertions(+), 36 deletions(-) diff --git a/packages/eslint-plugin-query/src/extra-utils.ts b/packages/eslint-plugin-query/src/extra-utils.ts index 59ce66e0cd..33a54763f0 100644 --- a/packages/eslint-plugin-query/src/extra-utils.ts +++ b/packages/eslint-plugin-query/src/extra-utils.ts @@ -1,10 +1,16 @@ -import type { TSESTree } from '@typescript-eslint/types' -import { AST_NODE_TYPES } from '@typescript-eslint/types' +import type { TSESTree } from '@typescript-eslint/utils' +import { AST_NODE_TYPES } from '@typescript-eslint/utils' export const ExtraUtils = { isIdentifier(node: TSESTree.Node): node is TSESTree.Identifier { return node.type === AST_NODE_TYPES.Identifier }, + isIdentifierWithName( + node: TSESTree.Node, + name: string, + ): node is TSESTree.Identifier { + return ExtraUtils.isIdentifier(node) && node.name === name + }, isLiteral(node: TSESTree.Node): node is TSESTree.Literal { return node.type === AST_NODE_TYPES.Literal }, @@ -14,14 +20,16 @@ export const ExtraUtils = { isProperty(node: TSESTree.Node): node is TSESTree.Property { return node.type === AST_NODE_TYPES.Property }, + isObjectExpression(node: TSESTree.Node): node is TSESTree.ObjectExpression { + return node.type === AST_NODE_TYPES.ObjectExpression + }, isPropertyWithIdentifierKey( node: TSESTree.Node, key: string, ): node is TSESTree.Property { return ( ExtraUtils.isProperty(node) && - ExtraUtils.isIdentifier(node.key) && - node.key.name === key + ExtraUtils.isIdentifierWithName(node.key, key) ) }, findPropertyWithIdentifierKey( diff --git a/packages/eslint-plugin-query/src/rules/exhaustive-deps.rule.ts b/packages/eslint-plugin-query/src/rules/exhaustive-deps.rule.ts index 5e2f717abc..de5586c80d 100644 --- a/packages/eslint-plugin-query/src/rules/exhaustive-deps.rule.ts +++ b/packages/eslint-plugin-query/src/rules/exhaustive-deps.rule.ts @@ -24,7 +24,7 @@ const ruleSchema: JSONSchema.JSONSchema4 = { }, } -const defaultRuleOptions = { whitelist: ['api', 'fetch', 'axios'] } +const defaultRuleOptions = { whitelist: [] } type RuleMessage = keyof typeof messages type RuleOptions = { whitelist?: string[] } @@ -50,11 +50,8 @@ export const exhaustiveDepsRule = createRule<[RuleOptions], RuleMessage>({ defaultOptions: [defaultRuleOptions], create(context) { return { - CallExpression(node) { - if ( - ExtraUtils.isIdentifier(node.callee) && - node.callee.name === 'useQuery' - ) { + Property(node) { + if (ExtraUtils.isIdentifierWithName(node.key, 'queryKey')) { runCheck({ node, context }) } }, @@ -62,25 +59,24 @@ export const exhaustiveDepsRule = createRule<[RuleOptions], RuleMessage>({ }, }) -function runCheck(params: { - node: TSESTree.CallExpression - context: RuleContext -}) { +function runCheck(params: { node: TSESTree.Property; context: RuleContext }) { const { node, context } = params - const queryOptions = node.arguments[0] const ruleOptions = { ...defaultRuleOptions, ...context.options[0] } - if (queryOptions?.type !== AST_NODE_TYPES.ObjectExpression) { + if ( + node.parent === undefined || + !ExtraUtils.isObjectExpression(node.parent) + ) { return } const scopeManager = context.getSourceCode().scopeManager const queryKey = ExtraUtils.findPropertyWithIdentifierKey( - queryOptions.properties, + node.parent.properties, 'queryKey', ) const queryFn = ExtraUtils.findPropertyWithIdentifierKey( - queryOptions.properties, + node.parent.properties, 'queryFn', ) @@ -114,7 +110,7 @@ function runCheck(params: { if (missingRefs.length > 0) { context.report({ - node: node.callee, + node: node, messageId: 'missingDeps', data: { deps: missingRefs.map(nodeToText).join(', '), @@ -165,7 +161,9 @@ function getExternalRefs(params: { } const readOnlyRefs = scope.references.filter((x) => x.isRead()) - const localRefIds = new Set([...scope.set.values()].map((x) => x.identifiers[0])) + const localRefIds = new Set( + [...scope.set.values()].map((x) => x.identifiers[0]), + ) const externalRefs = readOnlyRefs.filter( (x) => x.resolved === null || !localRefIds.has(x.resolved.identifiers[0]), ) diff --git a/packages/eslint-plugin-query/src/rules/exhaustive-deps.test.ts b/packages/eslint-plugin-query/src/rules/exhaustive-deps.test.ts index 9a4e186c15..9b9ea44d56 100644 --- a/packages/eslint-plugin-query/src/rules/exhaustive-deps.test.ts +++ b/packages/eslint-plugin-query/src/rules/exhaustive-deps.test.ts @@ -8,6 +8,10 @@ const ruleTester = new ESLintUtils.RuleTester({ ruleTester.run('exhaustive-deps', exhaustiveDepsRule, { valid: [ + { + name: 'should pass when deps are passed in array', + code: 'useQuery({ queryKey: ["todos"], queryFn: fetchTodos });', + }, { name: 'should pass when deps are passed in array', code: 'useQuery({ queryKey: ["entity", id], queryFn: () => api.getEntity(id) });', @@ -19,42 +23,72 @@ ruleTester.run('exhaustive-deps', exhaustiveDepsRule, { }, { name: 'should not pass fetch', - code: 'useQuery({ queryKey: ["entity", id], queryFn: () => fetch("...") });', + code: 'useQuery({ queryKey: ["entity", id], queryFn: () => fetch(id) });', }, { - name: 'should not pass axios', - code: 'useQuery({ queryKey: ["entity", id], queryFn: () => axios.get("...") });', + name: 'should not pass axios.get', + code: 'useQuery({ queryKey: ["entity", id], queryFn: () => axios.get(id) });', }, { - name: 'should not pass api', - code: 'useQuery({ queryKey: ["entity", id], queryFn: () => api.get("...") });', + name: 'should not pass api.entity.get', + code: 'useQuery({ queryKey: ["entity", id], queryFn: () => api.entity.get(id) });', }, ], invalid: [ { - name: 'should fail when no deps are passed', + name: 'should fail when deps are missing in query factory', code: ` - const id = 1; - useQuery({ queryKey: ["entity"], queryFn: () => api.getEntity(id) }); + const todoQueries = { + list: () => ({ queryKey: ['entity'], queryFn: fetchEntities }), + detail: (id) => ({ queryKey: ['entity'], queryFn: () => fetchEntity(id) }) + } `, output: ` - const id = 1; - useQuery({ queryKey: ["entity", id], queryFn: () => api.getEntity(id) }); + const todoQueries = { + list: () => ({ queryKey: ['entity'], queryFn: fetchEntities }), + detail: (id) => ({ queryKey: ['entity', id], queryFn: () => fetchEntity(id) }) + } `, errors: [{ messageId: 'missingDeps', data: { deps: 'id' } }], }, + { + name: 'should fail when no deps are passed', + code: ` + const id = 1; + useQuery({ queryKey: ["entity"], queryFn: () => api.getEntity(id) }); + `, + output: ` + const id = 1; + useQuery({ queryKey: ["entity", id], queryFn: () => api.getEntity(id) }); + `, + errors: [{ messageId: 'missingDeps', data: { deps: 'id' } }], + }, { name: 'should fail when deps are passed incorrectly', code: ` - const id = 1; - useQuery({ queryKey: ["entity/\${id}"], queryFn: () => api.getEntity(id) }); - `, + const id = 1; + useQuery({ queryKey: ["entity/\${id}"], queryFn: () => api.getEntity(id) }); + `, output: ` - const id = 1; - useQuery({ queryKey: ["entity/\${id}", id], queryFn: () => api.getEntity(id) }); - `, + const id = 1; + useQuery({ queryKey: ["entity/\${id}", id], queryFn: () => api.getEntity(id) }); + `, errors: [{ messageId: 'missingDeps', data: { deps: 'id' } }], }, + { + name: 'should pass missing dep while key has a template literal', + code: ` + const a = 1; + const b = 2; + useQuery({ queryKey: [\`entity/\${a}\`], queryFn: () => api.getEntity(a, b) }); + `, + output: ` + const a = 1; + const b = 2; + useQuery({ queryKey: [\`entity/\${a}\`, b], queryFn: () => api.getEntity(a, b) }); + `, + errors: [{ messageId: 'missingDeps', data: { deps: 'b' } }], + }, { name: 'should fail when dep exists inside setter and missing in queryKey', code: ` From 3f7e1c761c25e7f007c281a2378b602ed33577c5 Mon Sep 17 00:00:00 2001 From: Newbie012 Date: Mon, 24 Oct 2022 02:26:28 +0300 Subject: [PATCH 12/40] update pnpm-lock.yaml --- pnpm-lock.yaml | 62 +++++++++++++++++++++----------------------------- 1 file changed, 26 insertions(+), 36 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 67a0c5d269..08ee69cf97 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -162,7 +162,7 @@ importers: examples/react/basic: specifiers: - '@tanstack/eslint-plugin-query': ^1.0.0 + '@tanstack/eslint-plugin-query': ^4.13.0 '@tanstack/react-query': ^4.7.1 '@tanstack/react-query-devtools': ^4.7.1 '@vitejs/plugin-react': ^2.0.0 @@ -657,19 +657,15 @@ importers: specifiers: '@typescript-eslint/eslint-plugin': ^5.40.1 '@typescript-eslint/parser': ^5.40.1 - '@typescript-eslint/types': ^5.40.1 '@typescript-eslint/utils': ^5.40.1 eslint: ^8.26.0 tsup: ^6.3.0 - typescript: 4.8.4 devDependencies: - '@typescript-eslint/eslint-plugin': 5.40.1_c4zyna56jjjrggqkyejnaxjxfu - '@typescript-eslint/parser': 5.40.1_wyqvi574yv7oiwfeinomdzmc3m - '@typescript-eslint/types': 5.40.1 - '@typescript-eslint/utils': 5.40.1_wyqvi574yv7oiwfeinomdzmc3m + '@typescript-eslint/eslint-plugin': 5.40.1_yxvyo6v5zwzggfecalzmx54lre + '@typescript-eslint/parser': 5.40.1_eslint@8.26.0 + '@typescript-eslint/utils': 5.40.1_eslint@8.26.0 eslint: 8.26.0 - tsup: 6.3.0_typescript@4.8.4 - typescript: 4.8.4 + tsup: 6.3.0 packages/query-async-storage-persister: specifiers: @@ -5914,7 +5910,7 @@ packages: - supports-color dev: true - /@typescript-eslint/eslint-plugin/5.40.1_c4zyna56jjjrggqkyejnaxjxfu: + /@typescript-eslint/eslint-plugin/5.40.1_yxvyo6v5zwzggfecalzmx54lre: resolution: {integrity: sha512-FsWboKkWdytGiXT5O1/R9j37YgcjO8MKHSUmWnIEjVaz0krHkplPnYi7mwdb+5+cs0toFNQb0HIrN7zONdIEWg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -5925,17 +5921,16 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/parser': 5.40.1_wyqvi574yv7oiwfeinomdzmc3m + '@typescript-eslint/parser': 5.40.1_eslint@8.26.0 '@typescript-eslint/scope-manager': 5.40.1 - '@typescript-eslint/type-utils': 5.40.1_wyqvi574yv7oiwfeinomdzmc3m - '@typescript-eslint/utils': 5.40.1_wyqvi574yv7oiwfeinomdzmc3m + '@typescript-eslint/type-utils': 5.40.1_eslint@8.26.0 + '@typescript-eslint/utils': 5.40.1_eslint@8.26.0 debug: 4.3.4 eslint: 8.26.0 ignore: 5.2.0 regexpp: 3.2.0 semver: 7.3.7 - tsutils: 3.21.0_typescript@4.8.4 - typescript: 4.8.4 + tsutils: 3.21.0 transitivePeerDependencies: - supports-color dev: true @@ -6014,7 +6009,7 @@ packages: - supports-color dev: true - /@typescript-eslint/parser/5.40.1_wyqvi574yv7oiwfeinomdzmc3m: + /@typescript-eslint/parser/5.40.1_eslint@8.26.0: resolution: {integrity: sha512-IK6x55va5w4YvXd4b3VrXQPldV9vQTxi5ov+g4pMANsXPTXOcfjx08CRR1Dfrcc51syPtXHF5bgLlMHYFrvQtg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -6026,10 +6021,9 @@ packages: dependencies: '@typescript-eslint/scope-manager': 5.40.1 '@typescript-eslint/types': 5.40.1 - '@typescript-eslint/typescript-estree': 5.40.1_typescript@4.8.4 + '@typescript-eslint/typescript-estree': 5.40.1 debug: 4.3.4 eslint: 8.26.0 - typescript: 4.8.4 transitivePeerDependencies: - supports-color dev: true @@ -6077,7 +6071,7 @@ packages: - supports-color dev: true - /@typescript-eslint/type-utils/5.40.1_wyqvi574yv7oiwfeinomdzmc3m: + /@typescript-eslint/type-utils/5.40.1_eslint@8.26.0: resolution: {integrity: sha512-DLAs+AHQOe6n5LRraXiv27IYPhleF0ldEmx6yBqBgBLaNRKTkffhV1RPsjoJBhVup2zHxfaRtan8/YRBgYhU9Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -6087,12 +6081,11 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': 5.40.1_typescript@4.8.4 - '@typescript-eslint/utils': 5.40.1_wyqvi574yv7oiwfeinomdzmc3m + '@typescript-eslint/typescript-estree': 5.40.1 + '@typescript-eslint/utils': 5.40.1_eslint@8.26.0 debug: 4.3.4 eslint: 8.26.0 - tsutils: 3.21.0_typescript@4.8.4 - typescript: 4.8.4 + tsutils: 3.21.0 transitivePeerDependencies: - supports-color dev: true @@ -6175,7 +6168,7 @@ packages: - supports-color dev: true - /@typescript-eslint/typescript-estree/5.40.1_typescript@4.8.4: + /@typescript-eslint/typescript-estree/5.40.1: resolution: {integrity: sha512-5QTP/nW5+60jBcEPfXy/EZL01qrl9GZtbgDZtDPlfW5zj/zjNrdI2B5zMUHmOsfvOr2cWqwVdWjobCiHcedmQA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -6190,8 +6183,7 @@ packages: globby: 11.1.0 is-glob: 4.0.3 semver: 7.3.7 - tsutils: 3.21.0_typescript@4.8.4 - typescript: 4.8.4 + tsutils: 3.21.0 transitivePeerDependencies: - supports-color dev: true @@ -6214,7 +6206,7 @@ packages: - typescript dev: true - /@typescript-eslint/utils/5.40.1_wyqvi574yv7oiwfeinomdzmc3m: + /@typescript-eslint/utils/5.40.1_eslint@8.26.0: resolution: {integrity: sha512-a2TAVScoX9fjryNrW6BZRnreDUszxqm9eQ9Esv8n5nXApMW0zeANUYlwh/DED04SC/ifuBvXgZpIK5xeJHQ3aw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -6224,7 +6216,7 @@ packages: '@types/semver': 7.3.12 '@typescript-eslint/scope-manager': 5.40.1 '@typescript-eslint/types': 5.40.1 - '@typescript-eslint/typescript-estree': 5.40.1_typescript@4.8.4 + '@typescript-eslint/typescript-estree': 5.40.1 eslint: 8.26.0 eslint-scope: 5.1.1 eslint-utils: 3.0.0_eslint@8.26.0 @@ -15112,7 +15104,7 @@ packages: /tslib/2.4.0: resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==} - /tsup/6.3.0_typescript@4.8.4: + /tsup/6.3.0: resolution: {integrity: sha512-IaNQO/o1rFgadLhNonVKNCT2cks+vvnWX3DnL8sB87lBDqRvJXHENr5lSPJlqwplUlDxSwZK8dSg87rgBu6Emw==} engines: {node: '>=14'} hasBin: true @@ -15142,40 +15134,38 @@ packages: source-map: 0.8.0-beta.0 sucrase: 3.23.0 tree-kill: 1.2.2 - typescript: 4.8.4 transitivePeerDependencies: - supports-color - ts-node dev: true - /tsutils/3.21.0_typescript@4.4.4: + /tsutils/3.21.0: resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} peerDependencies: typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' dependencies: tslib: 1.14.1 - typescript: 4.4.4 dev: true - /tsutils/3.21.0_typescript@4.8.3: + /tsutils/3.21.0_typescript@4.4.4: resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} peerDependencies: typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' dependencies: tslib: 1.14.1 - typescript: 4.8.3 + typescript: 4.4.4 dev: true - /tsutils/3.21.0_typescript@4.8.4: + /tsutils/3.21.0_typescript@4.8.3: resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} peerDependencies: typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' dependencies: tslib: 1.14.1 - typescript: 4.8.4 + typescript: 4.8.3 dev: true /type-check/0.3.2: From dc8c0eb44f3114e1ace9ea492d5f074fd2e988ee Mon Sep 17 00:00:00 2001 From: Newbie012 Date: Tue, 25 Oct 2022 01:49:09 +0300 Subject: [PATCH 13/40] fix auto-fix suggestion for complex ast --- packages/eslint-plugin-query/package.json | 1 + .../eslint-plugin-query/src/extra-utils.ts | 46 +++++++++++++++ .../src/rules/exhaustive-deps.rule.ts | 46 +++++---------- .../src/rules/exhaustive-deps.test.ts | 20 +++++++ pnpm-lock.yaml | 56 +++++++------------ 5 files changed, 102 insertions(+), 67 deletions(-) diff --git a/packages/eslint-plugin-query/package.json b/packages/eslint-plugin-query/package.json index 58f982d1ac..64c594a8c3 100644 --- a/packages/eslint-plugin-query/package.json +++ b/packages/eslint-plugin-query/package.json @@ -38,6 +38,7 @@ "@typescript-eslint/parser": "^5.40.1", "@typescript-eslint/utils": "^5.40.1", "eslint": "^8.26.0", + "recast": "^0.21.5", "tsup": "^6.3.0" } } diff --git a/packages/eslint-plugin-query/src/extra-utils.ts b/packages/eslint-plugin-query/src/extra-utils.ts index 33a54763f0..b8412207b0 100644 --- a/packages/eslint-plugin-query/src/extra-utils.ts +++ b/packages/eslint-plugin-query/src/extra-utils.ts @@ -40,6 +40,37 @@ export const ExtraUtils = { ExtraUtils.isPropertyWithIdentifierKey(x, key), ) as TSESTree.Property | undefined }, + getIdentifiersRecursive(node: TSESTree.Node): TSESTree.Identifier[] { + const identifiers: TSESTree.Identifier[] = [] + + if (ExtraUtils.isIdentifier(node)) { + identifiers.push(node) + } + + if (node.type === AST_NODE_TYPES.ArrayExpression) { + node.elements.forEach((x) => { + identifiers.push(...ExtraUtils.getIdentifiersRecursive(x)) + }) + } + + if (node.type === AST_NODE_TYPES.ObjectExpression) { + node.properties.forEach((x) => { + identifiers.push(...ExtraUtils.getIdentifiersRecursive(x)) + }) + } + + if (node.type === AST_NODE_TYPES.Property) { + identifiers.push(...ExtraUtils.getIdentifiersRecursive(node.value)) + } + + if (node.type === AST_NODE_TYPES.TemplateLiteral) { + node.expressions.forEach((x) => { + identifiers.push(...ExtraUtils.getIdentifiersRecursive(x)) + }) + } + + return identifiers + }, getIdentifiersFromArrayExpression( node: TSESTree.ArrayExpression, ): TSESTree.Identifier[] { @@ -83,4 +114,19 @@ export const ExtraUtils = { return false }, + getNodeLiteralQuote(node: TSESTree.Node): 'single' | 'double' | 'auto' { + if (node.type === AST_NODE_TYPES.Literal) { + return node.raw.startsWith("'") ? 'single' : 'double' + } + + return 'auto' + }, + builder: { + identifier: (name: string): TSESTree.Identifier => ({ + type: AST_NODE_TYPES.Identifier, + loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } }, + range: [0, name.length], + name, + }), + }, } diff --git a/packages/eslint-plugin-query/src/rules/exhaustive-deps.rule.ts b/packages/eslint-plugin-query/src/rules/exhaustive-deps.rule.ts index de5586c80d..78fb304c67 100644 --- a/packages/eslint-plugin-query/src/rules/exhaustive-deps.rule.ts +++ b/packages/eslint-plugin-query/src/rules/exhaustive-deps.rule.ts @@ -1,5 +1,6 @@ import type { JSONSchema, TSESLint, TSESTree } from '@typescript-eslint/utils' import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils' +import * as recast from 'recast' import { ExtraUtils } from '../extra-utils' const messages = { @@ -95,8 +96,7 @@ function runCheck(params: { node: TSESTree.Property; context: RuleContext }) { } const queryKeyValue = queryKey.value - const queryKeyDeps = - ExtraUtils.getIdentifiersFromArrayExpression(queryKeyValue) + const queryKeyDeps = ExtraUtils.getIdentifiersRecursive(queryKeyValue) const refs = getExternalRefs({ scopeManager, node: queryFn.value }) const missingRefs = refs .filter((reference) => { @@ -113,42 +113,26 @@ function runCheck(params: { node: TSESTree.Property; context: RuleContext }) { node: node, messageId: 'missingDeps', data: { - deps: missingRefs.map(nodeToText).join(', '), + deps: missingRefs.map((ref) => ref.name).join(', '), }, fix(fixer) { - const relevantQueryKeyDeps = queryKeyValue.elements.filter( - (element) => - ExtraUtils.isIdentifier(element) || ExtraUtils.isLiteral(element), - ) - const newQueryKeyDeps = [...relevantQueryKeyDeps, ...missingRefs] - - return fixer.replaceText( - queryKeyValue, - `[${newQueryKeyDeps.map((x) => nodeToText(x)).join(', ')}]`, - ) + const newQueryKeyText = [ + ...queryKeyValue.elements, + ...missingRefs.map((ref) => ExtraUtils.builder.identifier(ref.name)), + ] + .map((x) => + recast + .print(x, { quote: ExtraUtils.getNodeLiteralQuote(x) }) + .code.replace(/\n\s{4}([^\s]*)\n/gm, ' $1 '), + ) + .join(', ') + + return fixer.replaceText(queryKeyValue, `[${newQueryKeyText}]`) }, }) } } -function nodeToText(node: TSESTree.Node): string | null { - if (node.type === AST_NODE_TYPES.Identifier) { - return node.name - } - - if (node.type === AST_NODE_TYPES.Literal) { - return node.raw - } - - if (node.type === AST_NODE_TYPES.TemplateLiteral) { - const expressions = node.expressions.map((x) => nodeToText(x)) - const quasis = node.quasis.map((x) => x.value.raw) - return quasis.reduce((acc, x, i) => acc + x + (expressions[i] ?? ''), '') - } - - return null -} - function getExternalRefs(params: { scopeManager: TSESLint.Scope.ScopeManager node: TSESTree.Node diff --git a/packages/eslint-plugin-query/src/rules/exhaustive-deps.test.ts b/packages/eslint-plugin-query/src/rules/exhaustive-deps.test.ts index 9b9ea44d56..b6359695a4 100644 --- a/packages/eslint-plugin-query/src/rules/exhaustive-deps.test.ts +++ b/packages/eslint-plugin-query/src/rules/exhaustive-deps.test.ts @@ -113,5 +113,25 @@ ruleTester.run('exhaustive-deps', exhaustiveDepsRule, { `, errors: [{ messageId: 'missingDeps', data: { deps: 'id' } }], }, + { + name: 'should fail when dep does not exist while having a complex queryKey', + code: ` + const todoQueries = { + key: (a, b, c, d, e) => ({ + queryKey: ["entity", a, [b], { c }, 1, true], + queryFn: () => api.getEntity(a, b, c, d, e) + }) + } + `, + output: ` + const todoQueries = { + key: (a, b, c, d, e) => ({ + queryKey: ["entity", a, [b], { c }, 1, true, d, e], + queryFn: () => api.getEntity(a, b, c, d, e) + }) + } + `, + errors: [{ messageId: 'missingDeps', data: { deps: 'd, e' } }], + }, ], }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 08ee69cf97..87f54c8455 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -646,7 +646,7 @@ importers: vite: 3.1.4 vue: 3.2.39 dependencies: - '@tanstack/vue-query': 4.13.2_vue@3.2.39 + '@tanstack/vue-query': link:../../../packages/vue-query vue: 3.2.39 devDependencies: '@vitejs/plugin-vue': 3.1.0_vite@3.1.4+vue@3.2.39 @@ -659,12 +659,14 @@ importers: '@typescript-eslint/parser': ^5.40.1 '@typescript-eslint/utils': ^5.40.1 eslint: ^8.26.0 + recast: ^0.21.5 tsup: ^6.3.0 devDependencies: '@typescript-eslint/eslint-plugin': 5.40.1_yxvyo6v5zwzggfecalzmx54lre '@typescript-eslint/parser': 5.40.1_eslint@8.26.0 '@typescript-eslint/utils': 5.40.1_eslint@8.26.0 eslint: 8.26.0 + recast: 0.21.5 tsup: 6.3.0 packages/query-async-storage-persister: @@ -5485,10 +5487,6 @@ packages: remove-accents: 0.4.2 dev: false - /@tanstack/query-core/4.13.0: - resolution: {integrity: sha512-PzmLQcEgC4rl2OzkiPHYPC9O79DFcMGaKsOzDEP+U4PJ+tbkcEP+Z+FQDlfvX8mCwYC7UNH7hXrQ5EdkGlJjVg==} - dev: false - /@tanstack/react-location/3.7.4_biqbaboplfbrettd7655fr4n2y: resolution: {integrity: sha512-6rH2vNHGr0uyeUz5ZHvWMYjeYKGgIKFzvs5749QtnS9f+FU7t7fQE0hKZAzltBZk82LT7iYbcHBRyUg2lW13VA==} engines: {node: '>=12'} @@ -5502,22 +5500,6 @@ packages: react-dom: 18.2.0_react@18.2.0 dev: false - /@tanstack/vue-query/4.13.2_vue@3.2.39: - resolution: {integrity: sha512-VwEwoiuUzLfeM+wQ9wgz+SHH24RvAy0AZ+C1MXBE6U6RswkcDI73OgXGtrWxP1EuTv+kac6aErfDMLWZyvuomg==} - peerDependencies: - '@vue/composition-api': ^1.1.2 - vue: ^2.5.0 || ^3.0.0 - peerDependenciesMeta: - '@vue/composition-api': - optional: true - dependencies: - '@tanstack/match-sorter-utils': 8.1.1 - '@tanstack/query-core': 4.13.0 - '@vue/devtools-api': 6.4.2 - vue: 3.2.39 - vue-demi: 0.13.11_vue@3.2.39 - dev: false - /@testing-library/dom/7.31.2: resolution: {integrity: sha512-3UqjCpey6HiTZT92vODYLPxTBWlM8ZOOjr3LX5F37/VRipW2M1kX6I/Cm4VXzteZqfGfagg8yXywpcOgQBlNsQ==} engines: {node: '>=10'} @@ -6867,6 +6849,13 @@ packages: dependencies: tslib: 2.4.0 + /ast-types/0.15.2: + resolution: {integrity: sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg==} + engines: {node: '>=4'} + dependencies: + tslib: 2.4.0 + dev: true + /astral-regex/1.0.0: resolution: {integrity: sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==} engines: {node: '>=4'} @@ -13787,6 +13776,16 @@ packages: source-map: 0.6.1 tslib: 2.4.0 + /recast/0.21.5: + resolution: {integrity: sha512-hjMmLaUXAm1hIuTqOdeYObMslq/q+Xff6QE3Y2P+uoHAg2nmVlLBps2hzh1UJDdMtDTMXOFewK6ky51JQIeECg==} + engines: {node: '>= 4'} + dependencies: + ast-types: 0.15.2 + esprima: 4.0.1 + source-map: 0.6.1 + tslib: 2.4.0 + dev: true + /rechoir/0.6.2: resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} engines: {node: '>= 0.10'} @@ -15540,21 +15539,6 @@ packages: resolution: {integrity: sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==} dev: false - /vue-demi/0.13.11_vue@3.2.39: - resolution: {integrity: sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==} - engines: {node: '>=12'} - hasBin: true - requiresBuild: true - peerDependencies: - '@vue/composition-api': ^1.0.0-rc.1 - vue: ^3.0.0-0 || ^2.6.0 - peerDependenciesMeta: - '@vue/composition-api': - optional: true - dependencies: - vue: 3.2.39 - dev: false - /vue-demi/0.13.11_zv57lmwz3lpne326jxcwg2uc6q: resolution: {integrity: sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==} engines: {node: '>=12'} From 59214a83932f48ccaa50c9f615215f381dae9fe3 Mon Sep 17 00:00:00 2001 From: Newbie012 Date: Tue, 25 Oct 2022 02:23:57 +0300 Subject: [PATCH 14/40] better inline query key --- .../src/rules/exhaustive-deps.rule.ts | 14 ++++++++++--- .../src/rules/exhaustive-deps.test.ts | 20 +++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin-query/src/rules/exhaustive-deps.rule.ts b/packages/eslint-plugin-query/src/rules/exhaustive-deps.rule.ts index 78fb304c67..df78a811a8 100644 --- a/packages/eslint-plugin-query/src/rules/exhaustive-deps.rule.ts +++ b/packages/eslint-plugin-query/src/rules/exhaustive-deps.rule.ts @@ -121,9 +121,10 @@ function runCheck(params: { node: TSESTree.Property; context: RuleContext }) { ...missingRefs.map((ref) => ExtraUtils.builder.identifier(ref.name)), ] .map((x) => - recast - .print(x, { quote: ExtraUtils.getNodeLiteralQuote(x) }) - .code.replace(/\n\s{4}([^\s]*)\n/gm, ' $1 '), + inlineCode( + recast.print(x, { quote: ExtraUtils.getNodeLiteralQuote(x) }) + .code, + ), ) .join(', ') @@ -133,6 +134,13 @@ function runCheck(params: { node: TSESTree.Property; context: RuleContext }) { } } +function inlineCode(code: string) { + return code + .replace(/[\n\s]/gm, '') + .replace(/([,:])/gm, '$1 ') + .replace(/^(?:){(.*)}/gm, '{ $1 }') +} + function getExternalRefs(params: { scopeManager: TSESLint.Scope.ScopeManager node: TSESTree.Node diff --git a/packages/eslint-plugin-query/src/rules/exhaustive-deps.test.ts b/packages/eslint-plugin-query/src/rules/exhaustive-deps.test.ts index b6359695a4..db24c1ac35 100644 --- a/packages/eslint-plugin-query/src/rules/exhaustive-deps.test.ts +++ b/packages/eslint-plugin-query/src/rules/exhaustive-deps.test.ts @@ -133,5 +133,25 @@ ruleTester.run('exhaustive-deps', exhaustiveDepsRule, { `, errors: [{ messageId: 'missingDeps', data: { deps: 'd, e' } }], }, + { + name: 'should fail when dep does not exist while having a complex queryKey #2', + code: ` + const todoQueries = { + key: (dep1, dep2, dep3, dep4, dep5, dep6, dep7, dep8) => ({ + queryKey: ['foo', {dep1, dep2: dep2, bar: dep3, baz: [dep4, dep5]}, [dep6, dep7]], + queryFn: () => api.getEntity(dep1, dep2, dep3, dep4, dep5, dep6, dep7, dep8), + }), + }; + `, + output: ` + const todoQueries = { + key: (dep1, dep2, dep3, dep4, dep5, dep6, dep7, dep8) => ({ + queryKey: ['foo', { dep1, dep2: dep2, bar: dep3, baz: [dep4, dep5] }, [dep6, dep7], dep8], + queryFn: () => api.getEntity(dep1, dep2, dep3, dep4, dep5, dep6, dep7, dep8), + }), + }; + `, + errors: [{ messageId: 'missingDeps', data: { deps: 'dep8' } }], + }, ], }) From a78f85fbf5c190dd504ae1892a4a74d1452d5b88 Mon Sep 17 00:00:00 2001 From: Newbie012 Date: Tue, 25 Oct 2022 02:25:38 +0300 Subject: [PATCH 15/40] fix typo --- packages/eslint-plugin-query/src/rules/exhaustive-deps.rule.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin-query/src/rules/exhaustive-deps.rule.ts b/packages/eslint-plugin-query/src/rules/exhaustive-deps.rule.ts index df78a811a8..7f3722d0e3 100644 --- a/packages/eslint-plugin-query/src/rules/exhaustive-deps.rule.ts +++ b/packages/eslint-plugin-query/src/rules/exhaustive-deps.rule.ts @@ -36,7 +36,7 @@ const createRule = ESLintUtils.RuleCreator( ) export const exhaustiveDepsRule = createRule<[RuleOptions], RuleMessage>({ - name: 'exhausitive-deps', + name: 'exhaustive-deps', meta: { docs: { description: From 776bca612e8b7d553a8177b6a3076346632642cb Mon Sep 17 00:00:00 2001 From: Newbie012 Date: Tue, 25 Oct 2022 12:41:51 +0300 Subject: [PATCH 16/40] update scripts --- packages/eslint-plugin-query/package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin-query/package.json b/packages/eslint-plugin-query/package.json index 64c594a8c3..4beb22b326 100644 --- a/packages/eslint-plugin-query/package.json +++ b/packages/eslint-plugin-query/package.json @@ -15,9 +15,10 @@ "typecheck": "tsc", "build": "tsup --minify", "dev": "tsup --watch --sourcemap", + "clean": "rm -rf ./build", "test:jest": "../../node_modules/.bin/jest --config ./jest.config.ts", - "test:dev": "pnpm run test:jest --watch", - "test:eslint": "eslint --ext .js,.jsx,.ts,.tsx src" + "test:eslint": "../../node_modules/.bin/eslint --ext .ts,.tsx ./src", + "test:dev": "pnpm run test:jest --watch" }, "files": [ "build" From 6a714366ea84ce5111829e19537553a3a6cb13be Mon Sep 17 00:00:00 2001 From: Newbie012 Date: Tue, 25 Oct 2022 12:54:18 +0300 Subject: [PATCH 17/40] restore .eslintrc --- packages/eslint-plugin-query/.eslintrc | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 packages/eslint-plugin-query/.eslintrc diff --git a/packages/eslint-plugin-query/.eslintrc b/packages/eslint-plugin-query/.eslintrc new file mode 100644 index 0000000000..067a57a416 --- /dev/null +++ b/packages/eslint-plugin-query/.eslintrc @@ -0,0 +1,6 @@ +{ + "parserOptions": { + "project": "./tsconfig.json", + "sourceType": "module" + } +} From 02a062c7eff03f0d055896e52e944dffb240e732 Mon Sep 17 00:00:00 2001 From: Newbie012 Date: Tue, 25 Oct 2022 12:54:52 +0300 Subject: [PATCH 18/40] update typescript-eslint packages --- package.json | 4 +- packages/eslint-plugin-query/package.json | 6 +- pnpm-lock.yaml | 195 ++++++++++------------ 3 files changed, 93 insertions(+), 112 deletions(-) diff --git a/package.json b/package.json index e7400db44b..0687348427 100644 --- a/package.json +++ b/package.json @@ -44,8 +44,8 @@ "@types/react": "^18.0.14", "@types/react-dom": "^18.0.5", "@types/testing-library__jest-dom": "^5.14.5", - "@typescript-eslint/eslint-plugin": "^5.6.0", - "@typescript-eslint/parser": "^5.6.0", + "@typescript-eslint/eslint-plugin": "^5.41.0", + "@typescript-eslint/parser": "^5.41.0", "axios": "^0.26.1", "babel-eslint": "^10.1.0", "babel-jest": "^27.5.1", diff --git a/packages/eslint-plugin-query/package.json b/packages/eslint-plugin-query/package.json index 4beb22b326..8770b0ca43 100644 --- a/packages/eslint-plugin-query/package.json +++ b/packages/eslint-plugin-query/package.json @@ -35,9 +35,9 @@ "outDir": "build/lib" }, "devDependencies": { - "@typescript-eslint/eslint-plugin": "^5.40.1", - "@typescript-eslint/parser": "^5.40.1", - "@typescript-eslint/utils": "^5.40.1", + "@typescript-eslint/eslint-plugin": "^5.41.0", + "@typescript-eslint/parser": "^5.41.0", + "@typescript-eslint/utils": "^5.41.0", "eslint": "^8.26.0", "recast": "^0.21.5", "tsup": "^6.3.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 87f54c8455..88f4baa8ff 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -25,8 +25,8 @@ importers: '@types/react': ^18.0.14 '@types/react-dom': ^18.0.5 '@types/testing-library__jest-dom': ^5.14.5 - '@typescript-eslint/eslint-plugin': ^5.6.0 - '@typescript-eslint/parser': ^5.6.0 + '@typescript-eslint/eslint-plugin': ^5.41.0 + '@typescript-eslint/parser': ^5.41.0 axios: ^0.26.1 babel-eslint: ^10.1.0 babel-jest: ^27.5.1 @@ -94,8 +94,8 @@ importers: '@types/react': 18.0.15 '@types/react-dom': 18.0.6 '@types/testing-library__jest-dom': 5.14.5 - '@typescript-eslint/eslint-plugin': 5.30.5_6iifm7pgb2pmejrcuwv2knz73e - '@typescript-eslint/parser': 5.30.5_dyxdave6dwjbccc5dgiifcmuza + '@typescript-eslint/eslint-plugin': 5.41.0_b2esjfxatoxyearpbj3ml2e4hy + '@typescript-eslint/parser': 5.41.0_dyxdave6dwjbccc5dgiifcmuza axios: 0.26.1 babel-eslint: 10.1.0_eslint@7.32.0 babel-jest: 27.5.1_@babel+core@7.19.1 @@ -106,12 +106,12 @@ importers: current-git-branch: 1.1.0 eslint: 7.32.0 eslint-config-prettier: 6.15.0_eslint@7.32.0 - eslint-config-react-app: 5.2.1_c3hysayjpc7bjl5sczl2h5kjf4 + eslint-config-react-app: 5.2.1_g3icqb772h4a27ny4eeob5pf34 eslint-config-standard: 14.1.1_tkxmwkr7odbap4fwlt52e6z7au eslint-config-standard-react: 9.2.0_c3l3lp52boouk5zx5rvypilpme eslint-import-resolver-typescript: 2.7.1_hpmu7kn6tcn2vnxpfzvv33bxmy eslint-plugin-flowtype: 5.10.0_eslint@7.32.0 - eslint-plugin-import: 2.26.0_eufbkykj4pn23vgavsuagghc64 + eslint-plugin-import: 2.26.0_r6mdt2nwtavxychxvtdgpnclqq eslint-plugin-jsx-a11y: 6.6.0_eslint@7.32.0 eslint-plugin-node: 11.1.0_eslint@7.32.0 eslint-plugin-prettier: 3.4.1_rpj2lnfnqon5xeujjqgixljdgq @@ -655,16 +655,16 @@ importers: packages/eslint-plugin-query: specifiers: - '@typescript-eslint/eslint-plugin': ^5.40.1 - '@typescript-eslint/parser': ^5.40.1 - '@typescript-eslint/utils': ^5.40.1 + '@typescript-eslint/eslint-plugin': ^5.41.0 + '@typescript-eslint/parser': ^5.41.0 + '@typescript-eslint/utils': ^5.41.0 eslint: ^8.26.0 recast: ^0.21.5 tsup: ^6.3.0 devDependencies: - '@typescript-eslint/eslint-plugin': 5.40.1_yxvyo6v5zwzggfecalzmx54lre - '@typescript-eslint/parser': 5.40.1_eslint@8.26.0 - '@typescript-eslint/utils': 5.40.1_eslint@8.26.0 + '@typescript-eslint/eslint-plugin': 5.41.0_c2flhriocdzler6lrwbyxxyoca + '@typescript-eslint/parser': 5.41.0_eslint@8.26.0 + '@typescript-eslint/utils': 5.41.0_eslint@8.26.0 eslint: 8.26.0 recast: 0.21.5 tsup: 6.3.0 @@ -5865,8 +5865,8 @@ packages: - supports-color dev: true - /@typescript-eslint/eslint-plugin/5.30.5_6iifm7pgb2pmejrcuwv2knz73e: - resolution: {integrity: sha512-lftkqRoBvc28VFXEoRgyZuztyVUQ04JvUnATSPtIRFAccbXTWL6DEtXGYMcbg998kXw1NLUJm7rTQ9eUt+q6Ig==} + /@typescript-eslint/eslint-plugin/5.41.0_b2esjfxatoxyearpbj3ml2e4hy: + resolution: {integrity: sha512-DXUS22Y57/LAFSg3x7Vi6RNAuLpTXwxB9S2nIA7msBb/Zt8p7XqMwdpdc1IU7CkOQUPgAqR5fWvxuKCbneKGmA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: '@typescript-eslint/parser': ^5.0.0 @@ -5876,13 +5876,12 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/parser': 5.30.5_dyxdave6dwjbccc5dgiifcmuza - '@typescript-eslint/scope-manager': 5.30.5 - '@typescript-eslint/type-utils': 5.30.5_dyxdave6dwjbccc5dgiifcmuza - '@typescript-eslint/utils': 5.30.5_dyxdave6dwjbccc5dgiifcmuza + '@typescript-eslint/parser': 5.41.0_dyxdave6dwjbccc5dgiifcmuza + '@typescript-eslint/scope-manager': 5.41.0 + '@typescript-eslint/type-utils': 5.41.0_dyxdave6dwjbccc5dgiifcmuza + '@typescript-eslint/utils': 5.41.0_dyxdave6dwjbccc5dgiifcmuza debug: 4.3.4 eslint: 7.32.0 - functional-red-black-tree: 1.0.1 ignore: 5.2.0 regexpp: 3.2.0 semver: 7.3.7 @@ -5892,8 +5891,8 @@ packages: - supports-color dev: true - /@typescript-eslint/eslint-plugin/5.40.1_yxvyo6v5zwzggfecalzmx54lre: - resolution: {integrity: sha512-FsWboKkWdytGiXT5O1/R9j37YgcjO8MKHSUmWnIEjVaz0krHkplPnYi7mwdb+5+cs0toFNQb0HIrN7zONdIEWg==} + /@typescript-eslint/eslint-plugin/5.41.0_c2flhriocdzler6lrwbyxxyoca: + resolution: {integrity: sha512-DXUS22Y57/LAFSg3x7Vi6RNAuLpTXwxB9S2nIA7msBb/Zt8p7XqMwdpdc1IU7CkOQUPgAqR5fWvxuKCbneKGmA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: '@typescript-eslint/parser': ^5.0.0 @@ -5903,10 +5902,10 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/parser': 5.40.1_eslint@8.26.0 - '@typescript-eslint/scope-manager': 5.40.1 - '@typescript-eslint/type-utils': 5.40.1_eslint@8.26.0 - '@typescript-eslint/utils': 5.40.1_eslint@8.26.0 + '@typescript-eslint/parser': 5.41.0_eslint@8.26.0 + '@typescript-eslint/scope-manager': 5.41.0 + '@typescript-eslint/type-utils': 5.41.0_eslint@8.26.0 + '@typescript-eslint/utils': 5.41.0_eslint@8.26.0 debug: 4.3.4 eslint: 8.26.0 ignore: 5.2.0 @@ -5971,8 +5970,8 @@ packages: - supports-color dev: true - /@typescript-eslint/parser/5.30.5_dyxdave6dwjbccc5dgiifcmuza: - resolution: {integrity: sha512-zj251pcPXI8GO9NDKWWmygP6+UjwWmrdf9qMW/L/uQJBM/0XbU2inxe5io/234y/RCvwpKEYjZ6c1YrXERkK4Q==} + /@typescript-eslint/parser/5.41.0_dyxdave6dwjbccc5dgiifcmuza: + resolution: {integrity: sha512-HQVfix4+RL5YRWZboMD1pUfFN8MpRH4laziWkkAzyO1fvNOY/uinZcvo3QiFJVS/siNHupV8E5+xSwQZrl6PZA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -5981,9 +5980,9 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/scope-manager': 5.30.5 - '@typescript-eslint/types': 5.30.5 - '@typescript-eslint/typescript-estree': 5.30.5_typescript@4.8.3 + '@typescript-eslint/scope-manager': 5.41.0 + '@typescript-eslint/types': 5.41.0 + '@typescript-eslint/typescript-estree': 5.41.0_typescript@4.8.3 debug: 4.3.4 eslint: 7.32.0 typescript: 4.8.3 @@ -5991,8 +5990,8 @@ packages: - supports-color dev: true - /@typescript-eslint/parser/5.40.1_eslint@8.26.0: - resolution: {integrity: sha512-IK6x55va5w4YvXd4b3VrXQPldV9vQTxi5ov+g4pMANsXPTXOcfjx08CRR1Dfrcc51syPtXHF5bgLlMHYFrvQtg==} + /@typescript-eslint/parser/5.41.0_eslint@8.26.0: + resolution: {integrity: sha512-HQVfix4+RL5YRWZboMD1pUfFN8MpRH4laziWkkAzyO1fvNOY/uinZcvo3QiFJVS/siNHupV8E5+xSwQZrl6PZA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -6001,9 +6000,9 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/scope-manager': 5.40.1 - '@typescript-eslint/types': 5.40.1 - '@typescript-eslint/typescript-estree': 5.40.1 + '@typescript-eslint/scope-manager': 5.41.0 + '@typescript-eslint/types': 5.41.0 + '@typescript-eslint/typescript-estree': 5.41.0 debug: 4.3.4 eslint: 8.26.0 transitivePeerDependencies: @@ -6018,24 +6017,16 @@ packages: '@typescript-eslint/visitor-keys': 4.33.0 dev: true - /@typescript-eslint/scope-manager/5.30.5: - resolution: {integrity: sha512-NJ6F+YHHFT/30isRe2UTmIGGAiXKckCyMnIV58cE3JkHmaD6e5zyEYm5hBDv0Wbin+IC0T1FWJpD3YqHUG/Ydg==} + /@typescript-eslint/scope-manager/5.41.0: + resolution: {integrity: sha512-xOxPJCnuktUkY2xoEZBKXO5DBCugFzjrVndKdUnyQr3+9aDWZReKq9MhaoVnbL+maVwWJu/N0SEtrtEUNb62QQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - '@typescript-eslint/types': 5.30.5 - '@typescript-eslint/visitor-keys': 5.30.5 + '@typescript-eslint/types': 5.41.0 + '@typescript-eslint/visitor-keys': 5.41.0 dev: true - /@typescript-eslint/scope-manager/5.40.1: - resolution: {integrity: sha512-jkn4xsJiUQucI16OLCXrLRXDZ3afKhOIqXs4R3O+M00hdQLKR58WuyXPZZjhKLFCEP2g+TXdBRtLQ33UfAdRUg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - '@typescript-eslint/types': 5.40.1 - '@typescript-eslint/visitor-keys': 5.40.1 - dev: true - - /@typescript-eslint/type-utils/5.30.5_dyxdave6dwjbccc5dgiifcmuza: - resolution: {integrity: sha512-k9+ejlv1GgwN1nN7XjVtyCgE0BTzhzT1YsQF0rv4Vfj2U9xnslBgMYYvcEYAFVdvhuEscELJsB7lDkN7WusErw==} + /@typescript-eslint/type-utils/5.41.0_dyxdave6dwjbccc5dgiifcmuza: + resolution: {integrity: sha512-L30HNvIG6A1Q0R58e4hu4h+fZqaO909UcnnPbwKiN6Rc3BUEx6ez2wgN7aC0cBfcAjZfwkzE+E2PQQ9nEuoqfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: '*' @@ -6044,7 +6035,8 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/utils': 5.30.5_dyxdave6dwjbccc5dgiifcmuza + '@typescript-eslint/typescript-estree': 5.41.0_typescript@4.8.3 + '@typescript-eslint/utils': 5.41.0_dyxdave6dwjbccc5dgiifcmuza debug: 4.3.4 eslint: 7.32.0 tsutils: 3.21.0_typescript@4.8.3 @@ -6053,8 +6045,8 @@ packages: - supports-color dev: true - /@typescript-eslint/type-utils/5.40.1_eslint@8.26.0: - resolution: {integrity: sha512-DLAs+AHQOe6n5LRraXiv27IYPhleF0ldEmx6yBqBgBLaNRKTkffhV1RPsjoJBhVup2zHxfaRtan8/YRBgYhU9Q==} + /@typescript-eslint/type-utils/5.41.0_eslint@8.26.0: + resolution: {integrity: sha512-L30HNvIG6A1Q0R58e4hu4h+fZqaO909UcnnPbwKiN6Rc3BUEx6ez2wgN7aC0cBfcAjZfwkzE+E2PQQ9nEuoqfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: '*' @@ -6063,8 +6055,8 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': 5.40.1 - '@typescript-eslint/utils': 5.40.1_eslint@8.26.0 + '@typescript-eslint/typescript-estree': 5.41.0 + '@typescript-eslint/utils': 5.41.0_eslint@8.26.0 debug: 4.3.4 eslint: 8.26.0 tsutils: 3.21.0 @@ -6077,13 +6069,8 @@ packages: engines: {node: ^8.10.0 || ^10.13.0 || >=11.10.1} dev: true - /@typescript-eslint/types/5.30.5: - resolution: {integrity: sha512-kZ80w/M2AvsbRvOr3PjaNh6qEW1LFqs2pLdo2s5R38B2HYXG8Z0PP48/4+j1QHJFL3ssHIbJ4odPRS8PlHrFfw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: true - - /@typescript-eslint/types/5.40.1: - resolution: {integrity: sha512-Icg9kiuVJSwdzSQvtdGspOlWNjVDnF3qVIKXdJ103o36yRprdl3Ge5cABQx+csx960nuMF21v8qvO31v9t3OHw==} + /@typescript-eslint/types/5.41.0: + resolution: {integrity: sha512-5BejraMXMC+2UjefDvrH0Fo/eLwZRV6859SXRg+FgbhA0R0l6lDqDGAQYhKbXhPN2ofk2kY5sgGyLNL907UXpA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true @@ -6129,8 +6116,8 @@ packages: - supports-color dev: true - /@typescript-eslint/typescript-estree/5.30.5_typescript@4.8.3: - resolution: {integrity: sha512-qGTc7QZC801kbYjAr4AgdOfnokpwStqyhSbiQvqGBLixniAKyH+ib2qXIVo4P9NgGzwyfD9I0nlJN7D91E1VpQ==} + /@typescript-eslint/typescript-estree/5.41.0: + resolution: {integrity: sha512-SlzFYRwFSvswzDSQ/zPkIWcHv8O5y42YUskko9c4ki+fV6HATsTODUPbRbcGDFYP86gaJL5xohUEytvyNNcXWg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: typescript: '*' @@ -6138,20 +6125,19 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/types': 5.30.5 - '@typescript-eslint/visitor-keys': 5.30.5 + '@typescript-eslint/types': 5.41.0 + '@typescript-eslint/visitor-keys': 5.41.0 debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 semver: 7.3.7 - tsutils: 3.21.0_typescript@4.8.3 - typescript: 4.8.3 + tsutils: 3.21.0 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/typescript-estree/5.40.1: - resolution: {integrity: sha512-5QTP/nW5+60jBcEPfXy/EZL01qrl9GZtbgDZtDPlfW5zj/zjNrdI2B5zMUHmOsfvOr2cWqwVdWjobCiHcedmQA==} + /@typescript-eslint/typescript-estree/5.41.0_typescript@4.8.3: + resolution: {integrity: sha512-SlzFYRwFSvswzDSQ/zPkIWcHv8O5y42YUskko9c4ki+fV6HATsTODUPbRbcGDFYP86gaJL5xohUEytvyNNcXWg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: typescript: '*' @@ -6159,46 +6145,49 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/types': 5.40.1 - '@typescript-eslint/visitor-keys': 5.40.1 + '@typescript-eslint/types': 5.41.0 + '@typescript-eslint/visitor-keys': 5.41.0 debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 semver: 7.3.7 - tsutils: 3.21.0 + tsutils: 3.21.0_typescript@4.8.3 + typescript: 4.8.3 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/utils/5.30.5_dyxdave6dwjbccc5dgiifcmuza: - resolution: {integrity: sha512-o4SSUH9IkuA7AYIfAvatldovurqTAHrfzPApOZvdUq01hHojZojCFXx06D/aFpKCgWbMPRdJBWAC3sWp3itwTA==} + /@typescript-eslint/utils/5.41.0_dyxdave6dwjbccc5dgiifcmuza: + resolution: {integrity: sha512-QlvfwaN9jaMga9EBazQ+5DDx/4sAdqDkcs05AsQHMaopluVCUyu1bTRUVKzXbgjDlrRAQrYVoi/sXJ9fmG+KLQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: '@types/json-schema': 7.0.11 - '@typescript-eslint/scope-manager': 5.30.5 - '@typescript-eslint/types': 5.30.5 - '@typescript-eslint/typescript-estree': 5.30.5_typescript@4.8.3 + '@types/semver': 7.3.12 + '@typescript-eslint/scope-manager': 5.41.0 + '@typescript-eslint/types': 5.41.0 + '@typescript-eslint/typescript-estree': 5.41.0_typescript@4.8.3 eslint: 7.32.0 eslint-scope: 5.1.1 eslint-utils: 3.0.0_eslint@7.32.0 + semver: 7.3.7 transitivePeerDependencies: - supports-color - typescript dev: true - /@typescript-eslint/utils/5.40.1_eslint@8.26.0: - resolution: {integrity: sha512-a2TAVScoX9fjryNrW6BZRnreDUszxqm9eQ9Esv8n5nXApMW0zeANUYlwh/DED04SC/ifuBvXgZpIK5xeJHQ3aw==} + /@typescript-eslint/utils/5.41.0_eslint@8.26.0: + resolution: {integrity: sha512-QlvfwaN9jaMga9EBazQ+5DDx/4sAdqDkcs05AsQHMaopluVCUyu1bTRUVKzXbgjDlrRAQrYVoi/sXJ9fmG+KLQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: '@types/json-schema': 7.0.11 '@types/semver': 7.3.12 - '@typescript-eslint/scope-manager': 5.40.1 - '@typescript-eslint/types': 5.40.1 - '@typescript-eslint/typescript-estree': 5.40.1 + '@typescript-eslint/scope-manager': 5.41.0 + '@typescript-eslint/types': 5.41.0 + '@typescript-eslint/typescript-estree': 5.41.0 eslint: 8.26.0 eslint-scope: 5.1.1 eslint-utils: 3.0.0_eslint@8.26.0 @@ -6216,19 +6205,11 @@ packages: eslint-visitor-keys: 2.1.0 dev: true - /@typescript-eslint/visitor-keys/5.30.5: - resolution: {integrity: sha512-D+xtGo9HUMELzWIUqcQc0p2PO4NyvTrgIOK/VnSH083+8sq0tiLozNRKuLarwHYGRuA6TVBQSuuLwJUDWd3aaA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - '@typescript-eslint/types': 5.30.5 - eslint-visitor-keys: 3.3.0 - dev: true - - /@typescript-eslint/visitor-keys/5.40.1: - resolution: {integrity: sha512-A2DGmeZ+FMja0geX5rww+DpvILpwo1OsiQs0M+joPWJYsiEFBLsH0y1oFymPNul6Z5okSmHpP4ivkc2N0Cgfkw==} + /@typescript-eslint/visitor-keys/5.41.0: + resolution: {integrity: sha512-vilqeHj267v8uzzakbm13HkPMl7cbYpKVjgFWZPIOHIJHZtinvypUhJ5xBXfWYg4eFKqztbMMpOgFpT9Gfx4fw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - '@typescript-eslint/types': 5.40.1 + '@typescript-eslint/types': 5.41.0 eslint-visitor-keys: 3.3.0 dev: true @@ -8652,7 +8633,7 @@ packages: eslint: 7.32.0 dev: true - /eslint-config-react-app/5.2.1_c3hysayjpc7bjl5sczl2h5kjf4: + /eslint-config-react-app/5.2.1_g3icqb772h4a27ny4eeob5pf34: resolution: {integrity: sha512-pGIZ8t0mFLcV+6ZirRgYK6RVqUIKRIi9MmgzUEmrIknsn3AdO0I32asO86dJgloHq+9ZPl8UIg8mYrvgP5u2wQ==} peerDependencies: '@typescript-eslint/eslint-plugin': 2.x @@ -8669,13 +8650,13 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/eslint-plugin': 5.30.5_6iifm7pgb2pmejrcuwv2knz73e - '@typescript-eslint/parser': 5.30.5_dyxdave6dwjbccc5dgiifcmuza + '@typescript-eslint/eslint-plugin': 5.41.0_b2esjfxatoxyearpbj3ml2e4hy + '@typescript-eslint/parser': 5.41.0_dyxdave6dwjbccc5dgiifcmuza babel-eslint: 10.1.0_eslint@7.32.0 confusing-browser-globals: 1.0.11 eslint: 7.32.0 eslint-plugin-flowtype: 5.10.0_eslint@7.32.0 - eslint-plugin-import: 2.26.0_eufbkykj4pn23vgavsuagghc64 + eslint-plugin-import: 2.26.0_r6mdt2nwtavxychxvtdgpnclqq eslint-plugin-jsx-a11y: 6.6.0_eslint@7.32.0 eslint-plugin-react: 7.20.0_eslint@7.32.0 eslint-plugin-react-hooks: 4.6.0_eslint@7.32.0 @@ -8713,7 +8694,7 @@ packages: eslint-plugin-standard: '>=4.0.0' dependencies: eslint: 7.32.0 - eslint-plugin-import: 2.26.0_eufbkykj4pn23vgavsuagghc64 + eslint-plugin-import: 2.26.0_r6mdt2nwtavxychxvtdgpnclqq eslint-plugin-node: 11.1.0_eslint@7.32.0 eslint-plugin-promise: 4.3.1 eslint-plugin-standard: 4.1.0_eslint@7.32.0 @@ -8744,7 +8725,7 @@ packages: dependencies: debug: 4.3.4 eslint: 7.32.0 - eslint-plugin-import: 2.26.0_eufbkykj4pn23vgavsuagghc64 + eslint-plugin-import: 2.26.0_r6mdt2nwtavxychxvtdgpnclqq glob: 7.2.3 is-glob: 4.0.3 resolve: 1.22.1 @@ -8779,7 +8760,7 @@ packages: - supports-color dev: true - /eslint-module-utils/2.7.3_ngc5pgxzrdnldt43xbcfncn6si: + /eslint-module-utils/2.7.3_mrajrahd43gcci7tt5bsa7uqem: resolution: {integrity: sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==} engines: {node: '>=4'} peerDependencies: @@ -8797,7 +8778,7 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 5.30.5_dyxdave6dwjbccc5dgiifcmuza + '@typescript-eslint/parser': 5.41.0_dyxdave6dwjbccc5dgiifcmuza debug: 3.2.7 eslint-import-resolver-node: 0.3.6 eslint-import-resolver-typescript: 2.7.1_hpmu7kn6tcn2vnxpfzvv33bxmy @@ -8838,7 +8819,7 @@ packages: string-natural-compare: 3.0.1 dev: true - /eslint-plugin-import/2.26.0_eufbkykj4pn23vgavsuagghc64: + /eslint-plugin-import/2.26.0_ffi3uiz42rv3jyhs6cr7p7qqry: resolution: {integrity: sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==} engines: {node: '>=4'} peerDependencies: @@ -8848,14 +8829,14 @@ packages: '@typescript-eslint/parser': optional: true dependencies: - '@typescript-eslint/parser': 5.30.5_dyxdave6dwjbccc5dgiifcmuza + '@typescript-eslint/parser': 4.33.0_wnilx7boviscikmvsfkd6ljepe array-includes: 3.1.5 array.prototype.flat: 1.3.0 debug: 2.6.9 doctrine: 2.1.0 eslint: 7.32.0 eslint-import-resolver-node: 0.3.6 - eslint-module-utils: 2.7.3_ngc5pgxzrdnldt43xbcfncn6si + eslint-module-utils: 2.7.3_lkzaig2qiyp6elizstfbgvzhie has: 1.0.3 is-core-module: 2.9.0 is-glob: 4.0.3 @@ -8869,7 +8850,7 @@ packages: - supports-color dev: true - /eslint-plugin-import/2.26.0_ffi3uiz42rv3jyhs6cr7p7qqry: + /eslint-plugin-import/2.26.0_r6mdt2nwtavxychxvtdgpnclqq: resolution: {integrity: sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==} engines: {node: '>=4'} peerDependencies: @@ -8879,14 +8860,14 @@ packages: '@typescript-eslint/parser': optional: true dependencies: - '@typescript-eslint/parser': 4.33.0_wnilx7boviscikmvsfkd6ljepe + '@typescript-eslint/parser': 5.41.0_dyxdave6dwjbccc5dgiifcmuza array-includes: 3.1.5 array.prototype.flat: 1.3.0 debug: 2.6.9 doctrine: 2.1.0 eslint: 7.32.0 eslint-import-resolver-node: 0.3.6 - eslint-module-utils: 2.7.3_lkzaig2qiyp6elizstfbgvzhie + eslint-module-utils: 2.7.3_mrajrahd43gcci7tt5bsa7uqem has: 1.0.3 is-core-module: 2.9.0 is-glob: 4.0.3 From 1c38511bbaff6b42387cb812094d36de3d5737de Mon Sep 17 00:00:00 2001 From: Newbie012 Date: Tue, 25 Oct 2022 22:36:44 +0300 Subject: [PATCH 19/40] support member expression keys --- packages/eslint-plugin-query/.eslintrc | 1 + packages/eslint-plugin-query/package.json | 1 - .../eslint-plugin-query/src/extra-utils.ts | 58 +++++------------- .../src/rules/exhaustive-deps.rule.ts | 61 +++++++++++-------- .../src/rules/exhaustive-deps.test.ts | 10 ++- pnpm-lock.yaml | 19 ------ 6 files changed, 61 insertions(+), 89 deletions(-) diff --git a/packages/eslint-plugin-query/.eslintrc b/packages/eslint-plugin-query/.eslintrc index 067a57a416..96e805633f 100644 --- a/packages/eslint-plugin-query/.eslintrc +++ b/packages/eslint-plugin-query/.eslintrc @@ -1,4 +1,5 @@ { + "parser": "@typescript-eslint/parser", "parserOptions": { "project": "./tsconfig.json", "sourceType": "module" diff --git a/packages/eslint-plugin-query/package.json b/packages/eslint-plugin-query/package.json index 8770b0ca43..c9ad172ca0 100644 --- a/packages/eslint-plugin-query/package.json +++ b/packages/eslint-plugin-query/package.json @@ -39,7 +39,6 @@ "@typescript-eslint/parser": "^5.41.0", "@typescript-eslint/utils": "^5.41.0", "eslint": "^8.26.0", - "recast": "^0.21.5", "tsup": "^6.3.0" } } diff --git a/packages/eslint-plugin-query/src/extra-utils.ts b/packages/eslint-plugin-query/src/extra-utils.ts index b8412207b0..c16e0a4057 100644 --- a/packages/eslint-plugin-query/src/extra-utils.ts +++ b/packages/eslint-plugin-query/src/extra-utils.ts @@ -11,12 +11,6 @@ export const ExtraUtils = { ): node is TSESTree.Identifier { return ExtraUtils.isIdentifier(node) && node.name === name }, - isLiteral(node: TSESTree.Node): node is TSESTree.Literal { - return node.type === AST_NODE_TYPES.Literal - }, - isTemplateLiteral(node: TSESTree.Node): node is TSESTree.TemplateLiteral { - return node.type === AST_NODE_TYPES.TemplateLiteral - }, isProperty(node: TSESTree.Node): node is TSESTree.Property { return node.type === AST_NODE_TYPES.Property }, @@ -40,7 +34,7 @@ export const ExtraUtils = { ExtraUtils.isPropertyWithIdentifierKey(x, key), ) as TSESTree.Property | undefined }, - getIdentifiersRecursive(node: TSESTree.Node): TSESTree.Identifier[] { + getNestedIdentifiers(node: TSESTree.Node): TSESTree.Identifier[] { const identifiers: TSESTree.Identifier[] = [] if (ExtraUtils.isIdentifier(node)) { @@ -49,45 +43,28 @@ export const ExtraUtils = { if (node.type === AST_NODE_TYPES.ArrayExpression) { node.elements.forEach((x) => { - identifiers.push(...ExtraUtils.getIdentifiersRecursive(x)) + identifiers.push(...ExtraUtils.getNestedIdentifiers(x)) }) } if (node.type === AST_NODE_TYPES.ObjectExpression) { node.properties.forEach((x) => { - identifiers.push(...ExtraUtils.getIdentifiersRecursive(x)) + identifiers.push(...ExtraUtils.getNestedIdentifiers(x)) }) } if (node.type === AST_NODE_TYPES.Property) { - identifiers.push(...ExtraUtils.getIdentifiersRecursive(node.value)) + identifiers.push(...ExtraUtils.getNestedIdentifiers(node.value)) } if (node.type === AST_NODE_TYPES.TemplateLiteral) { node.expressions.forEach((x) => { - identifiers.push(...ExtraUtils.getIdentifiersRecursive(x)) + identifiers.push(...ExtraUtils.getNestedIdentifiers(x)) }) } - return identifiers - }, - getIdentifiersFromArrayExpression( - node: TSESTree.ArrayExpression, - ): TSESTree.Identifier[] { - const identifiers: TSESTree.Identifier[] = [] - - for (const element of node.elements) { - if (ExtraUtils.isIdentifier(element)) { - identifiers.push(element) - } - - if (ExtraUtils.isTemplateLiteral(element)) { - element.expressions.forEach((expression) => { - if (ExtraUtils.isIdentifier(expression)) { - identifiers.push(expression) - } - }) - } + if (node.type === AST_NODE_TYPES.MemberExpression) { + identifiers.push(...ExtraUtils.getNestedIdentifiers(node.object)) } return identifiers @@ -114,19 +91,16 @@ export const ExtraUtils = { return false }, - getNodeLiteralQuote(node: TSESTree.Node): 'single' | 'double' | 'auto' { - if (node.type === AST_NODE_TYPES.Literal) { - return node.raw.startsWith("'") ? 'single' : 'double' + traverseUpOnly( + identifier: TSESTree.Node, + allowedNodeTypes: AST_NODE_TYPES[], + ): TSESTree.Node { + const parent = identifier.parent + + if (parent !== undefined && allowedNodeTypes.includes(parent.type)) { + return ExtraUtils.traverseUpOnly(parent, allowedNodeTypes) } - return 'auto' - }, - builder: { - identifier: (name: string): TSESTree.Identifier => ({ - type: AST_NODE_TYPES.Identifier, - loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } }, - range: [0, name.length], - name, - }), + return identifier }, } diff --git a/packages/eslint-plugin-query/src/rules/exhaustive-deps.rule.ts b/packages/eslint-plugin-query/src/rules/exhaustive-deps.rule.ts index 7f3722d0e3..e312cd7209 100644 --- a/packages/eslint-plugin-query/src/rules/exhaustive-deps.rule.ts +++ b/packages/eslint-plugin-query/src/rules/exhaustive-deps.rule.ts @@ -1,6 +1,5 @@ import type { JSONSchema, TSESLint, TSESTree } from '@typescript-eslint/utils' import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils' -import * as recast from 'recast' import { ExtraUtils } from '../extra-utils' const messages = { @@ -95,52 +94,50 @@ function runCheck(params: { node: TSESTree.Property; context: RuleContext }) { return } + const sourceCode = context.getSourceCode() const queryKeyValue = queryKey.value - const queryKeyDeps = ExtraUtils.getIdentifiersRecursive(queryKeyValue) const refs = getExternalRefs({ scopeManager, node: queryFn.value }) + + const existingKeys = ExtraUtils.getNestedIdentifiers(queryKeyValue).map( + (identifier) => mapKeyNodeToText(identifier, sourceCode), + ) + const missingRefs = refs - .filter((reference) => { + .map((ref) => ({ + ref: ref, + text: mapKeyNodeToText(ref.identifier, sourceCode), + })) + .filter(({ ref, text }) => { return ( - !ExtraUtils.isAncestorIsCallee(reference.identifier) && - !ruleOptions.whitelist.includes(reference.identifier.name) && - !queryKeyDeps.some((y) => y.name === reference.identifier.name) + !ExtraUtils.isAncestorIsCallee(ref.identifier) && + !ruleOptions.whitelist.includes(ref.identifier.name) && + !existingKeys.some((existingKey) => existingKey === text) ) }) - .map((ref) => ref.identifier) + .map(({ ref, text }) => ({ identifier: ref.identifier, text: text })) if (missingRefs.length > 0) { context.report({ node: node, messageId: 'missingDeps', data: { - deps: missingRefs.map((ref) => ref.name).join(', '), + deps: missingRefs.map((ref) => ref.text).join(', '), }, fix(fixer) { - const newQueryKeyText = [ - ...queryKeyValue.elements, - ...missingRefs.map((ref) => ExtraUtils.builder.identifier(ref.name)), - ] - .map((x) => - inlineCode( - recast.print(x, { quote: ExtraUtils.getNodeLiteralQuote(x) }) - .code, - ), - ) + const missingAsText = missingRefs + .map((ref) => mapKeyNodeToText(ref.identifier, sourceCode)) .join(', ') - return fixer.replaceText(queryKeyValue, `[${newQueryKeyText}]`) + const existingWithMissing = sourceCode + .getText(queryKeyValue) + .replace(/\]$/, `, ${missingAsText}]`) + + return fixer.replaceText(queryKeyValue, existingWithMissing) }, }) } } -function inlineCode(code: string) { - return code - .replace(/[\n\s]/gm, '') - .replace(/([,:])/gm, '$1 ') - .replace(/^(?:){(.*)}/gm, '{ $1 }') -} - function getExternalRefs(params: { scopeManager: TSESLint.Scope.ScopeManager node: TSESTree.Node @@ -163,6 +160,18 @@ function getExternalRefs(params: { return uniqueBy(externalRefs, (x) => x.resolved) } +function mapKeyNodeToText( + node: TSESTree.Node, + sourceCode: Readonly, +) { + return sourceCode.getText( + ExtraUtils.traverseUpOnly(node, [ + AST_NODE_TYPES.MemberExpression, + AST_NODE_TYPES.Identifier, + ]), + ) +} + function uniqueBy(arr: T[], fn: (x: T) => unknown): T[] { return arr.filter((x, i, a) => a.findIndex((y) => fn(x) === fn(y)) === i) } diff --git a/packages/eslint-plugin-query/src/rules/exhaustive-deps.test.ts b/packages/eslint-plugin-query/src/rules/exhaustive-deps.test.ts index db24c1ac35..50ffec87f3 100644 --- a/packages/eslint-plugin-query/src/rules/exhaustive-deps.test.ts +++ b/packages/eslint-plugin-query/src/rules/exhaustive-deps.test.ts @@ -33,6 +33,14 @@ ruleTester.run('exhaustive-deps', exhaustiveDepsRule, { name: 'should not pass api.entity.get', code: 'useQuery({ queryKey: ["entity", id], queryFn: () => api.entity.get(id) });', }, + { + name: 'should pass props.src', + code: ` + function MyComponent(props) { + useQuery({ queryKey: ["entity", props.src], queryFn: () => api.entity.get(props.src) }); + } + `, + }, ], invalid: [ { @@ -146,7 +154,7 @@ ruleTester.run('exhaustive-deps', exhaustiveDepsRule, { output: ` const todoQueries = { key: (dep1, dep2, dep3, dep4, dep5, dep6, dep7, dep8) => ({ - queryKey: ['foo', { dep1, dep2: dep2, bar: dep3, baz: [dep4, dep5] }, [dep6, dep7], dep8], + queryKey: ['foo', {dep1, dep2: dep2, bar: dep3, baz: [dep4, dep5]}, [dep6, dep7], dep8], queryFn: () => api.getEntity(dep1, dep2, dep3, dep4, dep5, dep6, dep7, dep8), }), }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 88f4baa8ff..8009a3cffa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -659,14 +659,12 @@ importers: '@typescript-eslint/parser': ^5.41.0 '@typescript-eslint/utils': ^5.41.0 eslint: ^8.26.0 - recast: ^0.21.5 tsup: ^6.3.0 devDependencies: '@typescript-eslint/eslint-plugin': 5.41.0_c2flhriocdzler6lrwbyxxyoca '@typescript-eslint/parser': 5.41.0_eslint@8.26.0 '@typescript-eslint/utils': 5.41.0_eslint@8.26.0 eslint: 8.26.0 - recast: 0.21.5 tsup: 6.3.0 packages/query-async-storage-persister: @@ -6830,13 +6828,6 @@ packages: dependencies: tslib: 2.4.0 - /ast-types/0.15.2: - resolution: {integrity: sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg==} - engines: {node: '>=4'} - dependencies: - tslib: 2.4.0 - dev: true - /astral-regex/1.0.0: resolution: {integrity: sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==} engines: {node: '>=4'} @@ -13757,16 +13748,6 @@ packages: source-map: 0.6.1 tslib: 2.4.0 - /recast/0.21.5: - resolution: {integrity: sha512-hjMmLaUXAm1hIuTqOdeYObMslq/q+Xff6QE3Y2P+uoHAg2nmVlLBps2hzh1UJDdMtDTMXOFewK6ky51JQIeECg==} - engines: {node: '>= 4'} - dependencies: - ast-types: 0.15.2 - esprima: 4.0.1 - source-map: 0.6.1 - tslib: 2.4.0 - dev: true - /rechoir/0.6.2: resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} engines: {node: '>= 0.10'} From eaee66bc72eff4af2f1a7b40dcdde9102bc9a8a7 Mon Sep 17 00:00:00 2001 From: Newbie012 Date: Wed, 26 Oct 2022 01:32:41 +0300 Subject: [PATCH 20/40] format --- packages/eslint-plugin-query/src/extra-utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin-query/src/extra-utils.ts b/packages/eslint-plugin-query/src/extra-utils.ts index c16e0a4057..16bca55f09 100644 --- a/packages/eslint-plugin-query/src/extra-utils.ts +++ b/packages/eslint-plugin-query/src/extra-utils.ts @@ -64,7 +64,7 @@ export const ExtraUtils = { } if (node.type === AST_NODE_TYPES.MemberExpression) { - identifiers.push(...ExtraUtils.getNestedIdentifiers(node.object)) + identifiers.push(...ExtraUtils.getNestedIdentifiers(node.object)) } return identifiers From e0b4f9fb329982ed10045229834d3e8f50ed87a8 Mon Sep 17 00:00:00 2001 From: Newbie012 Date: Wed, 26 Oct 2022 01:45:05 +0300 Subject: [PATCH 21/40] should not fix with duplicate keys --- .../src/rules/exhaustive-deps.rule.ts | 8 +++++--- .../src/rules/exhaustive-deps.test.ts | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin-query/src/rules/exhaustive-deps.rule.ts b/packages/eslint-plugin-query/src/rules/exhaustive-deps.rule.ts index e312cd7209..2463582807 100644 --- a/packages/eslint-plugin-query/src/rules/exhaustive-deps.rule.ts +++ b/packages/eslint-plugin-query/src/rules/exhaustive-deps.rule.ts @@ -116,15 +116,17 @@ function runCheck(params: { node: TSESTree.Property; context: RuleContext }) { }) .map(({ ref, text }) => ({ identifier: ref.identifier, text: text })) - if (missingRefs.length > 0) { + const uniqueMissingRefs = uniqueBy(missingRefs, (x) => x.text) + + if (uniqueMissingRefs.length > 0) { context.report({ node: node, messageId: 'missingDeps', data: { - deps: missingRefs.map((ref) => ref.text).join(', '), + deps: uniqueMissingRefs.map((ref) => ref.text).join(', '), }, fix(fixer) { - const missingAsText = missingRefs + const missingAsText = uniqueMissingRefs .map((ref) => mapKeyNodeToText(ref.identifier, sourceCode)) .join(', ') diff --git a/packages/eslint-plugin-query/src/rules/exhaustive-deps.test.ts b/packages/eslint-plugin-query/src/rules/exhaustive-deps.test.ts index 50ffec87f3..0c2c2beb3e 100644 --- a/packages/eslint-plugin-query/src/rules/exhaustive-deps.test.ts +++ b/packages/eslint-plugin-query/src/rules/exhaustive-deps.test.ts @@ -161,5 +161,19 @@ ruleTester.run('exhaustive-deps', exhaustiveDepsRule, { `, errors: [{ messageId: 'missingDeps', data: { deps: 'dep8' } }], }, + { + name: 'should fail when two deps that depend on each other are missing', + code: ` + function Component({ map, key }) { + useQuery({ queryKey: ["key"], queryFn: () => api.get(map[key]) }); + } + `, + output: ` + function Component({ map, key }) { + useQuery({ queryKey: ["key", map[key]], queryFn: () => api.get(map[key]) }); + } + `, + errors: [{ messageId: 'missingDeps', data: { deps: 'map[key]' } }], + }, ], }) From df3a8020a051fb0e1d1cffdf13d4120e6e60f7eb Mon Sep 17 00:00:00 2001 From: Newbie012 Date: Wed, 26 Oct 2022 09:46:55 +0300 Subject: [PATCH 22/40] format --- .../src/rules/exhaustive-deps.test.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/eslint-plugin-query/src/rules/exhaustive-deps.test.ts b/packages/eslint-plugin-query/src/rules/exhaustive-deps.test.ts index 0c2c2beb3e..dd6dfe4be8 100644 --- a/packages/eslint-plugin-query/src/rules/exhaustive-deps.test.ts +++ b/packages/eslint-plugin-query/src/rules/exhaustive-deps.test.ts @@ -162,18 +162,18 @@ ruleTester.run('exhaustive-deps', exhaustiveDepsRule, { errors: [{ messageId: 'missingDeps', data: { deps: 'dep8' } }], }, { - name: 'should fail when two deps that depend on each other are missing', - code: ` + name: 'should fail when two deps that depend on each other are missing', + code: ` function Component({ map, key }) { useQuery({ queryKey: ["key"], queryFn: () => api.get(map[key]) }); } `, - output: ` + output: ` function Component({ map, key }) { useQuery({ queryKey: ["key", map[key]], queryFn: () => api.get(map[key]) }); } `, - errors: [{ messageId: 'missingDeps', data: { deps: 'map[key]' } }], - }, + errors: [{ messageId: 'missingDeps', data: { deps: 'map[key]' } }], + }, ], }) From e8c2632d19c9d0c5bbfaa3a3dd20265467beccdc Mon Sep 17 00:00:00 2001 From: Newbie012 Date: Thu, 27 Oct 2022 11:14:19 +0300 Subject: [PATCH 23/40] empty commit (flaky test) From fdaa6a642d52f5764f5256a55888a02f4883981f Mon Sep 17 00:00:00 2001 From: Newbie012 Date: Sun, 30 Oct 2022 20:50:14 +0200 Subject: [PATCH 24/40] should ignore keys from callback --- .../src/rules/exhaustive-deps.test.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/eslint-plugin-query/src/rules/exhaustive-deps.test.ts b/packages/eslint-plugin-query/src/rules/exhaustive-deps.test.ts index dd6dfe4be8..13ea6230e3 100644 --- a/packages/eslint-plugin-query/src/rules/exhaustive-deps.test.ts +++ b/packages/eslint-plugin-query/src/rules/exhaustive-deps.test.ts @@ -41,6 +41,17 @@ ruleTester.run('exhaustive-deps', exhaustiveDepsRule, { } `, }, + { + name: 'should ignore keys from callback', + code: ` + function MyComponent(props) { + useQuery({ + queryKey: ["foo", dep1], + queryFn: ({ queryKey: [, dep] }) => fetch(dep), + }); + } + `, + }, ], invalid: [ { From 535be2cd5730ba624daa166a694a08c6845e0ba4 Mon Sep 17 00:00:00 2001 From: Eliya Cohen Date: Sun, 30 Oct 2022 21:00:40 +0200 Subject: [PATCH 25/40] Update packages/eslint-plugin-query/package.json Co-authored-by: Dominik Dorfmeister --- packages/eslint-plugin-query/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin-query/package.json b/packages/eslint-plugin-query/package.json index c9ad172ca0..2c6c3dd8b8 100644 --- a/packages/eslint-plugin-query/package.json +++ b/packages/eslint-plugin-query/package.json @@ -1,7 +1,7 @@ { "name": "@tanstack/eslint-plugin-query", "version": "4.13.0", - "description": "ESLint plugin for React Query", + "description": "ESLint plugin for TanStack Query", "author": "Eliya Cohen", "license": "MIT", "repository": "tanstack/query", From 3e1f2b667adb4723813697a3bae3ef34d16471f6 Mon Sep 17 00:00:00 2001 From: Newbie012 Date: Sun, 30 Oct 2022 23:58:20 +0200 Subject: [PATCH 26/40] add prefer-query-object-syntax rule --- examples/react/basic/.eslintrc | 3 +- examples/react/basic/src/index.jsx | 17 +- .../docs/rules/exhaustive-deps.md | 41 ++++ .../docs/rules/prefer-query-object-syntax.md | 53 ++++++ .../src/configs/index.test.ts | 19 ++ .../eslint-plugin-query/src/configs/index.ts | 22 +++ packages/eslint-plugin-query/src/index.ts | 7 +- .../src/rules/exhaustive-deps.rule.ts | 179 ------------------ .../exhaustive-deps/exhaustive-deps.rule.ts | 117 ++++++++++++ .../exhaustive-deps.test.ts | 4 +- .../eslint-plugin-query/src/rules/index.ts | 7 + .../prefer-query-object-syntax.test.ts | 126 ++++++++++++ .../prefer-query-object-syntax.ts | 118 ++++++++++++ .../{extra-utils.ts => utils/ast-utils.ts} | 67 +++++-- .../src/utils/create-rule.ts | 22 +++ .../src/utils/detect-react-query-imports.ts | 72 +++++++ .../src/utils/test-utils.ts | 82 ++++++++ .../src/utils/unique-by.ts | 3 + 18 files changed, 753 insertions(+), 206 deletions(-) create mode 100644 packages/eslint-plugin-query/docs/rules/exhaustive-deps.md create mode 100644 packages/eslint-plugin-query/docs/rules/prefer-query-object-syntax.md create mode 100644 packages/eslint-plugin-query/src/configs/index.test.ts create mode 100644 packages/eslint-plugin-query/src/configs/index.ts delete mode 100644 packages/eslint-plugin-query/src/rules/exhaustive-deps.rule.ts create mode 100644 packages/eslint-plugin-query/src/rules/exhaustive-deps/exhaustive-deps.rule.ts rename packages/eslint-plugin-query/src/rules/{ => exhaustive-deps}/exhaustive-deps.test.ts (98%) create mode 100644 packages/eslint-plugin-query/src/rules/index.ts create mode 100644 packages/eslint-plugin-query/src/rules/prefer-query-object-syntax/prefer-query-object-syntax.test.ts create mode 100644 packages/eslint-plugin-query/src/rules/prefer-query-object-syntax/prefer-query-object-syntax.ts rename packages/eslint-plugin-query/src/{extra-utils.ts => utils/ast-utils.ts} (53%) create mode 100644 packages/eslint-plugin-query/src/utils/create-rule.ts create mode 100644 packages/eslint-plugin-query/src/utils/detect-react-query-imports.ts create mode 100644 packages/eslint-plugin-query/src/utils/test-utils.ts create mode 100644 packages/eslint-plugin-query/src/utils/unique-by.ts diff --git a/examples/react/basic/.eslintrc b/examples/react/basic/.eslintrc index 59df28de5d..f6ae985ac4 100644 --- a/examples/react/basic/.eslintrc +++ b/examples/react/basic/.eslintrc @@ -2,6 +2,7 @@ "extends": ["react-app", "prettier"], "plugins": ["@tanstack/eslint-plugin-query"], "rules": { - "@tanstack/query/exhaustive-deps": "error" + "@tanstack/query/exhaustive-deps": "error", + "@tanstack/query/prefer-query-object-syntax": "error" } } diff --git a/examples/react/basic/src/index.jsx b/examples/react/basic/src/index.jsx index 755c409705..468702c38a 100644 --- a/examples/react/basic/src/index.jsx +++ b/examples/react/basic/src/index.jsx @@ -38,11 +38,14 @@ function App() { } function usePosts() { - return useQuery(["posts"], async () => { - const { data } = await axios.get( - "https://jsonplaceholder.typicode.com/posts" - ); - return data; + return useQuery({ + queryKey: ["posts"], + queryFn: async () => { + const { data } = await axios.get( + "https://jsonplaceholder.typicode.com/posts" + ); + return data; + }, }); } @@ -98,7 +101,9 @@ const getPostById = async (id) => { }; function usePost(postId) { - return useQuery(["post", postId], () => getPostById(postId), { + return useQuery({ + queryKey: ["post", postId], + queryFn: () => getPostById(postId), enabled: !!postId, }); } diff --git a/packages/eslint-plugin-query/docs/rules/exhaustive-deps.md b/packages/eslint-plugin-query/docs/rules/exhaustive-deps.md new file mode 100644 index 0000000000..8ded72791c --- /dev/null +++ b/packages/eslint-plugin-query/docs/rules/exhaustive-deps.md @@ -0,0 +1,41 @@ +# Prefer object syntax for useQuery (`@tanstack/query/exhaustive-deps`) + +TODO @TkDodo a description of this rule + +## Rule Details + +Examples of **incorrect** code for this rule: + +```ts +useQuery({ + queryKey: ["todo"], + queryFn: () => api.getTodo(todoId) +}) + +const todoQueries = { + detail: (id) => ({ queryKey: ["todo"], queryFn: () => api.getTodo(id) }) +} +``` + + +Examples of **correct** code for this rule: + +```ts +useQuery({ + queryKey: ["todo", todoId], + queryFn: () => api.getTodo(todoId) +}) + +const todoQueries = { + detail: (id) => ({ queryKey: ["todo", id], queryFn: () => api.getTodo(id) }) +} +``` + +## When Not To Use It + +If you don't care about the rules of the query keys, then you will not need this rule. + +## Attributes + +- [x] ✅ Recommended +- [x] 🔧 Fixable diff --git a/packages/eslint-plugin-query/docs/rules/prefer-query-object-syntax.md b/packages/eslint-plugin-query/docs/rules/prefer-query-object-syntax.md new file mode 100644 index 0000000000..3907f751f6 --- /dev/null +++ b/packages/eslint-plugin-query/docs/rules/prefer-query-object-syntax.md @@ -0,0 +1,53 @@ +# Prefer object syntax for useQuery (`@tanstack/query/prefer-query-object-syntax`) + +You can use [`useQuery`](https://tanstack.com/query/v4/docs/reference/useQuery) in two different ways. + +Standard + +```ts +useQuery(queryKey, queryFn?, options?) + +// or + +useQuery(options) +``` + +This rule prefers the second option, as it is more consistent with other React Query hooks, like `useQueries` or `useMutation`. + +## Rule Details + +Examples of **incorrect** code for this rule: + +```js +import { useQuery } from '@tanstack/react-query'; + +useQuery(queryKey, queryFn, { + onSuccess, +}); + +useQuery(queryKey, { + queryFn, + onSuccess, +}); +``` + +Examples of **correct** code for this rule: + +```js +import { useQuery } from '@tanstack/react-query'; + +useQuery({ + queryKey, + queryFn, + onSuccess, +}); +``` + +## When Not To Use It + +If you don't care about useQuery consistency, then you will not need this rule. + +## Attributes + +- [x] ✅ Recommended +- [x] 🔧 Fixable diff --git a/packages/eslint-plugin-query/src/configs/index.test.ts b/packages/eslint-plugin-query/src/configs/index.test.ts new file mode 100644 index 0000000000..1a7c7aa974 --- /dev/null +++ b/packages/eslint-plugin-query/src/configs/index.test.ts @@ -0,0 +1,19 @@ +import { configs } from './index' + +describe('configs', () => { + it('should match snapshot', () => { + expect(configs).toMatchInlineSnapshot(` + { + "recommended": { + "plugins": [ + "@tanstack/eslint-plugin-query", + ], + "rules": { + "@tanstack/query/exhaustive-deps": "error", + "@tanstack/query/prefer-query-object-syntax": "error", + }, + }, + } + `) + }) +}) diff --git a/packages/eslint-plugin-query/src/configs/index.ts b/packages/eslint-plugin-query/src/configs/index.ts new file mode 100644 index 0000000000..83e5b818d7 --- /dev/null +++ b/packages/eslint-plugin-query/src/configs/index.ts @@ -0,0 +1,22 @@ +import { TSESLint } from '@typescript-eslint/utils' +import { rules } from '../rules' + +function generateRecommendedConfig( + allRules: Record>, +) { + return Object.entries(allRules).reduce((memo, [name, rule]) => { + const { recommended } = rule.meta.docs || {} + + return { + ...memo, + ...(recommended ? { [`@tanstack/query/${name}`]: recommended } : {}), + } + }, {} as Record) +} + +export const configs = { + recommended: { + plugins: ['@tanstack/eslint-plugin-query'], + rules: generateRecommendedConfig(rules), + }, +} diff --git a/packages/eslint-plugin-query/src/index.ts b/packages/eslint-plugin-query/src/index.ts index 9bbeb41360..eef4c020b5 100644 --- a/packages/eslint-plugin-query/src/index.ts +++ b/packages/eslint-plugin-query/src/index.ts @@ -1,5 +1,2 @@ -import { exhaustiveDepsRule } from './rules/exhaustive-deps.rule' - -export const rules = { - 'exhaustive-deps': exhaustiveDepsRule, -} +export { configs } from './configs' +export { rules } from './rules' diff --git a/packages/eslint-plugin-query/src/rules/exhaustive-deps.rule.ts b/packages/eslint-plugin-query/src/rules/exhaustive-deps.rule.ts deleted file mode 100644 index 2463582807..0000000000 --- a/packages/eslint-plugin-query/src/rules/exhaustive-deps.rule.ts +++ /dev/null @@ -1,179 +0,0 @@ -import type { JSONSchema, TSESLint, TSESTree } from '@typescript-eslint/utils' -import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils' -import { ExtraUtils } from '../extra-utils' - -const messages = { - missingDeps: - 'The following dependencies are missing in your queryKey: {{deps}}', -} - -const ruleSchema: JSONSchema.JSONSchema4 = { - type: 'array', - minItems: 0, - maxItems: 1, - items: { - type: 'object', - properties: { - whitelist: { - type: 'array', - items: { - type: 'string', - }, - }, - }, - }, -} - -const defaultRuleOptions = { whitelist: [] } - -type RuleMessage = keyof typeof messages -type RuleOptions = { whitelist?: string[] } -type RuleContext = Readonly> - -const createRule = ESLintUtils.RuleCreator( - () => 'https://github.com/tanstack/query', -) - -export const exhaustiveDepsRule = createRule<[RuleOptions], RuleMessage>({ - name: 'exhaustive-deps', - meta: { - docs: { - description: - 'This rule ensures that all dependencies are passed to the useQuery queryKey parameter', - recommended: 'warn', - }, - messages: messages, - type: 'problem', - fixable: 'code', - schema: ruleSchema, - }, - defaultOptions: [defaultRuleOptions], - create(context) { - return { - Property(node) { - if (ExtraUtils.isIdentifierWithName(node.key, 'queryKey')) { - runCheck({ node, context }) - } - }, - } - }, -}) - -function runCheck(params: { node: TSESTree.Property; context: RuleContext }) { - const { node, context } = params - const ruleOptions = { ...defaultRuleOptions, ...context.options[0] } - - if ( - node.parent === undefined || - !ExtraUtils.isObjectExpression(node.parent) - ) { - return - } - - const scopeManager = context.getSourceCode().scopeManager - const queryKey = ExtraUtils.findPropertyWithIdentifierKey( - node.parent.properties, - 'queryKey', - ) - const queryFn = ExtraUtils.findPropertyWithIdentifierKey( - node.parent.properties, - 'queryFn', - ) - - if ( - scopeManager === null || - queryKey === undefined || - queryFn === undefined || - queryFn.value.type !== AST_NODE_TYPES.ArrowFunctionExpression - ) { - return - } - - if (queryKey.value.type !== AST_NODE_TYPES.ArrayExpression) { - // TODO support query key factory - return - } - - const sourceCode = context.getSourceCode() - const queryKeyValue = queryKey.value - const refs = getExternalRefs({ scopeManager, node: queryFn.value }) - - const existingKeys = ExtraUtils.getNestedIdentifiers(queryKeyValue).map( - (identifier) => mapKeyNodeToText(identifier, sourceCode), - ) - - const missingRefs = refs - .map((ref) => ({ - ref: ref, - text: mapKeyNodeToText(ref.identifier, sourceCode), - })) - .filter(({ ref, text }) => { - return ( - !ExtraUtils.isAncestorIsCallee(ref.identifier) && - !ruleOptions.whitelist.includes(ref.identifier.name) && - !existingKeys.some((existingKey) => existingKey === text) - ) - }) - .map(({ ref, text }) => ({ identifier: ref.identifier, text: text })) - - const uniqueMissingRefs = uniqueBy(missingRefs, (x) => x.text) - - if (uniqueMissingRefs.length > 0) { - context.report({ - node: node, - messageId: 'missingDeps', - data: { - deps: uniqueMissingRefs.map((ref) => ref.text).join(', '), - }, - fix(fixer) { - const missingAsText = uniqueMissingRefs - .map((ref) => mapKeyNodeToText(ref.identifier, sourceCode)) - .join(', ') - - const existingWithMissing = sourceCode - .getText(queryKeyValue) - .replace(/\]$/, `, ${missingAsText}]`) - - return fixer.replaceText(queryKeyValue, existingWithMissing) - }, - }) - } -} - -function getExternalRefs(params: { - scopeManager: TSESLint.Scope.ScopeManager - node: TSESTree.Node -}): TSESLint.Scope.Reference[] { - const { scopeManager, node } = params - const scope = scopeManager.acquire(node) - - if (scope === null) { - return [] - } - - const readOnlyRefs = scope.references.filter((x) => x.isRead()) - const localRefIds = new Set( - [...scope.set.values()].map((x) => x.identifiers[0]), - ) - const externalRefs = readOnlyRefs.filter( - (x) => x.resolved === null || !localRefIds.has(x.resolved.identifiers[0]), - ) - - return uniqueBy(externalRefs, (x) => x.resolved) -} - -function mapKeyNodeToText( - node: TSESTree.Node, - sourceCode: Readonly, -) { - return sourceCode.getText( - ExtraUtils.traverseUpOnly(node, [ - AST_NODE_TYPES.MemberExpression, - AST_NODE_TYPES.Identifier, - ]), - ) -} - -function uniqueBy(arr: T[], fn: (x: T) => unknown): T[] { - return arr.filter((x, i, a) => a.findIndex((y) => fn(x) === fn(y)) === i) -} 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 new file mode 100644 index 0000000000..74bbc435a0 --- /dev/null +++ b/packages/eslint-plugin-query/src/rules/exhaustive-deps/exhaustive-deps.rule.ts @@ -0,0 +1,117 @@ +import { AST_NODE_TYPES } from '@typescript-eslint/utils' +import { ASTUtils } from '../../utils/ast-utils' +import { createRule } from '../../utils/create-rule' +import { uniqueBy } from '../../utils/unique-by' + +const QUERY_KEY = 'queryKey' +const QUERY_FN = 'queryFn' + +export const name = 'exhaustive-deps' + +export const rule = createRule({ + name, + meta: { + type: 'problem', + docs: { + description: 'Prefer object syntax for useQuery', + recommended: 'error', + }, + messages: { + missingDeps: `The following dependencies are missing in your queryKey: {{deps}}`, + }, + fixable: 'code', + schema: [], + }, + defaultOptions: [], + + create(context, _) { + return { + Property(node) { + if (ASTUtils.isIdentifierWithName(node.key, QUERY_KEY)) { + if ( + node.parent === undefined || + !ASTUtils.isObjectExpression(node.parent) + ) { + return + } + + const scopeManager = context.getSourceCode().scopeManager + const queryKey = ASTUtils.findPropertyWithIdentifierKey( + node.parent.properties, + QUERY_KEY, + ) + const queryFn = ASTUtils.findPropertyWithIdentifierKey( + node.parent.properties, + QUERY_FN, + ) + + if ( + scopeManager === null || + queryKey === undefined || + queryFn === undefined || + queryFn.value.type !== AST_NODE_TYPES.ArrowFunctionExpression + ) { + return + } + + if (queryKey.value.type !== AST_NODE_TYPES.ArrayExpression) { + // TODO support query key factory + return + } + + const sourceCode = context.getSourceCode() + const queryKeyValue = queryKey.value + const refs = ASTUtils.getExternalRefs({ + scopeManager, + node: queryFn.value, + }) + + const existingKeys = ASTUtils.getNestedIdentifiers(queryKeyValue).map( + (identifier) => ASTUtils.mapKeyNodeToText(identifier, sourceCode), + ) + + const missingRefs = refs + .map((ref) => ({ + ref: ref, + text: ASTUtils.mapKeyNodeToText(ref.identifier, sourceCode), + })) + .filter(({ ref, text }) => { + return ( + !ASTUtils.isAncestorIsCallee(ref.identifier) && + !existingKeys.some((existingKey) => existingKey === text) + ) + }) + .map(({ ref, text }) => ({ + identifier: ref.identifier, + text: text, + })) + + const uniqueMissingRefs = uniqueBy(missingRefs, (x) => x.text) + + if (uniqueMissingRefs.length > 0) { + context.report({ + node: node, + messageId: 'missingDeps', + data: { + deps: uniqueMissingRefs.map((ref) => ref.text).join(', '), + }, + fix(fixer) { + const missingAsText = uniqueMissingRefs + .map((ref) => + ASTUtils.mapKeyNodeToText(ref.identifier, sourceCode), + ) + .join(', ') + + const existingWithMissing = sourceCode + .getText(queryKeyValue) + .replace(/\]$/, `, ${missingAsText}]`) + + return fixer.replaceText(queryKeyValue, existingWithMissing) + }, + }) + } + } + }, + } + }, +}) diff --git a/packages/eslint-plugin-query/src/rules/exhaustive-deps.test.ts b/packages/eslint-plugin-query/src/rules/exhaustive-deps/exhaustive-deps.test.ts similarity index 98% rename from packages/eslint-plugin-query/src/rules/exhaustive-deps.test.ts rename to packages/eslint-plugin-query/src/rules/exhaustive-deps/exhaustive-deps.test.ts index 13ea6230e3..6625acf001 100644 --- a/packages/eslint-plugin-query/src/rules/exhaustive-deps.test.ts +++ b/packages/eslint-plugin-query/src/rules/exhaustive-deps/exhaustive-deps.test.ts @@ -1,12 +1,12 @@ import { ESLintUtils } from '@typescript-eslint/utils' -import { exhaustiveDepsRule } from './exhaustive-deps.rule' +import { rule } from './exhaustive-deps.rule' const ruleTester = new ESLintUtils.RuleTester({ parser: '@typescript-eslint/parser', settings: {}, }) -ruleTester.run('exhaustive-deps', exhaustiveDepsRule, { +ruleTester.run('exhaustive-deps', rule, { valid: [ { name: 'should pass when deps are passed in array', diff --git a/packages/eslint-plugin-query/src/rules/index.ts b/packages/eslint-plugin-query/src/rules/index.ts new file mode 100644 index 0000000000..84a9708fa6 --- /dev/null +++ b/packages/eslint-plugin-query/src/rules/index.ts @@ -0,0 +1,7 @@ +import * as exaustiveDeps from './exhaustive-deps/exhaustive-deps.rule' +import * as preferObjectSyntax from './prefer-query-object-syntax/prefer-query-object-syntax' + +export const rules = { + [exaustiveDeps.name]: exaustiveDeps.rule, + [preferObjectSyntax.name]: preferObjectSyntax.rule, +} diff --git a/packages/eslint-plugin-query/src/rules/prefer-query-object-syntax/prefer-query-object-syntax.test.ts b/packages/eslint-plugin-query/src/rules/prefer-query-object-syntax/prefer-query-object-syntax.test.ts new file mode 100644 index 0000000000..b8e4101cc6 --- /dev/null +++ b/packages/eslint-plugin-query/src/rules/prefer-query-object-syntax/prefer-query-object-syntax.test.ts @@ -0,0 +1,126 @@ +import { rule, name } from './prefer-query-object-syntax' +import { createRuleTester, normalizeIndent } from '../../utils/test-utils' + +const ruleTester = createRuleTester() + +ruleTester.run(name, rule, { + valid: [ + { + code: normalizeIndent` + useQuery() + `, + }, + { + code: normalizeIndent` + import { useQuery } from "@tanstack/react-query"; + useQuery(); + `, + }, + { + code: normalizeIndent` + import { useQuery } from "@tanstack/react-query"; + useQuery({ queryKey, queryFn, enabled }); + `, + }, + { + code: normalizeIndent` + import { useQuery } from "@tanstack/react-query"; + const result = useQuery({ queryKey, queryFn, enabled }); + `, + }, + { + code: normalizeIndent` + import { useQuery } from "somewhere-else"; + useQuery(queryKey, queryFn, { enabled }); + `, + }, + ], + + invalid: [ + { + code: normalizeIndent` + import { useQuery } from "@tanstack/react-query"; + useQuery('data'); + `, + errors: [{ messageId: 'preferObjectSyntax' }], + output: normalizeIndent` + import { useQuery } from "@tanstack/react-query"; + useQuery({ queryKey: 'data' }); + `, + }, + { + code: normalizeIndent` + import { useQuery } from "@tanstack/react-query"; + useQuery(queryKey); + `, + errors: [{ messageId: 'preferObjectSyntax' }], + output: normalizeIndent` + import { useQuery } from "@tanstack/react-query"; + useQuery({ queryKey }); + `, + }, + { + code: normalizeIndent` + import { useQuery } from "@tanstack/react-query"; + useQuery(queryKey, queryFn); + `, + errors: [{ messageId: 'preferObjectSyntax' }], + // no autofix + }, + { + code: normalizeIndent` + import { useQuery } from "@tanstack/react-query"; + useQuery('data', () => fetchData()); + `, + errors: [{ messageId: 'preferObjectSyntax' }], + output: normalizeIndent` + import { useQuery } from "@tanstack/react-query"; + useQuery({ queryKey: 'data', queryFn: () => fetchData() }); + `, + }, + { + code: normalizeIndent` + import { useQuery } from "@tanstack/react-query"; + useQuery(queryKey, queryFn, { enabled }); + `, + errors: [{ messageId: 'preferObjectSyntax' }], + output: normalizeIndent` + import { useQuery } from "@tanstack/react-query"; + useQuery({ queryKey, queryFn, enabled }); + `, + }, + { + code: normalizeIndent` + import { useQuery } from "@tanstack/react-query"; + useQuery('data', () => fetchData(), { enabled: false }); + `, + errors: [{ messageId: 'preferObjectSyntax' }], + output: normalizeIndent` + import { useQuery } from "@tanstack/react-query"; + useQuery({ queryKey: 'data', queryFn: () => fetchData(), enabled: false }); + `, + }, + { + code: normalizeIndent` + import { useQuery } from "@tanstack/react-query"; + useQuery(queryKey, { queryFn, enabled }); + `, + errors: [{ messageId: 'preferObjectSyntax' }], + output: normalizeIndent` + import { useQuery } from "@tanstack/react-query"; + useQuery({ queryKey, queryFn, enabled }); + `, + }, + { + code: normalizeIndent` + import { useQuery } from "@tanstack/react-query"; + useQuery('data', { queryFn: () => fetchData(), enabled: false }); + `, + errors: [{ messageId: 'preferObjectSyntax' }], + output: normalizeIndent` + import { useQuery } from "@tanstack/react-query"; + useQuery({ queryKey: 'data', queryFn: () => fetchData(), enabled: false }); + `, + }, + ], +}) diff --git a/packages/eslint-plugin-query/src/rules/prefer-query-object-syntax/prefer-query-object-syntax.ts b/packages/eslint-plugin-query/src/rules/prefer-query-object-syntax/prefer-query-object-syntax.ts new file mode 100644 index 0000000000..b3f6ada3e9 --- /dev/null +++ b/packages/eslint-plugin-query/src/rules/prefer-query-object-syntax/prefer-query-object-syntax.ts @@ -0,0 +1,118 @@ +import { TSESLint } from '@typescript-eslint/utils' +import { createRule } from '../../utils/create-rule' +import { ASTUtils } from '../../utils/ast-utils' + +const USE_QUERY = 'useQuery' + +export const name = 'prefer-query-object-syntax' + +export const rule = createRule({ + name, + meta: { + type: 'problem', + docs: { + description: 'Prefer object syntax for useQuery', + recommended: 'error', + }, + messages: { + preferObjectSyntax: `Objects syntax for useQuery is preferred`, + }, + fixable: 'code', + schema: [], + }, + defaultOptions: [], + + create(context, _, helpers) { + const sourceCode = context.getSourceCode() + return { + CallExpression(node) { + const isUseQuery = + node.callee.type === 'Identifier' && + node.callee.name === USE_QUERY && + helpers.isReactQueryImport(node.callee) + if (!isUseQuery) { + return + } + + const firstArgument = node.arguments[0] + if (!firstArgument) { + return + } + + const hasFirstObjectArgument = firstArgument.type === 'ObjectExpression' + if (hasFirstObjectArgument) { + return + } + + const secondArgument = node.arguments[1] + const thirdArgument = node.arguments[2] + + const optionsObject = + secondArgument?.type === 'ObjectExpression' + ? secondArgument + : thirdArgument?.type === 'ObjectExpression' + ? thirdArgument + : undefined + + if ( + secondArgument && + !thirdArgument && + secondArgument !== optionsObject && + secondArgument.type === 'Identifier' + ) { + // Unable to determine if the secondArgument identifier is the options object or query fn. + // User has to fix the code manually. + context.report({ node, messageId: 'preferObjectSyntax' }) + return + } + + context.report({ + node, + messageId: 'preferObjectSyntax', + fix(fixer) { + const ruleFixes: TSESLint.RuleFix[] = [] + const optionsObjectProperties: string[] = [] + + // queryKey + const queryKey = sourceCode.getText(firstArgument) + const queryKeyProperty = + queryKey === 'queryKey' ? 'queryKey' : `queryKey: ${queryKey}` + optionsObjectProperties.push(queryKeyProperty) + + // queryFn + if (secondArgument && secondArgument !== optionsObject) { + const queryFn = sourceCode.getText(secondArgument) + const queryFnProperty = + queryFn === 'queryFn' ? 'queryFn' : `queryFn: ${queryFn}` + optionsObjectProperties.push(queryFnProperty) + } + + // options + if (optionsObject) { + const existingObjectProperties = optionsObject.properties.map( + (objectLiteral) => { + return sourceCode.getText(objectLiteral) + }, + ) + optionsObjectProperties.push(...existingObjectProperties) + } + + const argumentsRange = ASTUtils.getRangeOfArguments(node) + if (argumentsRange) { + ruleFixes.push(fixer.removeRange(argumentsRange)) + } + + ruleFixes.push( + fixer.insertTextAfterRange( + [node.range[0], node.range[1] - 1], + `{ ${optionsObjectProperties.join(', ')} }`, + ), + ) + + return ruleFixes + }, + }) + }, + } + }, +}) diff --git a/packages/eslint-plugin-query/src/extra-utils.ts b/packages/eslint-plugin-query/src/utils/ast-utils.ts similarity index 53% rename from packages/eslint-plugin-query/src/extra-utils.ts rename to packages/eslint-plugin-query/src/utils/ast-utils.ts index 16bca55f09..dc16fe4e9f 100644 --- a/packages/eslint-plugin-query/src/extra-utils.ts +++ b/packages/eslint-plugin-query/src/utils/ast-utils.ts @@ -1,7 +1,8 @@ -import type { TSESTree } from '@typescript-eslint/utils' +import type { TSESLint, TSESTree } from '@typescript-eslint/utils' import { AST_NODE_TYPES } from '@typescript-eslint/utils' +import { uniqueBy } from './unique-by' -export const ExtraUtils = { +export const ASTUtils = { isIdentifier(node: TSESTree.Node): node is TSESTree.Identifier { return node.type === AST_NODE_TYPES.Identifier }, @@ -9,7 +10,7 @@ export const ExtraUtils = { node: TSESTree.Node, name: string, ): node is TSESTree.Identifier { - return ExtraUtils.isIdentifier(node) && node.name === name + return ASTUtils.isIdentifier(node) && node.name === name }, isProperty(node: TSESTree.Node): node is TSESTree.Property { return node.type === AST_NODE_TYPES.Property @@ -22,8 +23,7 @@ export const ExtraUtils = { key: string, ): node is TSESTree.Property { return ( - ExtraUtils.isProperty(node) && - ExtraUtils.isIdentifierWithName(node.key, key) + ASTUtils.isProperty(node) && ASTUtils.isIdentifierWithName(node.key, key) ) }, findPropertyWithIdentifierKey( @@ -31,40 +31,40 @@ export const ExtraUtils = { key: string, ): TSESTree.Property | undefined { return properties.find((x) => - ExtraUtils.isPropertyWithIdentifierKey(x, key), + ASTUtils.isPropertyWithIdentifierKey(x, key), ) as TSESTree.Property | undefined }, getNestedIdentifiers(node: TSESTree.Node): TSESTree.Identifier[] { const identifiers: TSESTree.Identifier[] = [] - if (ExtraUtils.isIdentifier(node)) { + if (ASTUtils.isIdentifier(node)) { identifiers.push(node) } if (node.type === AST_NODE_TYPES.ArrayExpression) { node.elements.forEach((x) => { - identifiers.push(...ExtraUtils.getNestedIdentifiers(x)) + identifiers.push(...ASTUtils.getNestedIdentifiers(x)) }) } if (node.type === AST_NODE_TYPES.ObjectExpression) { node.properties.forEach((x) => { - identifiers.push(...ExtraUtils.getNestedIdentifiers(x)) + identifiers.push(...ASTUtils.getNestedIdentifiers(x)) }) } if (node.type === AST_NODE_TYPES.Property) { - identifiers.push(...ExtraUtils.getNestedIdentifiers(node.value)) + identifiers.push(...ASTUtils.getNestedIdentifiers(node.value)) } if (node.type === AST_NODE_TYPES.TemplateLiteral) { node.expressions.forEach((x) => { - identifiers.push(...ExtraUtils.getNestedIdentifiers(x)) + identifiers.push(...ASTUtils.getNestedIdentifiers(x)) }) } if (node.type === AST_NODE_TYPES.MemberExpression) { - identifiers.push(...ExtraUtils.getNestedIdentifiers(node.object)) + identifiers.push(...ASTUtils.getNestedIdentifiers(node.object)) } return identifiers @@ -98,9 +98,50 @@ export const ExtraUtils = { const parent = identifier.parent if (parent !== undefined && allowedNodeTypes.includes(parent.type)) { - return ExtraUtils.traverseUpOnly(parent, allowedNodeTypes) + return ASTUtils.traverseUpOnly(parent, allowedNodeTypes) } return identifier }, + getRangeOfArguments( + node: TSESTree.CallExpression, + ): TSESTree.Range | undefined { + const firstArgument = node.arguments[0] + const lastArgument = node.arguments[node.arguments.length - 1] + return firstArgument && lastArgument + ? [firstArgument.range[0], lastArgument.range[1]] + : undefined + }, + getExternalRefs(params: { + scopeManager: TSESLint.Scope.ScopeManager + node: TSESTree.Node + }): TSESLint.Scope.Reference[] { + const { scopeManager, node } = params + const scope = scopeManager.acquire(node) + + if (scope === null) { + return [] + } + + const readOnlyRefs = scope.references.filter((x) => x.isRead()) + const localRefIds = new Set( + [...scope.set.values()].map((x) => x.identifiers[0]), + ) + const externalRefs = readOnlyRefs.filter( + (x) => x.resolved === null || !localRefIds.has(x.resolved.identifiers[0]), + ) + + return uniqueBy(externalRefs, (x) => x.resolved) + }, + mapKeyNodeToText( + node: TSESTree.Node, + sourceCode: Readonly, + ) { + return sourceCode.getText( + ASTUtils.traverseUpOnly(node, [ + AST_NODE_TYPES.MemberExpression, + AST_NODE_TYPES.Identifier, + ]), + ) + }, } diff --git a/packages/eslint-plugin-query/src/utils/create-rule.ts b/packages/eslint-plugin-query/src/utils/create-rule.ts new file mode 100644 index 0000000000..b67de07697 --- /dev/null +++ b/packages/eslint-plugin-query/src/utils/create-rule.ts @@ -0,0 +1,22 @@ +import { ESLintUtils } from '@typescript-eslint/utils' +import { + detectReactQueryImports, + EnhancedCreate, +} from './detect-react-query-imports' + +const getDocsUrl = (ruleName: string): string => + `https://github.com/tanstack/query/tree/master/packages/eslint-plugin-query/docs/rules/${ruleName}.md` + +type EslintRule = Omit< + Parameters>[0], + 'create' +> & { + create: EnhancedCreate +} + +export function createRule({ create, ...rest }: EslintRule) { + return ESLintUtils.RuleCreator(getDocsUrl)({ + ...rest, + create: detectReactQueryImports(create), + }) +} diff --git a/packages/eslint-plugin-query/src/utils/detect-react-query-imports.ts b/packages/eslint-plugin-query/src/utils/detect-react-query-imports.ts new file mode 100644 index 0000000000..eb37ea032b --- /dev/null +++ b/packages/eslint-plugin-query/src/utils/detect-react-query-imports.ts @@ -0,0 +1,72 @@ +import { ESLintUtils, TSESLint, TSESTree } from '@typescript-eslint/utils' + +type Create = Parameters< + ReturnType +>[0]['create'] + +type Context = Parameters[0] +type Options = Parameters[1] +type Helpers = { + isReactQueryImport: (node: TSESTree.Identifier) => boolean +} + +export type EnhancedCreate = ( + context: Context, + options: Options, + helpers: Helpers, +) => ReturnType + +export function detectReactQueryImports(create: EnhancedCreate): Create { + return (context, optionsWithDefault) => { + const reactQueryImportSpecifiers: TSESTree.ImportClause[] = [] + + const helpers: Helpers = { + isReactQueryImport(node) { + return !!reactQueryImportSpecifiers.find((specifier) => { + if (specifier.type === 'ImportSpecifier') { + return node.name === specifier.local.name + } + }) + }, + } + + const detectionInstructions: TSESLint.RuleListener = { + ImportDeclaration(node) { + if ( + node.specifiers.length > 0 && + node.importKind === 'value' && + node.source.value === '@tanstack/react-query' + ) { + reactQueryImportSpecifiers.push(...node.specifiers) + } + }, + } + + // Call original rule definition + const ruleInstructions = create(context, optionsWithDefault, helpers) + const enhancedRuleInstructions: TSESLint.RuleListener = {} + + const allKeys = new Set( + Object.keys(detectionInstructions).concat(Object.keys(ruleInstructions)), + ) + + // Iterate over ALL instructions keys so we can override original rule instructions + // to prevent their execution if conditions to report errors are not met. + allKeys.forEach((instruction) => { + enhancedRuleInstructions[instruction] = (node) => { + if (instruction in detectionInstructions) { + detectionInstructions[instruction]?.(node) + } + + // TODO: canReportErrors() + if (ruleInstructions[instruction]) { + return ruleInstructions[instruction]?.(node) + } + + return undefined + } + }) + + return enhancedRuleInstructions + } +} diff --git a/packages/eslint-plugin-query/src/utils/test-utils.ts b/packages/eslint-plugin-query/src/utils/test-utils.ts new file mode 100644 index 0000000000..a65614d6ab --- /dev/null +++ b/packages/eslint-plugin-query/src/utils/test-utils.ts @@ -0,0 +1,82 @@ +/** + +MIT License + +Copyright (c) 2019 Mario Beltrán Alarcón + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +import { resolve } from 'path' + +import { TSESLint } from '@typescript-eslint/utils' + +const DEFAULT_TEST_CASE_CONFIG = { + filename: 'MyComponent.test.js', +} + +class TestingLibraryRuleTester extends TSESLint.RuleTester { + run>( + ruleName: string, + rule: TSESLint.RuleModule, + tests: TSESLint.RunTests, + ): void { + const { valid, invalid } = tests + + const finalValid = valid.map((testCase) => { + if (typeof testCase === 'string') { + return { + ...DEFAULT_TEST_CASE_CONFIG, + code: testCase, + } + } + + return { ...DEFAULT_TEST_CASE_CONFIG, ...testCase } + }) + const finalInvalid = invalid.map((testCase) => ({ + ...DEFAULT_TEST_CASE_CONFIG, + ...testCase, + })) + + super.run(ruleName, rule, { valid: finalValid, invalid: finalInvalid }) + } +} + +export const createRuleTester = ( + parserOptions: Partial = {}, +): TSESLint.RuleTester => { + return new TestingLibraryRuleTester({ + parser: resolve('./node_modules/@typescript-eslint/parser'), + parserOptions: { + ecmaVersion: 2018, + sourceType: 'module', + ecmaFeatures: { + jsx: true, + }, + ...parserOptions, + }, + }) +} + +export function normalizeIndent(template: TemplateStringsArray) { + const codeLines = template[0]?.split('\n') ?? [''] + const leftPadding = codeLines[1]?.match(/\s+/)?.[0] ?? '' + return codeLines.map((line) => line.slice(leftPadding.length)).join('\n') +} diff --git a/packages/eslint-plugin-query/src/utils/unique-by.ts b/packages/eslint-plugin-query/src/utils/unique-by.ts new file mode 100644 index 0000000000..0738561af1 --- /dev/null +++ b/packages/eslint-plugin-query/src/utils/unique-by.ts @@ -0,0 +1,3 @@ +export function uniqueBy(arr: T[], fn: (x: T) => unknown): T[] { + return arr.filter((x, i, a) => a.findIndex((y) => fn(x) === fn(y)) === i) +} From 432017f9f05454c6dd951ab6cf944b9f854e1ab7 Mon Sep 17 00:00:00 2001 From: Newbie012 Date: Mon, 31 Oct 2022 00:03:05 +0200 Subject: [PATCH 27/40] fix indention --- .../exhaustive-deps/exhaustive-deps.rule.ts | 147 +++++++++--------- 1 file changed, 73 insertions(+), 74 deletions(-) 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 74bbc435a0..e870bbd640 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 @@ -24,92 +24,91 @@ export const rule = createRule({ }, defaultOptions: [], - create(context, _) { + create(context) { return { Property(node) { - if (ASTUtils.isIdentifierWithName(node.key, QUERY_KEY)) { - if ( - node.parent === undefined || - !ASTUtils.isObjectExpression(node.parent) - ) { - return - } + if ( + node.parent === undefined || + !ASTUtils.isObjectExpression(node.parent) || + !ASTUtils.isIdentifierWithName(node.key, QUERY_KEY) + ) { + return + } - const scopeManager = context.getSourceCode().scopeManager - const queryKey = ASTUtils.findPropertyWithIdentifierKey( - node.parent.properties, - QUERY_KEY, - ) - const queryFn = ASTUtils.findPropertyWithIdentifierKey( - node.parent.properties, - QUERY_FN, - ) + const scopeManager = context.getSourceCode().scopeManager + const queryKey = ASTUtils.findPropertyWithIdentifierKey( + node.parent.properties, + QUERY_KEY, + ) + const queryFn = ASTUtils.findPropertyWithIdentifierKey( + node.parent.properties, + QUERY_FN, + ) - if ( - scopeManager === null || - queryKey === undefined || - queryFn === undefined || - queryFn.value.type !== AST_NODE_TYPES.ArrowFunctionExpression - ) { - return - } + if ( + scopeManager === null || + queryKey === undefined || + queryFn === undefined || + queryFn.value.type !== AST_NODE_TYPES.ArrowFunctionExpression + ) { + return + } - if (queryKey.value.type !== AST_NODE_TYPES.ArrayExpression) { - // TODO support query key factory - return - } + if (queryKey.value.type !== AST_NODE_TYPES.ArrayExpression) { + // TODO support query key factory + return + } - const sourceCode = context.getSourceCode() - const queryKeyValue = queryKey.value - const refs = ASTUtils.getExternalRefs({ - scopeManager, - node: queryFn.value, - }) + const sourceCode = context.getSourceCode() + const queryKeyValue = queryKey.value + const refs = ASTUtils.getExternalRefs({ + scopeManager, + node: queryFn.value, + }) - const existingKeys = ASTUtils.getNestedIdentifiers(queryKeyValue).map( - (identifier) => ASTUtils.mapKeyNodeToText(identifier, sourceCode), - ) + const existingKeys = ASTUtils.getNestedIdentifiers(queryKeyValue).map( + (identifier) => ASTUtils.mapKeyNodeToText(identifier, sourceCode), + ) - const missingRefs = refs - .map((ref) => ({ - ref: ref, - text: ASTUtils.mapKeyNodeToText(ref.identifier, sourceCode), - })) - .filter(({ ref, text }) => { - return ( - !ASTUtils.isAncestorIsCallee(ref.identifier) && - !existingKeys.some((existingKey) => existingKey === text) - ) - }) - .map(({ ref, text }) => ({ - identifier: ref.identifier, - text: text, - })) + const missingRefs = refs + .map((ref) => ({ + ref: ref, + text: ASTUtils.mapKeyNodeToText(ref.identifier, sourceCode), + })) + .filter(({ ref, text }) => { + return ( + !ASTUtils.isAncestorIsCallee(ref.identifier) && + !existingKeys.some((existingKey) => existingKey === text) + ) + }) + .map(({ ref, text }) => ({ + identifier: ref.identifier, + text: text, + })) - const uniqueMissingRefs = uniqueBy(missingRefs, (x) => x.text) + const uniqueMissingRefs = uniqueBy(missingRefs, (x) => x.text) - if (uniqueMissingRefs.length > 0) { - context.report({ - node: node, - messageId: 'missingDeps', - data: { - deps: uniqueMissingRefs.map((ref) => ref.text).join(', '), - }, - fix(fixer) { - const missingAsText = uniqueMissingRefs - .map((ref) => - ASTUtils.mapKeyNodeToText(ref.identifier, sourceCode), - ) - .join(', ') + if (uniqueMissingRefs.length > 0) { + context.report({ + node: node, + messageId: 'missingDeps', + data: { + deps: uniqueMissingRefs.map((ref) => ref.text).join(', '), + }, + fix(fixer) { + const missingAsText = uniqueMissingRefs + .map((ref) => + ASTUtils.mapKeyNodeToText(ref.identifier, sourceCode), + ) + .join(', ') - const existingWithMissing = sourceCode - .getText(queryKeyValue) - .replace(/\]$/, `, ${missingAsText}]`) + const existingWithMissing = sourceCode + .getText(queryKeyValue) + .replace(/\]$/, `, ${missingAsText}]`) - return fixer.replaceText(queryKeyValue, existingWithMissing) - }, - }) - } + return fixer.replaceText(queryKeyValue, existingWithMissing) + }, + }) } }, } From 96d9192a4a59be0046210119de65d3627c89d228 Mon Sep 17 00:00:00 2001 From: Newbie012 Date: Mon, 31 Oct 2022 00:10:47 +0200 Subject: [PATCH 28/40] fix headline --- packages/eslint-plugin-query/docs/rules/exhaustive-deps.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin-query/docs/rules/exhaustive-deps.md b/packages/eslint-plugin-query/docs/rules/exhaustive-deps.md index 8ded72791c..f113bb4936 100644 --- a/packages/eslint-plugin-query/docs/rules/exhaustive-deps.md +++ b/packages/eslint-plugin-query/docs/rules/exhaustive-deps.md @@ -1,4 +1,4 @@ -# Prefer object syntax for useQuery (`@tanstack/query/exhaustive-deps`) +# Exhaustive dependencies for `queryKey` (`@tanstack/query/exhaustive-deps`) TODO @TkDodo a description of this rule From bc4c70a216a8c7dbc8ca1750ed0f7d21ff2f4527 Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Tue, 1 Nov 2022 09:13:04 +0100 Subject: [PATCH 29/40] add rule description --- packages/eslint-plugin-query/docs/rules/exhaustive-deps.md | 3 ++- .../docs/rules/prefer-query-object-syntax.md | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin-query/docs/rules/exhaustive-deps.md b/packages/eslint-plugin-query/docs/rules/exhaustive-deps.md index f113bb4936..32640c7b5b 100644 --- a/packages/eslint-plugin-query/docs/rules/exhaustive-deps.md +++ b/packages/eslint-plugin-query/docs/rules/exhaustive-deps.md @@ -1,6 +1,7 @@ # Exhaustive dependencies for `queryKey` (`@tanstack/query/exhaustive-deps`) -TODO @TkDodo a description of this rule +Query keys should be seen like a dependency array to your query function: Every variable that is used inside the queryFn should be added to the query key. +This makes sure that queries are cached independently and that queries are refetched automatically when the variables changes. ## Rule Details diff --git a/packages/eslint-plugin-query/docs/rules/prefer-query-object-syntax.md b/packages/eslint-plugin-query/docs/rules/prefer-query-object-syntax.md index 3907f751f6..dcdfaa270d 100644 --- a/packages/eslint-plugin-query/docs/rules/prefer-query-object-syntax.md +++ b/packages/eslint-plugin-query/docs/rules/prefer-query-object-syntax.md @@ -12,7 +12,7 @@ useQuery(queryKey, queryFn?, options?) useQuery(options) ``` -This rule prefers the second option, as it is more consistent with other React Query hooks, like `useQueries` or `useMutation`. +This rule prefers the second option, as it is more consistent with other React Query hooks, like `useQueries`. It will also be the only available option in a future major version. ## Rule Details From c83ae522bba342f43469ca3e19760e0413657b76 Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Tue, 1 Nov 2022 09:19:32 +0100 Subject: [PATCH 30/40] fix lint errors --- packages/eslint-plugin-query/src/configs/index.ts | 2 +- .../prefer-query-object-syntax.ts | 2 +- packages/eslint-plugin-query/src/utils/create-rule.ts | 6 ++---- .../src/utils/detect-react-query-imports.ts | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/eslint-plugin-query/src/configs/index.ts b/packages/eslint-plugin-query/src/configs/index.ts index 83e5b818d7..e5e5aba260 100644 --- a/packages/eslint-plugin-query/src/configs/index.ts +++ b/packages/eslint-plugin-query/src/configs/index.ts @@ -1,4 +1,4 @@ -import { TSESLint } from '@typescript-eslint/utils' +import type { TSESLint } from '@typescript-eslint/utils' import { rules } from '../rules' function generateRecommendedConfig( diff --git a/packages/eslint-plugin-query/src/rules/prefer-query-object-syntax/prefer-query-object-syntax.ts b/packages/eslint-plugin-query/src/rules/prefer-query-object-syntax/prefer-query-object-syntax.ts index b3f6ada3e9..f3d46bae7b 100644 --- a/packages/eslint-plugin-query/src/rules/prefer-query-object-syntax/prefer-query-object-syntax.ts +++ b/packages/eslint-plugin-query/src/rules/prefer-query-object-syntax/prefer-query-object-syntax.ts @@ -1,4 +1,4 @@ -import { TSESLint } from '@typescript-eslint/utils' +import type { TSESLint } from '@typescript-eslint/utils' import { createRule } from '../../utils/create-rule' import { ASTUtils } from '../../utils/ast-utils' diff --git a/packages/eslint-plugin-query/src/utils/create-rule.ts b/packages/eslint-plugin-query/src/utils/create-rule.ts index b67de07697..0193af4c37 100644 --- a/packages/eslint-plugin-query/src/utils/create-rule.ts +++ b/packages/eslint-plugin-query/src/utils/create-rule.ts @@ -1,8 +1,6 @@ import { ESLintUtils } from '@typescript-eslint/utils' -import { - detectReactQueryImports, - EnhancedCreate, -} from './detect-react-query-imports' +import type { EnhancedCreate } from './detect-react-query-imports' +import { detectReactQueryImports } from './detect-react-query-imports' const getDocsUrl = (ruleName: string): string => `https://github.com/tanstack/query/tree/master/packages/eslint-plugin-query/docs/rules/${ruleName}.md` diff --git a/packages/eslint-plugin-query/src/utils/detect-react-query-imports.ts b/packages/eslint-plugin-query/src/utils/detect-react-query-imports.ts index eb37ea032b..54388d49f8 100644 --- a/packages/eslint-plugin-query/src/utils/detect-react-query-imports.ts +++ b/packages/eslint-plugin-query/src/utils/detect-react-query-imports.ts @@ -1,4 +1,4 @@ -import { ESLintUtils, TSESLint, TSESTree } from '@typescript-eslint/utils' +import type { ESLintUtils, TSESLint, TSESTree } from '@typescript-eslint/utils' type Create = Parameters< ReturnType From 260d231c70029e0632adebfce712db6d42c4c804 Mon Sep 17 00:00:00 2001 From: Newbie012 Date: Tue, 1 Nov 2022 11:07:28 +0200 Subject: [PATCH 31/40] widen import check for all adapters --- .../src/utils/detect-react-query-imports.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin-query/src/utils/detect-react-query-imports.ts b/packages/eslint-plugin-query/src/utils/detect-react-query-imports.ts index eb37ea032b..068294995e 100644 --- a/packages/eslint-plugin-query/src/utils/detect-react-query-imports.ts +++ b/packages/eslint-plugin-query/src/utils/detect-react-query-imports.ts @@ -35,7 +35,8 @@ export function detectReactQueryImports(create: EnhancedCreate): Create { if ( node.specifiers.length > 0 && node.importKind === 'value' && - node.source.value === '@tanstack/react-query' + node.source.value.startsWith("@tanstack/") && + node.source.value.endsWith("-query") ) { reactQueryImportSpecifiers.push(...node.specifiers) } From d8f6dfe51ad4a7bd25754a9bf3725eee0cdedeb0 Mon Sep 17 00:00:00 2001 From: Newbie012 Date: Tue, 1 Nov 2022 11:09:24 +0200 Subject: [PATCH 32/40] add support for solid-query (createQuery) --- examples/react/basic/.eslintrc | 7 +- examples/solid/simple/.eslintrc | 1 + examples/solid/simple/package.json | 1 + .../exhaustive-deps/exhaustive-deps.test.ts | 20 +- .../prefer-query-object-syntax.test.ts | 33 ++- .../prefer-query-object-syntax.ts | 4 +- pnpm-lock.yaml | 207 ++++++++++++++---- 7 files changed, 216 insertions(+), 57 deletions(-) diff --git a/examples/react/basic/.eslintrc b/examples/react/basic/.eslintrc index f6ae985ac4..1699a80e89 100644 --- a/examples/react/basic/.eslintrc +++ b/examples/react/basic/.eslintrc @@ -1,8 +1,3 @@ { - "extends": ["react-app", "prettier"], - "plugins": ["@tanstack/eslint-plugin-query"], - "rules": { - "@tanstack/query/exhaustive-deps": "error", - "@tanstack/query/prefer-query-object-syntax": "error" - } + "extends": ["react-app", "prettier", "plugin:@tanstack/eslint-plugin-query/recommended"] } diff --git a/examples/solid/simple/.eslintrc b/examples/solid/simple/.eslintrc index f40d2bc7f1..137996aecd 100644 --- a/examples/solid/simple/.eslintrc +++ b/examples/solid/simple/.eslintrc @@ -1,4 +1,5 @@ { + "extends": ["plugin:@tanstack/eslint-plugin-query/recommended"], "parserOptions": { "project": "./tsconfig.json", "sourceType": "module" diff --git a/examples/solid/simple/package.json b/examples/solid/simple/package.json index 1a98f546af..8d777ca6f0 100644 --- a/examples/solid/simple/package.json +++ b/examples/solid/simple/package.json @@ -14,6 +14,7 @@ "solid-js": "^1.5.1" }, "devDependencies": { + "@tanstack/eslint-plugin-query": "^4.13.0", "typescript": "^4.8.2", "vite": "^3.0.9", "vite-plugin-solid": "^2.3.9" diff --git a/packages/eslint-plugin-query/src/rules/exhaustive-deps/exhaustive-deps.test.ts b/packages/eslint-plugin-query/src/rules/exhaustive-deps/exhaustive-deps.test.ts index 6625acf001..137a46caef 100644 --- a/packages/eslint-plugin-query/src/rules/exhaustive-deps/exhaustive-deps.test.ts +++ b/packages/eslint-plugin-query/src/rules/exhaustive-deps/exhaustive-deps.test.ts @@ -9,9 +9,13 @@ const ruleTester = new ESLintUtils.RuleTester({ ruleTester.run('exhaustive-deps', rule, { valid: [ { - name: 'should pass when deps are passed in array', + name: 'should pass when deps are passed in array (react)', code: 'useQuery({ queryKey: ["todos"], queryFn: fetchTodos });', }, + { + name: 'should pass when deps are passed in array (solid)', + code: 'createQuery({ queryKey: ["todos"], queryFn: fetchTodos });', + }, { name: 'should pass when deps are passed in array', code: 'useQuery({ queryKey: ["entity", id], queryFn: () => api.getEntity(id) });', @@ -71,7 +75,7 @@ ruleTester.run('exhaustive-deps', rule, { errors: [{ messageId: 'missingDeps', data: { deps: 'id' } }], }, { - name: 'should fail when no deps are passed', + name: 'should fail when no deps are passed (react)', code: ` const id = 1; useQuery({ queryKey: ["entity"], queryFn: () => api.getEntity(id) }); @@ -82,6 +86,18 @@ ruleTester.run('exhaustive-deps', rule, { `, errors: [{ messageId: 'missingDeps', data: { deps: 'id' } }], }, + { + name: 'should fail when no deps are passed (solid)', + code: ` + const id = 1; + createQuery({ queryKey: ["entity"], queryFn: () => api.getEntity(id) }); + `, + output: ` + const id = 1; + createQuery({ queryKey: ["entity", id], queryFn: () => api.getEntity(id) }); + `, + errors: [{ messageId: 'missingDeps', data: { deps: 'id' } }], + }, { name: 'should fail when deps are passed incorrectly', code: ` diff --git a/packages/eslint-plugin-query/src/rules/prefer-query-object-syntax/prefer-query-object-syntax.test.ts b/packages/eslint-plugin-query/src/rules/prefer-query-object-syntax/prefer-query-object-syntax.test.ts index b8e4101cc6..d506f9798d 100644 --- a/packages/eslint-plugin-query/src/rules/prefer-query-object-syntax/prefer-query-object-syntax.test.ts +++ b/packages/eslint-plugin-query/src/rules/prefer-query-object-syntax/prefer-query-object-syntax.test.ts @@ -28,6 +28,12 @@ ruleTester.run(name, rule, { const result = useQuery({ queryKey, queryFn, enabled }); `, }, + { + code: normalizeIndent` + import { createQuery } from "@tanstack/solid-query"; + const result = useQuery({ queryKey, queryFn, enabled }); + `, + }, { code: normalizeIndent` import { useQuery } from "somewhere-else"; @@ -40,12 +46,12 @@ ruleTester.run(name, rule, { { code: normalizeIndent` import { useQuery } from "@tanstack/react-query"; - useQuery('data'); + useQuery(['data']); `, errors: [{ messageId: 'preferObjectSyntax' }], output: normalizeIndent` import { useQuery } from "@tanstack/react-query"; - useQuery({ queryKey: 'data' }); + useQuery({ queryKey: ['data'] }); `, }, { @@ -70,12 +76,12 @@ ruleTester.run(name, rule, { { code: normalizeIndent` import { useQuery } from "@tanstack/react-query"; - useQuery('data', () => fetchData()); + useQuery(['data'], () => fetchData()); `, errors: [{ messageId: 'preferObjectSyntax' }], output: normalizeIndent` import { useQuery } from "@tanstack/react-query"; - useQuery({ queryKey: 'data', queryFn: () => fetchData() }); + useQuery({ queryKey: ['data'], queryFn: () => fetchData() }); `, }, { @@ -92,12 +98,12 @@ ruleTester.run(name, rule, { { code: normalizeIndent` import { useQuery } from "@tanstack/react-query"; - useQuery('data', () => fetchData(), { enabled: false }); + useQuery(['data'], () => fetchData(), { enabled: false }); `, errors: [{ messageId: 'preferObjectSyntax' }], output: normalizeIndent` import { useQuery } from "@tanstack/react-query"; - useQuery({ queryKey: 'data', queryFn: () => fetchData(), enabled: false }); + useQuery({ queryKey: ['data'], queryFn: () => fetchData(), enabled: false }); `, }, { @@ -114,12 +120,23 @@ ruleTester.run(name, rule, { { code: normalizeIndent` import { useQuery } from "@tanstack/react-query"; - useQuery('data', { queryFn: () => fetchData(), enabled: false }); + useQuery(['data'], { queryFn: () => fetchData(), enabled: false }); `, errors: [{ messageId: 'preferObjectSyntax' }], output: normalizeIndent` import { useQuery } from "@tanstack/react-query"; - useQuery({ queryKey: 'data', queryFn: () => fetchData(), enabled: false }); + useQuery({ queryKey: ['data'], queryFn: () => fetchData(), enabled: false }); + `, + }, + { + code: normalizeIndent` + import { createQuery } from "@tanstack/solid-query"; + createQuery(['data'], { queryFn: () => fetchData(), enabled: false }); + `, + errors: [{ messageId: 'preferObjectSyntax' }], + output: normalizeIndent` + import { createQuery } from "@tanstack/solid-query"; + createQuery({ queryKey: ['data'], queryFn: () => fetchData(), enabled: false }); `, }, ], diff --git a/packages/eslint-plugin-query/src/rules/prefer-query-object-syntax/prefer-query-object-syntax.ts b/packages/eslint-plugin-query/src/rules/prefer-query-object-syntax/prefer-query-object-syntax.ts index b3f6ada3e9..b9a5b46677 100644 --- a/packages/eslint-plugin-query/src/rules/prefer-query-object-syntax/prefer-query-object-syntax.ts +++ b/packages/eslint-plugin-query/src/rules/prefer-query-object-syntax/prefer-query-object-syntax.ts @@ -2,7 +2,7 @@ import { TSESLint } from '@typescript-eslint/utils' import { createRule } from '../../utils/create-rule' import { ASTUtils } from '../../utils/ast-utils' -const USE_QUERY = 'useQuery' +const QUERY_CALLS = ['useQuery', 'createQuery']; export const name = 'prefer-query-object-syntax' @@ -28,7 +28,7 @@ export const rule = createRule({ CallExpression(node) { const isUseQuery = node.callee.type === 'Identifier' && - node.callee.name === USE_QUERY && + QUERY_CALLS.includes(node.callee.name) && helpers.isReactQueryImport(node.callee) if (!isUseQuery) { return diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index da9021c7bf..d50414c8ad 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -152,8 +152,8 @@ importers: react: ^18.2.0 react-dom: ^18.2.0 dependencies: - '@tanstack/react-query': link:../../../packages/react-query - '@tanstack/react-query-devtools': link:../../../packages/react-query-devtools + '@tanstack/react-query': 4.13.5_biqbaboplfbrettd7655fr4n2y + '@tanstack/react-query-devtools': 4.13.7_i7kuhmcsnmccyzigx3mxdtc2mu axios: 0.21.4 isomorphic-unfetch: 3.0.0 next: 12.2.2_biqbaboplfbrettd7655fr4n2y @@ -171,8 +171,8 @@ importers: react-dom: ^18.0.0 vite: ^3.0.0 dependencies: - '@tanstack/react-query': link:../../../packages/react-query - '@tanstack/react-query-devtools': link:../../../packages/react-query-devtools + '@tanstack/react-query': 4.13.5_biqbaboplfbrettd7655fr4n2y + '@tanstack/react-query-devtools': 4.13.7_i7kuhmcsnmccyzigx3mxdtc2mu axios: 0.21.4 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 @@ -192,8 +192,8 @@ importers: react-dom: ^18.0.0 vite: ^3.0.0 dependencies: - '@tanstack/react-query': link:../../../packages/react-query - '@tanstack/react-query-devtools': link:../../../packages/react-query-devtools + '@tanstack/react-query': 4.13.5_biqbaboplfbrettd7655fr4n2y + '@tanstack/react-query-devtools': 4.13.7_i7kuhmcsnmccyzigx3mxdtc2mu graphql: 15.8.0 graphql-request: 3.7.0_graphql@15.8.0 react: 18.2.0 @@ -220,9 +220,9 @@ importers: vite: ^3.0.0 dependencies: '@tanstack/query-sync-storage-persister': link:../../../packages/query-sync-storage-persister - '@tanstack/react-query': link:../../../packages/react-query - '@tanstack/react-query-devtools': link:../../../packages/react-query-devtools - '@tanstack/react-query-persist-client': link:../../../packages/react-query-persist-client + '@tanstack/react-query': 4.13.5_biqbaboplfbrettd7655fr4n2y + '@tanstack/react-query-devtools': 4.13.7_i7kuhmcsnmccyzigx3mxdtc2mu + '@tanstack/react-query-persist-client': 4.13.5_qmjxo3qgezvgk2bxg5ctecxjii axios: 0.26.1 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 @@ -245,8 +245,8 @@ importers: react-dom: ^18.0.0 vite: ^3.0.0 dependencies: - '@tanstack/react-query': link:../../../packages/react-query - '@tanstack/react-query-devtools': link:../../../packages/react-query-devtools + '@tanstack/react-query': 4.13.5_biqbaboplfbrettd7655fr4n2y + '@tanstack/react-query-devtools': 4.13.7_i7kuhmcsnmccyzigx3mxdtc2mu axios: 0.26.1 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 @@ -265,8 +265,8 @@ importers: react-dom: ^18.2.0 react-intersection-observer: ^8.33.1 dependencies: - '@tanstack/react-query': link:../../../packages/react-query - '@tanstack/react-query-devtools': link:../../../packages/react-query-devtools + '@tanstack/react-query': 4.13.5_biqbaboplfbrettd7655fr4n2y + '@tanstack/react-query-devtools': 4.13.7_i7kuhmcsnmccyzigx3mxdtc2mu axios: 0.21.4 isomorphic-unfetch: 3.0.0 next: 12.2.2_biqbaboplfbrettd7655fr4n2y @@ -286,8 +286,8 @@ importers: resolve-from: ^5.0.0 web-streams-polyfill: ^3.0.3 dependencies: - '@tanstack/react-query': link:../../../packages/react-query - '@tanstack/react-query-devtools': link:../../../packages/react-query-devtools + '@tanstack/react-query': 4.13.5_biqbaboplfbrettd7655fr4n2y + '@tanstack/react-query-devtools': 4.13.7_i7kuhmcsnmccyzigx3mxdtc2mu ky: 0.23.0 ky-universal: 0.8.2_53xdiffegfcxt6522645rot5ue next: 12.2.2_biqbaboplfbrettd7655fr4n2y @@ -313,9 +313,9 @@ importers: dependencies: '@tanstack/query-sync-storage-persister': link:../../../packages/query-sync-storage-persister '@tanstack/react-location': 3.7.4_biqbaboplfbrettd7655fr4n2y - '@tanstack/react-query': link:../../../packages/react-query - '@tanstack/react-query-devtools': link:../../../packages/react-query-devtools - '@tanstack/react-query-persist-client': link:../../../packages/react-query-persist-client + '@tanstack/react-query': 4.13.5_biqbaboplfbrettd7655fr4n2y + '@tanstack/react-query-devtools': 4.13.7_i7kuhmcsnmccyzigx3mxdtc2mu + '@tanstack/react-query-persist-client': 4.13.5_qmjxo3qgezvgk2bxg5ctecxjii ky: 0.30.0 msw: 0.39.2 react: 18.2.0 @@ -338,8 +338,8 @@ importers: react-dom: ^18.2.0 typescript: ^4.1.2 dependencies: - '@tanstack/react-query': link:../../../packages/react-query - '@tanstack/react-query-devtools': link:../../../packages/react-query-devtools + '@tanstack/react-query': 4.13.5_biqbaboplfbrettd7655fr4n2y + '@tanstack/react-query-devtools': 4.13.7_i7kuhmcsnmccyzigx3mxdtc2mu '@types/node': 14.14.14 '@types/react': 18.0.15 axios: 0.21.4 @@ -359,8 +359,8 @@ importers: react: ^18.2.0 react-dom: ^18.2.0 dependencies: - '@tanstack/react-query': link:../../../packages/react-query - '@tanstack/react-query-devtools': link:../../../packages/react-query-devtools + '@tanstack/react-query': 4.13.5_biqbaboplfbrettd7655fr4n2y + '@tanstack/react-query-devtools': 4.13.7_i7kuhmcsnmccyzigx3mxdtc2mu axios: 0.21.4 isomorphic-unfetch: 3.0.0 next: 12.2.2_biqbaboplfbrettd7655fr4n2y @@ -376,8 +376,8 @@ importers: react-dom: ^18.0.0 vite: ^3.0.0 dependencies: - '@tanstack/react-query': link:../../../packages/react-query - '@tanstack/react-query-devtools': link:../../../packages/react-query-devtools + '@tanstack/react-query': 4.13.5_biqbaboplfbrettd7655fr4n2y + '@tanstack/react-query-devtools': 4.13.7_i7kuhmcsnmccyzigx3mxdtc2mu react: 18.2.0 react-dom: 18.2.0_react@18.2.0 devDependencies: @@ -394,8 +394,8 @@ importers: react: ^18.2.0 react-dom: ^18.2.0 dependencies: - '@tanstack/react-query': link:../../../packages/react-query - '@tanstack/react-query-devtools': link:../../../packages/react-query-devtools + '@tanstack/react-query': 4.13.5_biqbaboplfbrettd7655fr4n2y + '@tanstack/react-query-devtools': 4.13.7_i7kuhmcsnmccyzigx3mxdtc2mu axios: 0.21.4 isomorphic-unfetch: 3.0.0 next: 12.2.2_biqbaboplfbrettd7655fr4n2y @@ -435,8 +435,8 @@ importers: '@react-native-community/netinfo': 6.0.2_react-native@0.64.3 '@react-navigation/native': 6.0.11_sbjh7r6wrxe2pvsvaqturwwxna '@react-navigation/stack': 6.2.2_dpltcvsy22isyfoj2zvicex7ry - '@tanstack/react-query': link:../../../packages/react-query - '@tanstack/react-query-devtools': link:../../../packages/react-query-devtools + '@tanstack/react-query': 4.13.5_6xi2cjj4jkvnn2q2msxp65enam + '@tanstack/react-query-devtools': 4.13.7_5bzeruihn7or2pfyocm35lfvcy expo: 43.0.5_@babel+core@7.19.1 expo-constants: 12.1.3 expo-status-bar: 1.1.0 @@ -475,8 +475,8 @@ importers: sort-by: ^1.2.0 vite: ^3.0.0 dependencies: - '@tanstack/react-query': link:../../../packages/react-query - '@tanstack/react-query-devtools': link:../../../packages/react-query-devtools + '@tanstack/react-query': 4.13.5_biqbaboplfbrettd7655fr4n2y + '@tanstack/react-query-devtools': 4.13.7_i7kuhmcsnmccyzigx3mxdtc2mu localforage: 1.10.0 match-sorter: 6.3.1 react: 18.2.0 @@ -501,8 +501,8 @@ importers: vite: ^3.0.0 dependencies: '@material-ui/core': 4.12.4_biqbaboplfbrettd7655fr4n2y - '@tanstack/react-query': link:../../../packages/react-query - '@tanstack/react-query-devtools': link:../../../packages/react-query-devtools + '@tanstack/react-query': 4.13.5_biqbaboplfbrettd7655fr4n2y + '@tanstack/react-query-devtools': 4.13.7_i7kuhmcsnmccyzigx3mxdtc2mu react: 18.2.0 react-dom: 18.2.0_react@18.2.0 react-router: 5.3.3_react@18.2.0 @@ -521,8 +521,8 @@ importers: react-dom: ^18.0.0 vite: ^3.0.0 dependencies: - '@tanstack/react-query': link:../../../packages/react-query - '@tanstack/react-query-devtools': link:../../../packages/react-query-devtools + '@tanstack/react-query': 4.13.5_biqbaboplfbrettd7655fr4n2y + '@tanstack/react-query-devtools': 4.13.7_i7kuhmcsnmccyzigx3mxdtc2mu axios: 0.26.1 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 @@ -543,8 +543,8 @@ importers: vite: ^3.0.0 dependencies: '@material-ui/core': 4.12.4_biqbaboplfbrettd7655fr4n2y - '@tanstack/react-query': link:../../../packages/react-query - '@tanstack/react-query-devtools': link:../../../packages/react-query-devtools + '@tanstack/react-query': 4.13.5_biqbaboplfbrettd7655fr4n2y + '@tanstack/react-query-devtools': 4.13.7_i7kuhmcsnmccyzigx3mxdtc2mu react: 18.2.0 react-dom: 18.2.0_react@18.2.0 react-router: 5.3.3_react@18.2.0 @@ -564,8 +564,8 @@ importers: react-error-boundary: ^2.2.3 vite: ^3.0.0 dependencies: - '@tanstack/react-query': link:../../../packages/react-query - '@tanstack/react-query-devtools': link:../../../packages/react-query-devtools + '@tanstack/react-query': 4.13.5_biqbaboplfbrettd7655fr4n2y + '@tanstack/react-query-devtools': 4.13.7_i7kuhmcsnmccyzigx3mxdtc2mu axios: 0.21.4 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 @@ -625,6 +625,7 @@ importers: examples/solid/simple: specifiers: + '@tanstack/eslint-plugin-query': ^4.13.0 '@tanstack/solid-query': ^4.3.9 solid-js: ^1.5.1 typescript: ^4.8.2 @@ -634,6 +635,7 @@ importers: '@tanstack/solid-query': link:../../../packages/solid-query solid-js: 1.5.4 devDependencies: + '@tanstack/eslint-plugin-query': link:../../../packages/eslint-plugin-query typescript: 4.8.3 vite: 3.1.3 vite-plugin-solid: 2.3.9_solid-js@1.5.4+vite@3.1.3 @@ -646,7 +648,7 @@ importers: vite: 3.1.4 vue: 3.2.39 dependencies: - '@tanstack/vue-query': link:../../../packages/vue-query + '@tanstack/vue-query': 4.13.5_vue@3.2.39 vue: 3.2.39 devDependencies: '@vitejs/plugin-vue': 3.1.0_vite@3.1.4+vue@3.2.39 @@ -5485,6 +5487,16 @@ packages: remove-accents: 0.4.2 dev: false + /@tanstack/query-core/4.13.4: + resolution: {integrity: sha512-DMIy6tgGehYoRUFyoR186+pQspOicyZNSGvBWxPc2CinHjWOQ7DPnGr9zmn/kE9xK4Zd3GXd25Nj3X20+TF6Lw==} + dev: false + + /@tanstack/query-persist-client-core/4.13.4: + resolution: {integrity: sha512-K6QQXyf37Xy3sG33IkPipaQknPAK7/44rveZIYj75yrN2TJPFQxDFE6eO51Nz1/AQdupZyq+G4ggcuL3z1hRzQ==} + peerDependencies: + '@tanstack/query-core': 4.13.4 + dev: false + /@tanstack/react-location/3.7.4_biqbaboplfbrettd7655fr4n2y: resolution: {integrity: sha512-6rH2vNHGr0uyeUz5ZHvWMYjeYKGgIKFzvs5749QtnS9f+FU7t7fQE0hKZAzltBZk82LT7iYbcHBRyUg2lW13VA==} engines: {node: '>=12'} @@ -5498,6 +5510,100 @@ packages: react-dom: 18.2.0_react@18.2.0 dev: false + /@tanstack/react-query-devtools/4.13.7_5bzeruihn7or2pfyocm35lfvcy: + resolution: {integrity: sha512-F3wIpnkEphrMGZLibYded5AzNtP1TbgSPu7D4Px7qhBTv71pPHJg/kU9N29jPG6S5drX3FrmkMPhsZs0qyGIpg==} + peerDependencies: + '@tanstack/react-query': 4.13.5 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + '@tanstack/match-sorter-utils': 8.1.1 + '@tanstack/react-query': 4.13.5_6xi2cjj4jkvnn2q2msxp65enam + react: 17.0.1 + react-dom: 17.0.1_react@17.0.1 + superjson: 1.10.0 + use-sync-external-store: 1.2.0_react@17.0.1 + dev: false + + /@tanstack/react-query-devtools/4.13.7_i7kuhmcsnmccyzigx3mxdtc2mu: + resolution: {integrity: sha512-F3wIpnkEphrMGZLibYded5AzNtP1TbgSPu7D4Px7qhBTv71pPHJg/kU9N29jPG6S5drX3FrmkMPhsZs0qyGIpg==} + peerDependencies: + '@tanstack/react-query': 4.13.5 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + '@tanstack/match-sorter-utils': 8.1.1 + '@tanstack/react-query': 4.13.5_biqbaboplfbrettd7655fr4n2y + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + superjson: 1.10.0 + use-sync-external-store: 1.2.0_react@18.2.0 + dev: false + + /@tanstack/react-query-persist-client/4.13.5_qmjxo3qgezvgk2bxg5ctecxjii: + resolution: {integrity: sha512-XlUGx3nmEANi588ooCmimo9kBviAsqF7bo4C1mUxlSl3EzGfT+3m4RcH4+Ph7UvyLg/Hy1CzqzNrtdCnAZDDwg==} + peerDependencies: + '@tanstack/react-query': 4.13.5 + dependencies: + '@tanstack/query-persist-client-core': 4.13.4 + '@tanstack/react-query': 4.13.5_biqbaboplfbrettd7655fr4n2y + transitivePeerDependencies: + - '@tanstack/query-core' + dev: false + + /@tanstack/react-query/4.13.5_6xi2cjj4jkvnn2q2msxp65enam: + resolution: {integrity: sha512-p2HcWGuqfvG7Pz04un4phWZeXzlITD7Ue0gMXjD56g8y3rP1r5qEYC/BckffrZLf4dZtQeVPCWOa8RYAqx036g==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-native: '*' + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + dependencies: + '@tanstack/query-core': 4.13.4 + react: 17.0.1 + react-dom: 17.0.1_react@17.0.1 + react-native: 0.64.3_gpe6tsd6gj6gjuc7hywxyjd2qm + use-sync-external-store: 1.2.0_react@17.0.1 + dev: false + + /@tanstack/react-query/4.13.5_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-p2HcWGuqfvG7Pz04un4phWZeXzlITD7Ue0gMXjD56g8y3rP1r5qEYC/BckffrZLf4dZtQeVPCWOa8RYAqx036g==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-native: '*' + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + dependencies: + '@tanstack/query-core': 4.13.4 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + use-sync-external-store: 1.2.0_react@18.2.0 + dev: false + + /@tanstack/vue-query/4.13.5_vue@3.2.39: + resolution: {integrity: sha512-ev7ATkyWG3UTkBif2m92BDe8N6Jv6nBifCTUfJlnkstPjRtEALBZ52YI3vxEhVYY5+emrN+UdXy2cCg9lAfqMQ==} + peerDependencies: + '@vue/composition-api': ^1.1.2 + vue: ^2.5.0 || ^3.0.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + dependencies: + '@tanstack/match-sorter-utils': 8.1.1 + '@tanstack/query-core': 4.13.4 + '@vue/devtools-api': 6.4.2 + vue: 3.2.39 + vue-demi: 0.13.11_vue@3.2.39 + dev: false + /@testing-library/dom/7.31.2: resolution: {integrity: sha512-3UqjCpey6HiTZT92vODYLPxTBWlM8ZOOjr3LX5F37/VRipW2M1kX6I/Cm4VXzteZqfGfagg8yXywpcOgQBlNsQ==} engines: {node: '>=10'} @@ -15350,6 +15456,14 @@ packages: react: 18.2.0 dev: false + /use-sync-external-store/1.2.0_react@17.0.1: + resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + react: 17.0.1 + dev: false + /use-sync-external-store/1.2.0_react@18.2.0: resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} peerDependencies: @@ -15501,6 +15615,21 @@ packages: resolution: {integrity: sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==} dev: false + /vue-demi/0.13.11_vue@3.2.39: + resolution: {integrity: sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + peerDependencies: + '@vue/composition-api': ^1.0.0-rc.1 + vue: ^3.0.0-0 || ^2.6.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + dependencies: + vue: 3.2.39 + dev: false + /vue-demi/0.13.11_zv57lmwz3lpne326jxcwg2uc6q: resolution: {integrity: sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==} engines: {node: '>=12'} From 253ced7cf2afc1ca23f730399e7d07dd8b73d426 Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Tue, 1 Nov 2022 10:41:04 +0100 Subject: [PATCH 33/40] docs: add eslint plugin section --- docs/config.json | 17 ++++++ docs/eslint/eslint-plugin-query.md | 49 ++++++++++++++++ docs/eslint/exhaustive-deps.md | 47 +++++++++++++++ docs/eslint/prefer-query-object-syntax.md | 58 +++++++++++++++++++ examples/react/basic-typescript/.eslintrc | 6 +- .../docs/rules/exhaustive-deps.md | 2 + .../docs/rules/prefer-query-object-syntax.md | 2 + 7 files changed, 176 insertions(+), 5 deletions(-) create mode 100644 docs/eslint/eslint-plugin-query.md create mode 100644 docs/eslint/exhaustive-deps.md create mode 100644 docs/eslint/prefer-query-object-syntax.md diff --git a/docs/config.json b/docs/config.json index d8fe842806..b1e33bb91b 100644 --- a/docs/config.json +++ b/docs/config.json @@ -298,6 +298,23 @@ } ] }, + { + "label": "Eslint", + "children": [ + { + "label": "Eslint Plugin Query", + "to": "eslint/eslint-plugin-query" + }, + { + "label": "Exhaustive Deps", + "to": "eslint/exhaustive-deps" + }, + { + "label": "prefer-query-object-syntax", + "to": "eslint/prefer-query-object-syntax" + } + ] + }, { "label": "Plugins", "children": [ diff --git a/docs/eslint/eslint-plugin-query.md b/docs/eslint/eslint-plugin-query.md new file mode 100644 index 0000000000..0900eaefe1 --- /dev/null +++ b/docs/eslint/eslint-plugin-query.md @@ -0,0 +1,49 @@ +--- +id: eslint-plugin-query +title: Eslint Plugin Query +--- + +TanStack Query comes with its own EsLint plugin. This plugin is used to enforce best practices and to help you avoid common mistakes. + +## Installation + +The plugin is a separate package that you need to install: + +```bash +$ npm i @tanstack/eslint-plugin-query +# or +$ pnpm add @tanstack/eslint-plugin-query +# or +$ yarn add @tanstack/eslint-plugin-query +``` + +## Usage + +Add `@tanstack/eslint-plugin-query` to the plugins section of your `.eslintrc` configuration file: + +```json +{ + "plugins": ["@tanstack/query"] +} +``` + +Then configure the rules you want to use under the rules section: + +```json +{ + "rules": { + "@tanstack/query/exhaustive-deps": "error", + "@tanstack/query/prefer-query-object-syntax": "error" + } +} +``` + +### Recommended config + +You can also enable all the recommended rules for our plugin. Add `plugin:@tanstack/eslint-plugin-query/recommended` in extends: + +```json +{ + "extends": ["plugin:@tanstack/eslint-plugin-query/recommended"] +} +``` diff --git a/docs/eslint/exhaustive-deps.md b/docs/eslint/exhaustive-deps.md new file mode 100644 index 0000000000..e3719818bc --- /dev/null +++ b/docs/eslint/exhaustive-deps.md @@ -0,0 +1,47 @@ +--- +id: exhaustive-deps +title: Exhaustive dependencies for query keys +--- + +Query keys should be seen like a dependency array to your query function: Every variable that is used inside the queryFn should be added to the query key. +This makes sure that queries are cached independently and that queries are refetched automatically when the variables changes. + +## Rule Details + +Examples of **incorrect** code for this rule: + +```ts +/* eslint "@tanstack/query/exhaustive-deps": "error" */ + +useQuery({ + queryKey: ["todo"], + queryFn: () => api.getTodo(todoId) +}) + +const todoQueries = { + detail: (id) => ({ queryKey: ["todo"], queryFn: () => api.getTodo(id) }) +} +``` + + +Examples of **correct** code for this rule: + +```ts +useQuery({ + queryKey: ["todo", todoId], + queryFn: () => api.getTodo(todoId) +}) + +const todoQueries = { + detail: (id) => ({ queryKey: ["todo", id], queryFn: () => api.getTodo(id) }) +} +``` + +## When Not To Use It + +If you don't care about the rules of the query keys, then you will not need this rule. + +## Attributes + +- [x] ✅ Recommended +- [x] 🔧 Fixable diff --git a/docs/eslint/prefer-query-object-syntax.md b/docs/eslint/prefer-query-object-syntax.md new file mode 100644 index 0000000000..c14018c44d --- /dev/null +++ b/docs/eslint/prefer-query-object-syntax.md @@ -0,0 +1,58 @@ +--- +id: prefer-query-object-syntax +title: Prefer object syntax for useQuery +--- + +You can use [`useQuery`](https://tanstack.com/query/v4/docs/reference/useQuery) in two different ways. + +Standard + +```ts +useQuery(queryKey, queryFn?, options?) + +// or + +useQuery(options) +``` + +This rule prefers the second option, as it is more consistent with other React Query hooks, like `useQueries`. It will also be the only available option in a future major version. + +## Rule Details + +Examples of **incorrect** code for this rule: + +```js +/* eslint "@tanstack/query/prefer-query-object-syntax": "error" */ + +import { useQuery } from '@tanstack/react-query'; + +useQuery(queryKey, queryFn, { + onSuccess, +}); + +useQuery(queryKey, { + queryFn, + onSuccess, +}); +``` + +Examples of **correct** code for this rule: + +```js +import { useQuery } from '@tanstack/react-query'; + +useQuery({ + queryKey, + queryFn, + onSuccess, +}); +``` + +## When Not To Use It + +If you don't care about useQuery consistency, then you will not need this rule. + +## Attributes + +- [x] ✅ Recommended +- [x] 🔧 Fixable diff --git a/examples/react/basic-typescript/.eslintrc b/examples/react/basic-typescript/.eslintrc index 404725ad66..1699a80e89 100644 --- a/examples/react/basic-typescript/.eslintrc +++ b/examples/react/basic-typescript/.eslintrc @@ -1,7 +1,3 @@ { - "extends": ["react-app", "prettier"], - "rules": { - // "eqeqeq": 0, - // "jsx-a11y/anchor-is-valid": 0 - } + "extends": ["react-app", "prettier", "plugin:@tanstack/eslint-plugin-query/recommended"] } diff --git a/packages/eslint-plugin-query/docs/rules/exhaustive-deps.md b/packages/eslint-plugin-query/docs/rules/exhaustive-deps.md index 32640c7b5b..898c3aa592 100644 --- a/packages/eslint-plugin-query/docs/rules/exhaustive-deps.md +++ b/packages/eslint-plugin-query/docs/rules/exhaustive-deps.md @@ -8,6 +8,8 @@ This makes sure that queries are cached independently and that queries are refet Examples of **incorrect** code for this rule: ```ts +/* eslint "@tanstack/query/exhaustive-deps": "error" */ + useQuery({ queryKey: ["todo"], queryFn: () => api.getTodo(todoId) diff --git a/packages/eslint-plugin-query/docs/rules/prefer-query-object-syntax.md b/packages/eslint-plugin-query/docs/rules/prefer-query-object-syntax.md index dcdfaa270d..2f8771466b 100644 --- a/packages/eslint-plugin-query/docs/rules/prefer-query-object-syntax.md +++ b/packages/eslint-plugin-query/docs/rules/prefer-query-object-syntax.md @@ -19,6 +19,8 @@ This rule prefers the second option, as it is more consistent with other React Q Examples of **incorrect** code for this rule: ```js +/* eslint "@tanstack/query/prefer-query-object-syntax": "error" */ + import { useQuery } from '@tanstack/react-query'; useQuery(queryKey, queryFn, { From 47192a7b566aa2d77f8d25988e3aecbc5ed22a7a Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Tue, 1 Nov 2022 10:47:13 +0100 Subject: [PATCH 34/40] prettier --- .../prefer-query-object-syntax/prefer-query-object-syntax.ts | 2 +- .../src/utils/detect-react-query-imports.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin-query/src/rules/prefer-query-object-syntax/prefer-query-object-syntax.ts b/packages/eslint-plugin-query/src/rules/prefer-query-object-syntax/prefer-query-object-syntax.ts index 65f192fd97..3d4b1d0001 100644 --- a/packages/eslint-plugin-query/src/rules/prefer-query-object-syntax/prefer-query-object-syntax.ts +++ b/packages/eslint-plugin-query/src/rules/prefer-query-object-syntax/prefer-query-object-syntax.ts @@ -2,7 +2,7 @@ import type { TSESLint } from '@typescript-eslint/utils' import { createRule } from '../../utils/create-rule' import { ASTUtils } from '../../utils/ast-utils' -const QUERY_CALLS = ['useQuery', 'createQuery']; +const QUERY_CALLS = ['useQuery', 'createQuery'] export const name = 'prefer-query-object-syntax' diff --git a/packages/eslint-plugin-query/src/utils/detect-react-query-imports.ts b/packages/eslint-plugin-query/src/utils/detect-react-query-imports.ts index 0725244fec..a536871508 100644 --- a/packages/eslint-plugin-query/src/utils/detect-react-query-imports.ts +++ b/packages/eslint-plugin-query/src/utils/detect-react-query-imports.ts @@ -35,8 +35,8 @@ export function detectReactQueryImports(create: EnhancedCreate): Create { if ( node.specifiers.length > 0 && node.importKind === 'value' && - node.source.value.startsWith("@tanstack/") && - node.source.value.endsWith("-query") + node.source.value.startsWith('@tanstack/') && + node.source.value.endsWith('-query') ) { reactQueryImportSpecifiers.push(...node.specifiers) } From 176d662098d8d332c0af73792999eea5e6d4ab5e Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Tue, 1 Nov 2022 11:02:59 +0100 Subject: [PATCH 35/40] move docs --- .../docs/rules/exhaustive-deps.md | 44 --------------- .../docs/rules/prefer-query-object-syntax.md | 55 ------------------- 2 files changed, 99 deletions(-) delete mode 100644 packages/eslint-plugin-query/docs/rules/exhaustive-deps.md delete mode 100644 packages/eslint-plugin-query/docs/rules/prefer-query-object-syntax.md diff --git a/packages/eslint-plugin-query/docs/rules/exhaustive-deps.md b/packages/eslint-plugin-query/docs/rules/exhaustive-deps.md deleted file mode 100644 index 898c3aa592..0000000000 --- a/packages/eslint-plugin-query/docs/rules/exhaustive-deps.md +++ /dev/null @@ -1,44 +0,0 @@ -# Exhaustive dependencies for `queryKey` (`@tanstack/query/exhaustive-deps`) - -Query keys should be seen like a dependency array to your query function: Every variable that is used inside the queryFn should be added to the query key. -This makes sure that queries are cached independently and that queries are refetched automatically when the variables changes. - -## Rule Details - -Examples of **incorrect** code for this rule: - -```ts -/* eslint "@tanstack/query/exhaustive-deps": "error" */ - -useQuery({ - queryKey: ["todo"], - queryFn: () => api.getTodo(todoId) -}) - -const todoQueries = { - detail: (id) => ({ queryKey: ["todo"], queryFn: () => api.getTodo(id) }) -} -``` - - -Examples of **correct** code for this rule: - -```ts -useQuery({ - queryKey: ["todo", todoId], - queryFn: () => api.getTodo(todoId) -}) - -const todoQueries = { - detail: (id) => ({ queryKey: ["todo", id], queryFn: () => api.getTodo(id) }) -} -``` - -## When Not To Use It - -If you don't care about the rules of the query keys, then you will not need this rule. - -## Attributes - -- [x] ✅ Recommended -- [x] 🔧 Fixable diff --git a/packages/eslint-plugin-query/docs/rules/prefer-query-object-syntax.md b/packages/eslint-plugin-query/docs/rules/prefer-query-object-syntax.md deleted file mode 100644 index 2f8771466b..0000000000 --- a/packages/eslint-plugin-query/docs/rules/prefer-query-object-syntax.md +++ /dev/null @@ -1,55 +0,0 @@ -# Prefer object syntax for useQuery (`@tanstack/query/prefer-query-object-syntax`) - -You can use [`useQuery`](https://tanstack.com/query/v4/docs/reference/useQuery) in two different ways. - -Standard - -```ts -useQuery(queryKey, queryFn?, options?) - -// or - -useQuery(options) -``` - -This rule prefers the second option, as it is more consistent with other React Query hooks, like `useQueries`. It will also be the only available option in a future major version. - -## Rule Details - -Examples of **incorrect** code for this rule: - -```js -/* eslint "@tanstack/query/prefer-query-object-syntax": "error" */ - -import { useQuery } from '@tanstack/react-query'; - -useQuery(queryKey, queryFn, { - onSuccess, -}); - -useQuery(queryKey, { - queryFn, - onSuccess, -}); -``` - -Examples of **correct** code for this rule: - -```js -import { useQuery } from '@tanstack/react-query'; - -useQuery({ - queryKey, - queryFn, - onSuccess, -}); -``` - -## When Not To Use It - -If you don't care about useQuery consistency, then you will not need this rule. - -## Attributes - -- [x] ✅ Recommended -- [x] 🔧 Fixable From 348f4614a3e7e58ce976f5726134fc47faf19b62 Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Tue, 1 Nov 2022 11:04:57 +0100 Subject: [PATCH 36/40] EsLint -> ESLint --- docs/config.json | 4 ++-- docs/eslint/eslint-plugin-query.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/config.json b/docs/config.json index b1e33bb91b..fb3b3cc5e8 100644 --- a/docs/config.json +++ b/docs/config.json @@ -299,10 +299,10 @@ ] }, { - "label": "Eslint", + "label": "ESLint", "children": [ { - "label": "Eslint Plugin Query", + "label": "ESLint Plugin Query", "to": "eslint/eslint-plugin-query" }, { diff --git a/docs/eslint/eslint-plugin-query.md b/docs/eslint/eslint-plugin-query.md index 0900eaefe1..ee632166d8 100644 --- a/docs/eslint/eslint-plugin-query.md +++ b/docs/eslint/eslint-plugin-query.md @@ -1,9 +1,9 @@ --- id: eslint-plugin-query -title: Eslint Plugin Query +title: ESLint Plugin Query --- -TanStack Query comes with its own EsLint plugin. This plugin is used to enforce best practices and to help you avoid common mistakes. +TanStack Query comes with its own ESLint plugin. This plugin is used to enforce best practices and to help you avoid common mistakes. ## Installation From 58bb6477750971b6f39c246de26cdf65d1a52e26 Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Tue, 1 Nov 2022 11:06:22 +0100 Subject: [PATCH 37/40] fix typo --- packages/eslint-plugin-query/src/rules/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin-query/src/rules/index.ts b/packages/eslint-plugin-query/src/rules/index.ts index 84a9708fa6..bba8422ffb 100644 --- a/packages/eslint-plugin-query/src/rules/index.ts +++ b/packages/eslint-plugin-query/src/rules/index.ts @@ -1,7 +1,7 @@ -import * as exaustiveDeps from './exhaustive-deps/exhaustive-deps.rule' +import * as exhaustiveDeps from './exhaustive-deps/exhaustive-deps.rule' import * as preferObjectSyntax from './prefer-query-object-syntax/prefer-query-object-syntax' export const rules = { - [exaustiveDeps.name]: exaustiveDeps.rule, + [exhaustiveDeps.name]: exhaustiveDeps.rule, [preferObjectSyntax.name]: preferObjectSyntax.rule, } From 7f961d26c25d7c614c6dde2e318f68711150f218 Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Tue, 1 Nov 2022 11:14:08 +0100 Subject: [PATCH 38/40] add missing dep --- examples/react/basic-typescript/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/react/basic-typescript/package.json b/examples/react/basic-typescript/package.json index 9de915c4cb..d48c11c2bd 100644 --- a/examples/react/basic-typescript/package.json +++ b/examples/react/basic-typescript/package.json @@ -17,6 +17,7 @@ "@tanstack/query-sync-storage-persister": "^4.7.1" }, "devDependencies": { + "@tanstack/eslint-plugin-query": "^4.13.0", "@types/react": "^17.0.3", "@types/react-dom": "^17.0.3", "@vitejs/plugin-react": "^2.0.0", From 3551295cfac10463a17ca6560a8bb4fa4f7a93d9 Mon Sep 17 00:00:00 2001 From: Newbie012 Date: Tue, 1 Nov 2022 12:30:11 +0200 Subject: [PATCH 39/40] update lockfile --- pnpm-lock.yaml | 207 ++++++++++--------------------------------------- 1 file changed, 41 insertions(+), 166 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d50414c8ad..22ba207979 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -152,8 +152,8 @@ importers: react: ^18.2.0 react-dom: ^18.2.0 dependencies: - '@tanstack/react-query': 4.13.5_biqbaboplfbrettd7655fr4n2y - '@tanstack/react-query-devtools': 4.13.7_i7kuhmcsnmccyzigx3mxdtc2mu + '@tanstack/react-query': link:../../../packages/react-query + '@tanstack/react-query-devtools': link:../../../packages/react-query-devtools axios: 0.21.4 isomorphic-unfetch: 3.0.0 next: 12.2.2_biqbaboplfbrettd7655fr4n2y @@ -171,8 +171,8 @@ importers: react-dom: ^18.0.0 vite: ^3.0.0 dependencies: - '@tanstack/react-query': 4.13.5_biqbaboplfbrettd7655fr4n2y - '@tanstack/react-query-devtools': 4.13.7_i7kuhmcsnmccyzigx3mxdtc2mu + '@tanstack/react-query': link:../../../packages/react-query + '@tanstack/react-query-devtools': link:../../../packages/react-query-devtools axios: 0.21.4 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 @@ -192,8 +192,8 @@ importers: react-dom: ^18.0.0 vite: ^3.0.0 dependencies: - '@tanstack/react-query': 4.13.5_biqbaboplfbrettd7655fr4n2y - '@tanstack/react-query-devtools': 4.13.7_i7kuhmcsnmccyzigx3mxdtc2mu + '@tanstack/react-query': link:../../../packages/react-query + '@tanstack/react-query-devtools': link:../../../packages/react-query-devtools graphql: 15.8.0 graphql-request: 3.7.0_graphql@15.8.0 react: 18.2.0 @@ -204,6 +204,7 @@ importers: examples/react/basic-typescript: specifiers: + '@tanstack/eslint-plugin-query': ^4.13.0 '@tanstack/query-sync-storage-persister': ^4.7.1 '@tanstack/react-query': ^4.7.1 '@tanstack/react-query-devtools': ^4.7.1 @@ -220,13 +221,14 @@ importers: vite: ^3.0.0 dependencies: '@tanstack/query-sync-storage-persister': link:../../../packages/query-sync-storage-persister - '@tanstack/react-query': 4.13.5_biqbaboplfbrettd7655fr4n2y - '@tanstack/react-query-devtools': 4.13.7_i7kuhmcsnmccyzigx3mxdtc2mu - '@tanstack/react-query-persist-client': 4.13.5_qmjxo3qgezvgk2bxg5ctecxjii + '@tanstack/react-query': link:../../../packages/react-query + '@tanstack/react-query-devtools': link:../../../packages/react-query-devtools + '@tanstack/react-query-persist-client': link:../../../packages/react-query-persist-client axios: 0.26.1 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 devDependencies: + '@tanstack/eslint-plugin-query': link:../../../packages/eslint-plugin-query '@types/react': 17.0.50 '@types/react-dom': 17.0.17 '@vitejs/plugin-react': 2.1.0_vite@3.1.4 @@ -245,8 +247,8 @@ importers: react-dom: ^18.0.0 vite: ^3.0.0 dependencies: - '@tanstack/react-query': 4.13.5_biqbaboplfbrettd7655fr4n2y - '@tanstack/react-query-devtools': 4.13.7_i7kuhmcsnmccyzigx3mxdtc2mu + '@tanstack/react-query': link:../../../packages/react-query + '@tanstack/react-query-devtools': link:../../../packages/react-query-devtools axios: 0.26.1 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 @@ -265,8 +267,8 @@ importers: react-dom: ^18.2.0 react-intersection-observer: ^8.33.1 dependencies: - '@tanstack/react-query': 4.13.5_biqbaboplfbrettd7655fr4n2y - '@tanstack/react-query-devtools': 4.13.7_i7kuhmcsnmccyzigx3mxdtc2mu + '@tanstack/react-query': link:../../../packages/react-query + '@tanstack/react-query-devtools': link:../../../packages/react-query-devtools axios: 0.21.4 isomorphic-unfetch: 3.0.0 next: 12.2.2_biqbaboplfbrettd7655fr4n2y @@ -286,8 +288,8 @@ importers: resolve-from: ^5.0.0 web-streams-polyfill: ^3.0.3 dependencies: - '@tanstack/react-query': 4.13.5_biqbaboplfbrettd7655fr4n2y - '@tanstack/react-query-devtools': 4.13.7_i7kuhmcsnmccyzigx3mxdtc2mu + '@tanstack/react-query': link:../../../packages/react-query + '@tanstack/react-query-devtools': link:../../../packages/react-query-devtools ky: 0.23.0 ky-universal: 0.8.2_53xdiffegfcxt6522645rot5ue next: 12.2.2_biqbaboplfbrettd7655fr4n2y @@ -313,9 +315,9 @@ importers: dependencies: '@tanstack/query-sync-storage-persister': link:../../../packages/query-sync-storage-persister '@tanstack/react-location': 3.7.4_biqbaboplfbrettd7655fr4n2y - '@tanstack/react-query': 4.13.5_biqbaboplfbrettd7655fr4n2y - '@tanstack/react-query-devtools': 4.13.7_i7kuhmcsnmccyzigx3mxdtc2mu - '@tanstack/react-query-persist-client': 4.13.5_qmjxo3qgezvgk2bxg5ctecxjii + '@tanstack/react-query': link:../../../packages/react-query + '@tanstack/react-query-devtools': link:../../../packages/react-query-devtools + '@tanstack/react-query-persist-client': link:../../../packages/react-query-persist-client ky: 0.30.0 msw: 0.39.2 react: 18.2.0 @@ -338,8 +340,8 @@ importers: react-dom: ^18.2.0 typescript: ^4.1.2 dependencies: - '@tanstack/react-query': 4.13.5_biqbaboplfbrettd7655fr4n2y - '@tanstack/react-query-devtools': 4.13.7_i7kuhmcsnmccyzigx3mxdtc2mu + '@tanstack/react-query': link:../../../packages/react-query + '@tanstack/react-query-devtools': link:../../../packages/react-query-devtools '@types/node': 14.14.14 '@types/react': 18.0.15 axios: 0.21.4 @@ -359,8 +361,8 @@ importers: react: ^18.2.0 react-dom: ^18.2.0 dependencies: - '@tanstack/react-query': 4.13.5_biqbaboplfbrettd7655fr4n2y - '@tanstack/react-query-devtools': 4.13.7_i7kuhmcsnmccyzigx3mxdtc2mu + '@tanstack/react-query': link:../../../packages/react-query + '@tanstack/react-query-devtools': link:../../../packages/react-query-devtools axios: 0.21.4 isomorphic-unfetch: 3.0.0 next: 12.2.2_biqbaboplfbrettd7655fr4n2y @@ -376,8 +378,8 @@ importers: react-dom: ^18.0.0 vite: ^3.0.0 dependencies: - '@tanstack/react-query': 4.13.5_biqbaboplfbrettd7655fr4n2y - '@tanstack/react-query-devtools': 4.13.7_i7kuhmcsnmccyzigx3mxdtc2mu + '@tanstack/react-query': link:../../../packages/react-query + '@tanstack/react-query-devtools': link:../../../packages/react-query-devtools react: 18.2.0 react-dom: 18.2.0_react@18.2.0 devDependencies: @@ -394,8 +396,8 @@ importers: react: ^18.2.0 react-dom: ^18.2.0 dependencies: - '@tanstack/react-query': 4.13.5_biqbaboplfbrettd7655fr4n2y - '@tanstack/react-query-devtools': 4.13.7_i7kuhmcsnmccyzigx3mxdtc2mu + '@tanstack/react-query': link:../../../packages/react-query + '@tanstack/react-query-devtools': link:../../../packages/react-query-devtools axios: 0.21.4 isomorphic-unfetch: 3.0.0 next: 12.2.2_biqbaboplfbrettd7655fr4n2y @@ -435,8 +437,8 @@ importers: '@react-native-community/netinfo': 6.0.2_react-native@0.64.3 '@react-navigation/native': 6.0.11_sbjh7r6wrxe2pvsvaqturwwxna '@react-navigation/stack': 6.2.2_dpltcvsy22isyfoj2zvicex7ry - '@tanstack/react-query': 4.13.5_6xi2cjj4jkvnn2q2msxp65enam - '@tanstack/react-query-devtools': 4.13.7_5bzeruihn7or2pfyocm35lfvcy + '@tanstack/react-query': link:../../../packages/react-query + '@tanstack/react-query-devtools': link:../../../packages/react-query-devtools expo: 43.0.5_@babel+core@7.19.1 expo-constants: 12.1.3 expo-status-bar: 1.1.0 @@ -475,8 +477,8 @@ importers: sort-by: ^1.2.0 vite: ^3.0.0 dependencies: - '@tanstack/react-query': 4.13.5_biqbaboplfbrettd7655fr4n2y - '@tanstack/react-query-devtools': 4.13.7_i7kuhmcsnmccyzigx3mxdtc2mu + '@tanstack/react-query': link:../../../packages/react-query + '@tanstack/react-query-devtools': link:../../../packages/react-query-devtools localforage: 1.10.0 match-sorter: 6.3.1 react: 18.2.0 @@ -501,8 +503,8 @@ importers: vite: ^3.0.0 dependencies: '@material-ui/core': 4.12.4_biqbaboplfbrettd7655fr4n2y - '@tanstack/react-query': 4.13.5_biqbaboplfbrettd7655fr4n2y - '@tanstack/react-query-devtools': 4.13.7_i7kuhmcsnmccyzigx3mxdtc2mu + '@tanstack/react-query': link:../../../packages/react-query + '@tanstack/react-query-devtools': link:../../../packages/react-query-devtools react: 18.2.0 react-dom: 18.2.0_react@18.2.0 react-router: 5.3.3_react@18.2.0 @@ -521,8 +523,8 @@ importers: react-dom: ^18.0.0 vite: ^3.0.0 dependencies: - '@tanstack/react-query': 4.13.5_biqbaboplfbrettd7655fr4n2y - '@tanstack/react-query-devtools': 4.13.7_i7kuhmcsnmccyzigx3mxdtc2mu + '@tanstack/react-query': link:../../../packages/react-query + '@tanstack/react-query-devtools': link:../../../packages/react-query-devtools axios: 0.26.1 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 @@ -543,8 +545,8 @@ importers: vite: ^3.0.0 dependencies: '@material-ui/core': 4.12.4_biqbaboplfbrettd7655fr4n2y - '@tanstack/react-query': 4.13.5_biqbaboplfbrettd7655fr4n2y - '@tanstack/react-query-devtools': 4.13.7_i7kuhmcsnmccyzigx3mxdtc2mu + '@tanstack/react-query': link:../../../packages/react-query + '@tanstack/react-query-devtools': link:../../../packages/react-query-devtools react: 18.2.0 react-dom: 18.2.0_react@18.2.0 react-router: 5.3.3_react@18.2.0 @@ -564,8 +566,8 @@ importers: react-error-boundary: ^2.2.3 vite: ^3.0.0 dependencies: - '@tanstack/react-query': 4.13.5_biqbaboplfbrettd7655fr4n2y - '@tanstack/react-query-devtools': 4.13.7_i7kuhmcsnmccyzigx3mxdtc2mu + '@tanstack/react-query': link:../../../packages/react-query + '@tanstack/react-query-devtools': link:../../../packages/react-query-devtools axios: 0.21.4 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 @@ -648,7 +650,7 @@ importers: vite: 3.1.4 vue: 3.2.39 dependencies: - '@tanstack/vue-query': 4.13.5_vue@3.2.39 + '@tanstack/vue-query': link:../../../packages/vue-query vue: 3.2.39 devDependencies: '@vitejs/plugin-vue': 3.1.0_vite@3.1.4+vue@3.2.39 @@ -5487,16 +5489,6 @@ packages: remove-accents: 0.4.2 dev: false - /@tanstack/query-core/4.13.4: - resolution: {integrity: sha512-DMIy6tgGehYoRUFyoR186+pQspOicyZNSGvBWxPc2CinHjWOQ7DPnGr9zmn/kE9xK4Zd3GXd25Nj3X20+TF6Lw==} - dev: false - - /@tanstack/query-persist-client-core/4.13.4: - resolution: {integrity: sha512-K6QQXyf37Xy3sG33IkPipaQknPAK7/44rveZIYj75yrN2TJPFQxDFE6eO51Nz1/AQdupZyq+G4ggcuL3z1hRzQ==} - peerDependencies: - '@tanstack/query-core': 4.13.4 - dev: false - /@tanstack/react-location/3.7.4_biqbaboplfbrettd7655fr4n2y: resolution: {integrity: sha512-6rH2vNHGr0uyeUz5ZHvWMYjeYKGgIKFzvs5749QtnS9f+FU7t7fQE0hKZAzltBZk82LT7iYbcHBRyUg2lW13VA==} engines: {node: '>=12'} @@ -5510,100 +5502,6 @@ packages: react-dom: 18.2.0_react@18.2.0 dev: false - /@tanstack/react-query-devtools/4.13.7_5bzeruihn7or2pfyocm35lfvcy: - resolution: {integrity: sha512-F3wIpnkEphrMGZLibYded5AzNtP1TbgSPu7D4Px7qhBTv71pPHJg/kU9N29jPG6S5drX3FrmkMPhsZs0qyGIpg==} - peerDependencies: - '@tanstack/react-query': 4.13.5 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - dependencies: - '@tanstack/match-sorter-utils': 8.1.1 - '@tanstack/react-query': 4.13.5_6xi2cjj4jkvnn2q2msxp65enam - react: 17.0.1 - react-dom: 17.0.1_react@17.0.1 - superjson: 1.10.0 - use-sync-external-store: 1.2.0_react@17.0.1 - dev: false - - /@tanstack/react-query-devtools/4.13.7_i7kuhmcsnmccyzigx3mxdtc2mu: - resolution: {integrity: sha512-F3wIpnkEphrMGZLibYded5AzNtP1TbgSPu7D4Px7qhBTv71pPHJg/kU9N29jPG6S5drX3FrmkMPhsZs0qyGIpg==} - peerDependencies: - '@tanstack/react-query': 4.13.5 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - dependencies: - '@tanstack/match-sorter-utils': 8.1.1 - '@tanstack/react-query': 4.13.5_biqbaboplfbrettd7655fr4n2y - react: 18.2.0 - react-dom: 18.2.0_react@18.2.0 - superjson: 1.10.0 - use-sync-external-store: 1.2.0_react@18.2.0 - dev: false - - /@tanstack/react-query-persist-client/4.13.5_qmjxo3qgezvgk2bxg5ctecxjii: - resolution: {integrity: sha512-XlUGx3nmEANi588ooCmimo9kBviAsqF7bo4C1mUxlSl3EzGfT+3m4RcH4+Ph7UvyLg/Hy1CzqzNrtdCnAZDDwg==} - peerDependencies: - '@tanstack/react-query': 4.13.5 - dependencies: - '@tanstack/query-persist-client-core': 4.13.4 - '@tanstack/react-query': 4.13.5_biqbaboplfbrettd7655fr4n2y - transitivePeerDependencies: - - '@tanstack/query-core' - dev: false - - /@tanstack/react-query/4.13.5_6xi2cjj4jkvnn2q2msxp65enam: - resolution: {integrity: sha512-p2HcWGuqfvG7Pz04un4phWZeXzlITD7Ue0gMXjD56g8y3rP1r5qEYC/BckffrZLf4dZtQeVPCWOa8RYAqx036g==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-native: '*' - peerDependenciesMeta: - react-dom: - optional: true - react-native: - optional: true - dependencies: - '@tanstack/query-core': 4.13.4 - react: 17.0.1 - react-dom: 17.0.1_react@17.0.1 - react-native: 0.64.3_gpe6tsd6gj6gjuc7hywxyjd2qm - use-sync-external-store: 1.2.0_react@17.0.1 - dev: false - - /@tanstack/react-query/4.13.5_biqbaboplfbrettd7655fr4n2y: - resolution: {integrity: sha512-p2HcWGuqfvG7Pz04un4phWZeXzlITD7Ue0gMXjD56g8y3rP1r5qEYC/BckffrZLf4dZtQeVPCWOa8RYAqx036g==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-native: '*' - peerDependenciesMeta: - react-dom: - optional: true - react-native: - optional: true - dependencies: - '@tanstack/query-core': 4.13.4 - react: 18.2.0 - react-dom: 18.2.0_react@18.2.0 - use-sync-external-store: 1.2.0_react@18.2.0 - dev: false - - /@tanstack/vue-query/4.13.5_vue@3.2.39: - resolution: {integrity: sha512-ev7ATkyWG3UTkBif2m92BDe8N6Jv6nBifCTUfJlnkstPjRtEALBZ52YI3vxEhVYY5+emrN+UdXy2cCg9lAfqMQ==} - peerDependencies: - '@vue/composition-api': ^1.1.2 - vue: ^2.5.0 || ^3.0.0 - peerDependenciesMeta: - '@vue/composition-api': - optional: true - dependencies: - '@tanstack/match-sorter-utils': 8.1.1 - '@tanstack/query-core': 4.13.4 - '@vue/devtools-api': 6.4.2 - vue: 3.2.39 - vue-demi: 0.13.11_vue@3.2.39 - dev: false - /@testing-library/dom/7.31.2: resolution: {integrity: sha512-3UqjCpey6HiTZT92vODYLPxTBWlM8ZOOjr3LX5F37/VRipW2M1kX6I/Cm4VXzteZqfGfagg8yXywpcOgQBlNsQ==} engines: {node: '>=10'} @@ -15456,14 +15354,6 @@ packages: react: 18.2.0 dev: false - /use-sync-external-store/1.2.0_react@17.0.1: - resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - dependencies: - react: 17.0.1 - dev: false - /use-sync-external-store/1.2.0_react@18.2.0: resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} peerDependencies: @@ -15615,21 +15505,6 @@ packages: resolution: {integrity: sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==} dev: false - /vue-demi/0.13.11_vue@3.2.39: - resolution: {integrity: sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==} - engines: {node: '>=12'} - hasBin: true - requiresBuild: true - peerDependencies: - '@vue/composition-api': ^1.0.0-rc.1 - vue: ^3.0.0-0 || ^2.6.0 - peerDependenciesMeta: - '@vue/composition-api': - optional: true - dependencies: - vue: 3.2.39 - dev: false - /vue-demi/0.13.11_zv57lmwz3lpne326jxcwg2uc6q: resolution: {integrity: sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==} engines: {node: '>=12'} From 6ec2643b093a414d70e4f78f9261f99d330f7f83 Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Tue, 1 Nov 2022 11:53:38 +0100 Subject: [PATCH 40/40] give credit where credit is due --- docs/eslint/prefer-query-object-syntax.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/eslint/prefer-query-object-syntax.md b/docs/eslint/prefer-query-object-syntax.md index c14018c44d..3d55133c4f 100644 --- a/docs/eslint/prefer-query-object-syntax.md +++ b/docs/eslint/prefer-query-object-syntax.md @@ -56,3 +56,7 @@ If you don't care about useQuery consistency, then you will not need this rule. - [x] ✅ Recommended - [x] 🔧 Fixable + +## Credits + +This rule was initially developed by [KubaJastrz](https://github.com/KubaJastrz) in [eslint-plugin-react-query](https://github.com/KubaJastrz/eslint-plugin-react-query).