Skip to content

Commit

Permalink
feat: eslint-plugin-query
Browse files Browse the repository at this point in the history
* add @tanstack/eslint-plugin-query

* fix .eslintrc rule name

* make rule compatible with babel parser

* fix script typo

* remove dedicated example in favor of the basic example

* align package version with the other packages

* align eslint-plugin-query version

* fix displayName

* remove typescript from package

* omit call expressions from keys without the need of whitelist

* lint queryKey instead of useQuery

* update pnpm-lock.yaml

* fix auto-fix suggestion for complex ast

* better inline query key

* fix typo

* update scripts

* restore .eslintrc

* update typescript-eslint packages

* support member expression keys

* format

* should not fix with duplicate keys

* format

* empty commit (flaky test)

* should ignore keys from callback

* Update packages/eslint-plugin-query/package.json

Co-authored-by: Dominik Dorfmeister <office@dorfmeister.cc>

* add prefer-query-object-syntax rule

* fix indention

* fix headline

* add rule description

* fix lint errors

* widen import check for all adapters

* add support for solid-query (createQuery)

* docs: add eslint plugin section

* prettier

* move docs

* EsLint -> ESLint

* fix typo

* add missing dep

* update lockfile

* give credit where credit is due

Co-authored-by: Dominik Dorfmeister <office@dorfmeister.cc>
  • Loading branch information
Newbie012 and TkDodo authored Nov 1, 2022
1 parent 1614c31 commit 5d5c0f0
Show file tree
Hide file tree
Showing 30 changed files with 1,674 additions and 85 deletions.
17 changes: 17 additions & 0 deletions docs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down
49 changes: 49 additions & 0 deletions docs/eslint/eslint-plugin-query.md
Original file line number Diff line number Diff line change
@@ -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"]
}
```
47 changes: 47 additions & 0 deletions docs/eslint/exhaustive-deps.md
Original file line number Diff line number Diff line change
@@ -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
62 changes: 62 additions & 0 deletions docs/eslint/prefer-query-object-syntax.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
---
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
## 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).
6 changes: 1 addition & 5 deletions examples/react/basic-typescript/.eslintrc
Original file line number Diff line number Diff line change
@@ -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"]
}
1 change: 1 addition & 0 deletions examples/react/basic-typescript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
6 changes: 1 addition & 5 deletions examples/react/basic/.eslintrc
Original file line number Diff line number Diff line change
@@ -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"]
}
7 changes: 4 additions & 3 deletions examples/react/basic/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +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": "^4.13.0",
"@vitejs/plugin-react": "^2.0.0",
"vite": "^3.0.0"
},
Expand Down
17 changes: 11 additions & 6 deletions examples/react/basic/src/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
},
});
}

Expand Down Expand Up @@ -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,
});
}
Expand Down
1 change: 1 addition & 0 deletions examples/solid/simple/.eslintrc
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"extends": ["plugin:@tanstack/eslint-plugin-query/recommended"],
"parserOptions": {
"project": "./tsconfig.json",
"sourceType": "module"
Expand Down
1 change: 1 addition & 0 deletions examples/solid/simple/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
7 changes: 7 additions & 0 deletions packages/eslint-plugin-query/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json",
"sourceType": "module"
}
}
4 changes: 4 additions & 0 deletions packages/eslint-plugin-query/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export default {
displayName: 'eslint-plugin-query',
preset: '../../jest-preset.js',
}
44 changes: 44 additions & 0 deletions packages/eslint-plugin-query/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"name": "@tanstack/eslint-plugin-query",
"version": "4.13.0",
"description": "ESLint plugin for TanStack 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",
"clean": "rm -rf ./build",
"test:jest": "../../node_modules/.bin/jest --config ./jest.config.ts",
"test:eslint": "../../node_modules/.bin/eslint --ext .ts,.tsx ./src",
"test:dev": "pnpm run test:jest --watch"
},
"files": [
"build"
],
"tsup": {
"entry": [
"src/index.ts"
],
"external": [
"eslint"
],
"clean": true,
"bundle": true,
"outDir": "build/lib"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.41.0",
"@typescript-eslint/parser": "^5.41.0",
"@typescript-eslint/utils": "^5.41.0",
"eslint": "^8.26.0",
"tsup": "^6.3.0"
}
}
19 changes: 19 additions & 0 deletions packages/eslint-plugin-query/src/configs/index.test.ts
Original file line number Diff line number Diff line change
@@ -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",
},
},
}
`)
})
})
22 changes: 22 additions & 0 deletions packages/eslint-plugin-query/src/configs/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { TSESLint } from '@typescript-eslint/utils'
import { rules } from '../rules'

function generateRecommendedConfig(
allRules: Record<string, TSESLint.RuleModule<any, any>>,
) {
return Object.entries(allRules).reduce((memo, [name, rule]) => {
const { recommended } = rule.meta.docs || {}

return {
...memo,
...(recommended ? { [`@tanstack/query/${name}`]: recommended } : {}),
}
}, {} as Record<string, 'strict' | 'error' | 'warn'>)
}

export const configs = {
recommended: {
plugins: ['@tanstack/eslint-plugin-query'],
rules: generateRecommendedConfig(rules),
},
}
2 changes: 2 additions & 0 deletions packages/eslint-plugin-query/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { configs } from './configs'
export { rules } from './rules'
Loading

0 comments on commit 5d5c0f0

Please sign in to comment.