├── .editorconfig ├── .github ├── FUNDING.yml └── workflows │ ├── feature.yaml │ └── main.yaml ├── .gitignore ├── .husky └── pre-commit ├── .releaserc ├── COMPARISON_TABLE.md ├── INCOMPATIBLE_RULES.md ├── LICENSE ├── README.md ├── bin └── generate-typescript-compatibility-rules.js ├── compare ├── compare.js ├── find-deprecated.js ├── package-lock.json ├── package.json └── utilities.js ├── configurations ├── auto.test.ts ├── auto.ts ├── ava.ts ├── browser.ts ├── canonical.ts ├── graphql.ts ├── index.ts ├── jest.ts ├── jsdoc.ts ├── json.ts ├── jsx-a11y.ts ├── lodash.ts ├── mocha.ts ├── module.ts ├── next.ts ├── node.ts ├── prettier.ts ├── react.ts ├── regexp.ts ├── typescript-compatibility.ts ├── typescript-type-checking.ts ├── typescript.ts ├── vitest.ts ├── yaml.ts └── zod.ts ├── eslint.config.ts ├── package-lock.json ├── package.json └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 2 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: gajus 2 | patreon: gajus 3 | -------------------------------------------------------------------------------- /.github/workflows/feature.yaml: -------------------------------------------------------------------------------- 1 | jobs: 2 | test: 3 | environment: release 4 | name: Test 5 | runs-on: ubuntu-latest 6 | steps: 7 | - name: setup repository 8 | uses: actions/checkout@v3 9 | with: 10 | fetch-depth: 0 11 | - name: setup node.js 12 | uses: actions/setup-node@v3 13 | with: 14 | node-version: '22' 15 | - run: npm install 16 | - run: npm run lint 17 | - run: npm run test 18 | - run: npm run build 19 | timeout-minutes: 10 20 | name: Test and build 21 | on: 22 | pull_request: 23 | branches: 24 | - main 25 | types: 26 | - opened 27 | - synchronize 28 | - reopened 29 | - ready_for_review 30 | -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | jobs: 2 | test: 3 | environment: release 4 | name: Test 5 | runs-on: ubuntu-latest 6 | steps: 7 | - name: setup repository 8 | uses: actions/checkout@v3 9 | with: 10 | fetch-depth: 0 11 | - name: setup node.js 12 | uses: actions/setup-node@v3 13 | with: 14 | node-version: '22' 15 | - run: npm install 16 | - run: npm run lint 17 | - run: npm run test 18 | - run: npm run build 19 | - env: 20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 21 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 22 | run: npx semantic-release 23 | name: Test, build and release 24 | on: 25 | push: 26 | branches: 27 | - main 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage 2 | dist 3 | node_modules 4 | *.log 5 | .* 6 | !.editorconfig 7 | !.eslintignore 8 | !.eslintrc 9 | !.gitattributes 10 | !.github 11 | !.gitignore 12 | !.husky 13 | !.lintstagedrc.js 14 | !.npmignore 15 | !.npmrc 16 | !.prettierignore 17 | !.prettierrc 18 | !.releaserc 19 | !.yarnrc 20 | pnpm-lock.yaml -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run test 5 | -------------------------------------------------------------------------------- /.releaserc: -------------------------------------------------------------------------------- 1 | { 2 | "branches": [ 3 | "main" 4 | ], 5 | "plugins": [ 6 | "@semantic-release/commit-analyzer", 7 | "@semantic-release/github", 8 | "@semantic-release/npm" 9 | ] 10 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023, Gajus Kuizinas (https://gajus.com/) 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the Gajus Kuizinas (https://gajus.com/) nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL GAJUS KUIZINAS BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Canonical ESLint Config 2 | 3 | [![NPM version](http://img.shields.io/npm/v/eslint-config-canonical.svg?style=flat-square)](https://www.npmjs.org/package/eslint-config-canonical) 4 | 5 | The most comprehensive code style guide. 6 | 7 | Canonical consists of 1,000+ rules (40% auto-fixable), some of which are [custom written](https://github.com/gajus/eslint-plugin-canonical) for Canonical. Canonical goal is to reduce noise in code version control and promote use of the latest ES features. 8 | 9 | ## Usage 10 | 11 | Most projects should simply extend from [`canonical/auto`](#canonicalauto-ruleset): 12 | 13 | ```ts 14 | // eslint.config.ts 15 | import auto from 'eslint-config-canonical/auto'; 16 | import tseslint from 'typescript-eslint'; 17 | 18 | export default tseslint.config(auto); 19 | ``` 20 | 21 | ## Rulesets 22 | 23 | > **Note** Most projects should just use [`canonical/auto`](#canonicalauto-ruleset) and override settings when necessary for individual frameworks or file patterns (e.g. vitest vs ava). 24 | 25 | This package includes the following rulesets: 26 | 27 | * [`canonical/auto`](./configurations/auto.ts) – The Canonical code style guide. 28 | * [`canonical/ava`](./configurations/ava.ts) – for projects that use [AVA](https://ava.li/). 29 | * [`canonical/browser`](./configurations/browser.ts) – for projects that use DOM and other browser APIs. 30 | * [`canonical/graphql`](./configurations/graphql.ts) – for projects that use [GraphQL](https://graphql.org/). 31 | * [`canonical/jest`](./configurations/jest.ts) – for projects that use [jest](https://facebook.github.io/jest/). 32 | * [`canonical/jsdoc`](./configurations/jsdoc.ts) – for projects that use [JSDoc](https://jsdoc.app/). 33 | * [`canonical/json`](./configurations/json.ts) – for projects that use JSON. 34 | * [`canonical/jsx-a11y`](./configurations/jsx-a11y.ts) – for projects that use [React](https://facebook.github.io/react/) and want to include [accessibility checks](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y). 35 | * [`canonical/lodash`](./configurations/lodash.ts) – for projects that use [lodash](https://lodash.com/). 36 | * [`canonical/mocha`](./configurations/mocha.ts) – for projects that use [Mocha](https://mochajs.org/). 37 | * [`canonical/module`](./configurations/module.ts) – for projects that use ESM modules. 38 | * [`canonical/next`](./configurations/next.ts) – for projects that use [Next.js](https://nextjs.org/). 39 | * [`canonical/node`](./configurations/node.ts) – for projects that use Node.js. 40 | * [`canonical/prettier`](./configurations/prettier.ts) – applies [Prettier](https://prettier.io/) formatting. 41 | * [`canonical/react`](./configurations/react.ts) – for projects that use [React](https://facebook.github.io/react/). 42 | * [`canonical/regexp`](./configurations/regexp.ts) – for projects that use regular expressions. 43 | * [`canonical/typescript-type-checking`](./configurations/typescript-type-checking.ts) – for projects that use [TypeScript](http://typescriptlang.org/) and want additional rules that require type information (rules using type information take longer to run). 44 | * [`canonical/typescript`](./configurations/typescript.ts) – for projects that use [TypeScript](http://typescriptlang.org/). 45 | * [`canonical/vitest`](./configurations/vitest.ts) – for projects that use [Vitest](https://vitest.dev/). 46 | * [`canonical/yaml`](./configurations/yaml.ts) – for projects that use YAML. 47 | * [`canonical/zod`](./configurations/zod.ts) – for projects that use [Zod](https://github.com/colinhacks/zod). 48 | 49 | ## `canonical/auto` ruleset 50 | 51 | [`canonical/auto`](./configurations/auto.ts) is a special ruleset that uses [overrides](https://eslint.org/docs/user-guide/configuring/configuration-files#how-do-overrides-work) to only apply relevant style guides. This reduces the linting time and the number of false-positives. 52 | 53 | `canonical/auto` can be fine-tuned using `overrides` just like any other ESLint ruleset, e.g. 54 | 55 | ```json 56 | { 57 | "extends": [ 58 | "canonical/auto" 59 | ], 60 | "overrides": [ 61 | { 62 | "extends": [ 63 | "canonical/jsx-a11y" 64 | ], 65 | "files": "*.tsx" 66 | }, 67 | { 68 | "extends": [ 69 | "canonical/vitest" 70 | ], 71 | "files": "*.test.{ts,tsx}" 72 | } 73 | ], 74 | "root": true 75 | } 76 | ``` 77 | 78 | ### Compatibility with Prettier 79 | 80 | For the most part, Prettier and Canonical are already compatible. There are only a few transformations that are incompatible, e.g. Prettier enforces line-length and Canonical does not. As such, there is no good reason to use both. However, if you wish to use Prettier, you can do so by using `canonical/prettier` ruleset, which uses [`eslint-plugin-prettier`](https://www.npmjs.com/package/eslint-plugin-prettier) to apply Prettier formatting after applying Canonical rules. 81 | 82 | ```json 83 | { 84 | "extends": [ 85 | "canonical", 86 | "canonical/jsdoc", 87 | "canonical/regexp", 88 | "canonical/react", 89 | "canonical/typescript", 90 | "canonical/jest", 91 | "canonical/prettier" 92 | ] 93 | } 94 | ``` 95 | 96 | > **Note** The reason for using Prettier as an ESLint plugin (as opposed to a separate tool) is because having multiple tools that apply formatting complicates IDE and other tooling setup. 97 | 98 | > **Note** Your local `.prettierrc` is going to be ignored when using `canonical/prettier`. 99 | 100 | ### Compatibility with other style guides 101 | 102 | Since Canonical style guide includes more rules than any other style guide, you can have your codebase compatible with a specific style guide (e.g. [airbnb](https://www.npmjs.com/package/eslint-config-airbnb)) and benefit from Canonical for rules that are not covered by the other guide. All you have to do is extend from the Canonical style guide before extending from the desired style guide, e.g. 103 | 104 | ```json 105 | { 106 | "extends": [ 107 | "canonical", 108 | "canonical/jsdoc", 109 | "canonical/regexp", 110 | "canonical/react", 111 | "airbnb" 112 | ] 113 | } 114 | ``` 115 | 116 | ## Integrations 117 | 118 | ### Visual Studio Code 119 | 120 | Use the [dbaeumer.vscode-eslint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) extension that Microsoft provides officially. 121 | 122 | Example `.vscode/settings.json`: 123 | 124 | ```json 125 | { 126 | "eslint.validate": [ 127 | "css", 128 | "html", 129 | "javascript", 130 | "javascriptreact", 131 | "json", 132 | "markdown", 133 | "typescript", 134 | "typescriptreact", 135 | "yaml" 136 | ] 137 | } 138 | ``` 139 | 140 | The setting below turns on _Auto Fix_ for all providers including ESLint: 141 | 142 | ```json 143 | { 144 | "editor.codeActionsOnSave": { 145 | "source.fixAll.eslint": true 146 | }, 147 | "editor.defaultFormatter": "dbaeumer.vscode-eslint", 148 | "editor.formatOnSave": true 149 | } 150 | ``` 151 | 152 | Additionally, we found it that being explicit about which formatter you are using for each file improves DX: 153 | 154 | ```json 155 | { 156 | "[css][html][javascript][javascriptreact][json][markdown][typescript][typescriptreact][yaml]": { 157 | "editor.defaultFormatter": "dbaeumer.vscode-eslint" 158 | } 159 | } 160 | ``` 161 | 162 | While not required if you've configured explicit formatter for each file type, I advise that you explicitly disable `prettier` extension in your project: 163 | 164 | ```json 165 | { 166 | "prettier.enable": false, 167 | } 168 | ``` 169 | 170 | Sharing these settings in your project should be sufficient to prevent local settings accidentally overriding the desired formatter behavior. 171 | 172 | ## Benchmark 173 | 174 | ### Canonical vs Prettier 175 | 176 | This benchmark compares running ESLint using Canonical style guide against a project with 3,000+ files VS linting the same project using Prettier. 177 | 178 | ``` 179 | System: 180 | OS: macOS 11.6 181 | CPU: (16) x64 Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz 182 | Memory: 64.00 GB 183 | npmPackages: 184 | eslint: 8.1.0 185 | prettier: 2.4.1 186 | ``` 187 | 188 | As you may expect, Prettier is going to complete checks quicker – this is because it runs a lot fewer transforms and it only runs style checks (as opposed to static analyses). 189 | 190 | The first time you run ESLint, it will take significantly more time. However, if you enable [`--cache`](https://eslint.org/docs/user-guide/command-line-interface#--cache), then the follow up checks will complete in no time. 191 | 192 | ```bash 193 | $ time prettier src 194 | 27.06s user 195 | 1.74s system 196 | 166% cpu 197 | 17.335 total 198 | 199 | $ eslint --cache src 200 | 182.43s user 201 | 9.13s system 202 | 126% cpu 203 | 2:31.22 total 204 | 205 | $ eslint --cache src 206 | 1.96s user 207 | 0.39s system 208 | 107% cpu 209 | 2.188 total 210 | ``` 211 | 212 | Using ESLint cache will dramatically improve ESLint running time by ensuring that only changed files are linted. This is useful if you are using ESLint to run `pre-commit` / `pre-push` [git hooks](https://git-scm.com/docs/githooks) or otherwise depend on these checks completing in real-time. 213 | 214 | Additionally, if performance is a consideration, you may consider: 215 | 216 | * [`jest-eslint-runner`](https://github.com/jest-community/jest-runner-eslint) 217 | * [Integrations](#integrations) 218 | 219 | These options provide near instant feedback just how you are used to when using Prettier. 220 | 221 | ## Table of Comparison 222 | 223 | [COMPARISON_TABLE.md](./COMPARISON_TABLE.md) 224 | 225 | ## Versioning Policy 226 | 227 | All breaking changes will bump the major version as per the semver convention. Therefore, every new rule addition will increase the major version. 228 | 229 | ## Development 230 | 231 | First, run `npm install` and then `npm run setup-dev`. Then, any time that ESLint dependencies are updated you must: 232 | 233 | 1. Run `npm run generate-typescript-compatibility-rules` script. It disables and override any TypeScript rules that are incompatible with ESLint built-in rules. 234 | 1. Run `npm run compare` script. It generates ruleset comparison table, updates README.md, and identifies rules that are not configured. 235 | 236 | ## Incompatible rules 237 | 238 | [INCOMPATIBLE_RULES.md](./INCOMPATIBLE_RULES.md) 239 | -------------------------------------------------------------------------------- /bin/generate-typescript-compatibility-rules.js: -------------------------------------------------------------------------------- 1 | import eslintConfiguration from '../configurations/eslintrc'; 2 | import typescriptRules from '@typescript-eslint/eslint-plugin'; 3 | import { builtinRules } from 'eslint/use-at-your-own-risk'; 4 | 5 | const builtinRuleNames = Object.keys(Object.fromEntries(builtinRules)); 6 | const typescriptRuleNames = Object.keys(typescriptRules); 7 | 8 | const incompatibleRuleNames = []; 9 | 10 | for (const typescriptRuleName of typescriptRuleNames) { 11 | if (builtinRuleNames.includes(typescriptRuleName)) { 12 | incompatibleRuleNames.push(typescriptRuleName); 13 | } 14 | } 15 | 16 | const appendRules = {}; 17 | 18 | for (const incompatibleRuleName of incompatibleRuleNames) { 19 | if (!eslintConfiguration.rules[incompatibleRuleName] === undefined) { 20 | continue; 21 | } 22 | 23 | appendRules[incompatibleRuleName] = 0; 24 | appendRules['@typescript-eslint/' + incompatibleRuleName] = 25 | eslintConfiguration.rules[incompatibleRuleName]; 26 | } 27 | 28 | const orderedRules = { 29 | rules: Object.fromEntries(Object.entries(appendRules).sort()), 30 | }; 31 | 32 | // eslint-disable-next-line no-console 33 | console.log('export default ' + JSON.stringify(orderedRules, '', ' ')); 34 | -------------------------------------------------------------------------------- /compare/compare.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | /* eslint-disable complexity */ 3 | 4 | const { 5 | getConfigurationRules, 6 | getLoadedRules, 7 | getRuleConfiguration, 8 | getRuleLink, 9 | isRuleEnabled, 10 | normalizeConfiguration, 11 | } = require('./utilities'); 12 | const { readFile, writeFile } = require('node:fs/promises'); 13 | const { resolve } = require('node:path'); 14 | const stringify = require('safe-stable-stringify'); 15 | 16 | const getIncompatibleRuleNames = (canonicalRules, comparedRules) => { 17 | const incompatibleRuleNames = []; 18 | 19 | for (const ruleName of Object.keys(comparedRules)) { 20 | if (!isRuleEnabled(comparedRules[ruleName]?.[0])) { 21 | continue; 22 | } 23 | 24 | const canonicalRuleConfiguration = stringify( 25 | normalizeConfiguration(canonicalRules[ruleName]), 26 | null, 27 | ' ', 28 | ); 29 | const comparedRuleConfiguration = stringify( 30 | normalizeConfiguration(comparedRules[ruleName]), 31 | null, 32 | ' ', 33 | ); 34 | 35 | if (canonicalRuleConfiguration === comparedRuleConfiguration) { 36 | continue; 37 | } 38 | 39 | incompatibleRuleNames.push(ruleName); 40 | } 41 | 42 | return incompatibleRuleNames; 43 | }; 44 | 45 | const createIncompatibleRuleSummary = ( 46 | urlSafeName, 47 | comparedName, 48 | canonicalRules, 49 | comparedRules, 50 | ) => { 51 | // We are ignoring these rules because their configuration is breaking table layout. 52 | const ignoreRuleNames = [ 53 | 'no-restricted-globals', 54 | 'no-restricted-syntax', 55 | 'capitalized-comments', 56 | 'react/sort-comp', 57 | 'no-restricted-properties', 58 | ]; 59 | 60 | const rows = []; 61 | 62 | const incompatibleRuleNames = getIncompatibleRuleNames( 63 | canonicalRules, 64 | comparedRules, 65 | ); 66 | 67 | for (const incompatibleRuleName of incompatibleRuleNames) { 68 | if (ignoreRuleNames.includes(incompatibleRuleName)) { 69 | continue; 70 | } 71 | 72 | const canonicalRuleConfiguration = stringify( 73 | normalizeConfiguration(canonicalRules[incompatibleRuleName]), 74 | null, 75 | ' ', 76 | ); 77 | const comparedRuleConfiguration = stringify( 78 | normalizeConfiguration(comparedRules[incompatibleRuleName]), 79 | null, 80 | ' ', 81 | ); 82 | 83 | rows.push( 84 | ` 85 | 86 | 87 | ${incompatibleRuleName} 88 | (back to comparison table 👆) 89 | 90 | 91 | 92 | 93 |
${canonicalRuleConfiguration}
94 |
${comparedRuleConfiguration}
95 | 96 | `.trim(), 97 | ); 98 | } 99 | 100 | return [ 101 | '### ' + comparedName + ' Incompatible Rules', 102 | '', 103 | ...rows, 104 | '
', 105 | ].join('\n'); 106 | }; 107 | 108 | (async () => { 109 | const loadedRules = await getLoadedRules(); 110 | 111 | const canonicalRules = await getConfigurationRules({ 112 | extends: [ 113 | 'canonical/ava', 114 | 'canonical/browser', 115 | 'canonical/jest', 116 | 'canonical/json', 117 | 'canonical/jsx-a11y', 118 | 'canonical/lodash', 119 | 'canonical/mocha', 120 | 'canonical/module', 121 | 'canonical/next', 122 | 'canonical/node', 123 | 'canonical/react', 124 | 'canonical/typescript', 125 | 'canonical/yaml', 126 | 127 | // The order is important! 128 | // The last ruleset overrides rules in previous rulesets. 129 | // This affects rules that are overridden in specific configs, e.g. 130 | // typescript disabled no-duplicate-imports but enables @import/no-duplicates. 131 | 'canonical', 132 | ], 133 | }); 134 | 135 | const airbnbRules = await getConfigurationRules({ 136 | extends: ['airbnb'], 137 | }); 138 | 139 | const googleRules = await getConfigurationRules({ 140 | extends: ['google'], 141 | }); 142 | 143 | const standardRules = await getConfigurationRules({ 144 | extends: ['standard'], 145 | }); 146 | 147 | const xoRules = await getConfigurationRules({ 148 | extends: ['xo'], 149 | }); 150 | 151 | const ruleNames = Object.keys(loadedRules); 152 | 153 | const markdownLines = [ 154 | '', 155 | '|Rule|CN|[AB](https://www.npmjs.com/package/eslint-config-airbnb)|[GG](https://www.npmjs.com/package/eslint-config-google)|[SD](https://www.npmjs.com/package/eslint-config-standard)|[XO](https://github.com/xojs/eslint-config-xo)|', 156 | '|---|---|---|---|---|---|', 157 | ]; 158 | 159 | let fixableRuleCount = 0; 160 | 161 | const airbnbIncompatibleRuleNames = getIncompatibleRuleNames( 162 | canonicalRules, 163 | airbnbRules, 164 | ); 165 | const googleIncompatibleRuleNames = getIncompatibleRuleNames( 166 | canonicalRules, 167 | googleRules, 168 | ); 169 | const standardIncompatibleRuleNames = getIncompatibleRuleNames( 170 | canonicalRules, 171 | standardRules, 172 | ); 173 | const xoIncompatibleRuleNames = getIncompatibleRuleNames( 174 | canonicalRules, 175 | xoRules, 176 | ); 177 | 178 | for (const ruleName of ruleNames) { 179 | if (loadedRules[ruleName]?.meta?.fixable) { 180 | fixableRuleCount++; 181 | } 182 | 183 | markdownLines.push( 184 | '|' + 185 | getRuleLink(ruleName) + 186 | '
' + 189 | (loadedRules[ruleName]?.meta?.fixable ? ' 🛠' : '') + 190 | (loadedRules[ruleName]?.meta?.deprecated ? ' ⛔️' : '') + 191 | '|' + 192 | getRuleConfiguration(canonicalRules, ruleName) + 193 | '|' + 194 | getRuleConfiguration(airbnbRules, ruleName) + 195 | (airbnbIncompatibleRuleNames.includes(ruleName) 196 | ? '?' 197 | : '') + 198 | '|' + 199 | getRuleConfiguration(googleRules, ruleName) + 200 | (googleIncompatibleRuleNames.includes(ruleName) 201 | ? '?' 202 | : '') + 203 | '|' + 204 | getRuleConfiguration(standardRules, ruleName) + 205 | (standardIncompatibleRuleNames.includes(ruleName) 206 | ? '?' 207 | : '') + 208 | '|' + 209 | getRuleConfiguration(xoRules, ruleName) + 210 | (xoIncompatibleRuleNames.includes(ruleName) 211 | ? '?' 212 | : '') + 213 | '|', 214 | ); 215 | } 216 | 217 | markdownLines.push(''); 218 | 219 | const README_PATH = resolve(__dirname, '../COMPARISON_TABLE.md'); 220 | 221 | await writeFile( 222 | README_PATH, 223 | (await readFile(README_PATH, 'utf8')).replace( 224 | /[\s\S]+/u, 225 | markdownLines.join('\n'), 226 | ), 227 | ); 228 | 229 | await writeFile( 230 | README_PATH, 231 | (await readFile(README_PATH, 'utf8')).replace( 232 | /[\s\S]+/u, 233 | '\n' + 234 | [ 235 | createIncompatibleRuleSummary( 236 | 'airbnb', 237 | 'AirBnb', 238 | canonicalRules, 239 | airbnbRules, 240 | ), 241 | createIncompatibleRuleSummary( 242 | 'google', 243 | 'Google', 244 | canonicalRules, 245 | googleRules, 246 | ), 247 | createIncompatibleRuleSummary( 248 | 'standard', 249 | 'Standard', 250 | canonicalRules, 251 | standardRules, 252 | ), 253 | createIncompatibleRuleSummary('xo', 'XO', canonicalRules, xoRules), 254 | ].join('\n\n') + 255 | '\n', 256 | ), 257 | ); 258 | 259 | const ignoreDisabled = [ 260 | 'camelcase', 261 | 'capitalized-comments', 262 | 'class-methods-use-this', 263 | 'import/named', 264 | 'import/no-unresolved', 265 | 'import/prefer-default-export', 266 | 'jsx-a11y/control-has-associated-label', 267 | 'jsx-a11y/lang', 268 | 'jsx-a11y/no-autofocus', 269 | 'max-classes-per-file', 270 | 'max-depth', 271 | 'max-len', 272 | 'max-nested-callbacks', 273 | 'max-params', 274 | 'multiline-ternary', 275 | 'new-cap', 276 | 'newline-per-chained-call', 277 | 'no-await-in-loop', 278 | 'no-continue', 279 | 'no-else-return', 280 | 'no-empty-function', 281 | 'no-invalid-this', 282 | 'no-mixed-operators', 283 | 'no-nested-ternary', 284 | 'no-plusplus', 285 | 'no-restricted-globals', 286 | 'no-restricted-imports', 287 | 'no-restricted-properties', 288 | 'no-restricted-syntax', 289 | 'no-return-await', 290 | 'no-underscore-dangle', 291 | 'object-curly-spacing', 292 | 'prefer-template', 293 | 'react/destructuring-assignment', 294 | 'react/forbid-foreign-prop-types', 295 | 'react/forbid-prop-types', 296 | 'react/jsx-filename-extension', 297 | 'react/jsx-one-expression-per-line', 298 | 'react/jsx-props-no-spreading', 299 | 'react/jsx-wrap-multilines', 300 | 'react/no-unescaped-entities', 301 | 'react/react-in-jsx-scope', 302 | 'react/require-default-props', 303 | ]; 304 | 305 | for (const ruleName of ruleNames) { 306 | if ( 307 | !ignoreDisabled.includes(ruleName) && 308 | loadedRules[ruleName]?.meta?.deprecated !== true && 309 | !isRuleEnabled(canonicalRules[ruleName]?.[0]) && 310 | (isRuleEnabled(airbnbRules[ruleName]?.[0]) || 311 | isRuleEnabled(googleRules[ruleName]?.[0]) || 312 | isRuleEnabled(standardRules[ruleName]?.[0]) || 313 | isRuleEnabled(xoRules[ruleName]?.[0])) 314 | ) { 315 | console.warn('disabled rule "' + ruleName + '"', { 316 | airbnb: airbnbRules[ruleName], 317 | canonical: canonicalRules[ruleName], 318 | google: googleRules[ruleName], 319 | standard: standardRules[ruleName], 320 | xo: xoRules[ruleName], 321 | }); 322 | } 323 | } 324 | 325 | const ignoreUnused = [ 326 | // Deprecated in documentation. 327 | // Not reflected in meta. 328 | 'jsx-a11y/no-onchange', 329 | ]; 330 | 331 | for (const ruleName of ruleNames) { 332 | if ( 333 | !ignoreUnused.includes(ruleName) && 334 | loadedRules[ruleName]?.meta?.deprecated !== true && 335 | !canonicalRules[ruleName] 336 | ) { 337 | console.warn('unused rule "' + ruleName + '"', { 338 | airbnb: airbnbRules[ruleName], 339 | google: googleRules[ruleName], 340 | standard: standardRules[ruleName], 341 | xo: xoRules[ruleName], 342 | }); 343 | } 344 | } 345 | 346 | for (const ruleName of ruleNames) { 347 | if (loadedRules[ruleName]?.meta?.deprecated && canonicalRules[ruleName]) { 348 | console.warn('deprecated rule "' + ruleName + '"'); 349 | } 350 | } 351 | 352 | console.log( 353 | 'Canonical rules: ' + 354 | Object.keys(canonicalRules).length + 355 | ' (' + 356 | Math.round( 357 | (fixableRuleCount / Object.keys(canonicalRules).length) * 100, 358 | ) + 359 | '% auto-fixable)', 360 | ); 361 | console.log('Airbnb rules: ' + Object.keys(airbnbRules).length); 362 | console.log('Google rules: ' + Object.keys(googleRules).length); 363 | console.log('Standard rules: ' + Object.keys(standardRules).length); 364 | console.log('XO rules: ' + Object.keys(xoRules).length); 365 | })(); 366 | -------------------------------------------------------------------------------- /compare/find-deprecated.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/issues/825 3 | * https://github.com/eslint/eslint/issues/15292 4 | */ 5 | 6 | const { getLoadedRules } = require('./utilities'); 7 | const got = require('got'); 8 | 9 | (async () => { 10 | const loadedRules = await getLoadedRules(); 11 | 12 | const ruleNames = Object.keys(loadedRules); 13 | 14 | for (const ruleName of ruleNames) { 15 | if (loadedRules[ruleName]?.meta?.deprecated) { 16 | continue; 17 | } 18 | 19 | const ruleDocumentationUrl = loadedRules[ruleName]?.meta?.docs?.url; 20 | 21 | if (!ruleDocumentationUrl) { 22 | continue; 23 | } 24 | 25 | const response = await got(ruleDocumentationUrl, { 26 | resolveBodyOnly: true, 27 | }); 28 | 29 | if (response.toLowerCase().includes('deprecated')) { 30 | // eslint-disable-next-line no-console 31 | console.warn( 32 | '⚠️ ' + 33 | ruleName + 34 | ' suspected improperly deprecated rule (' + 35 | ruleDocumentationUrl + 36 | ')', 37 | ); 38 | } 39 | } 40 | })(); 41 | -------------------------------------------------------------------------------- /compare/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "eslint": "^8.49.0", 4 | "eslint-config-airbnb": "^19.0.4", 5 | "eslint-config-canonical": "^41.2.1", 6 | "eslint-config-google": "^0.14.0", 7 | "eslint-config-standard": "^17.1.0", 8 | "eslint-config-xo": "^0.43.1", 9 | "eslint-plugin-jsx-a11y": "^6.7.1", 10 | "eslint-plugin-n": "^16.2.0", 11 | "eslint-plugin-standard": "^4.1.0", 12 | "got": "^12.0.0", 13 | "safe-stable-stringify": "^2.4.3", 14 | "typescript": "^5.2.2" 15 | }, 16 | "scripts": { 17 | "compare": "node compare.js" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /compare/utilities.js: -------------------------------------------------------------------------------- 1 | const { ESLint } = require('eslint'); 2 | const { builtinRules } = require('eslint/use-at-your-own-risk'); 3 | 4 | const getConfigurationPluginNames = async (configuration) => { 5 | const engine = new ESLint({ 6 | baseConfig: configuration, 7 | useEslintrc: false, 8 | }); 9 | 10 | const calculatedConfiguration = 11 | await engine.calculateConfigForFile('./compare'); 12 | 13 | return calculatedConfiguration.plugins; 14 | }; 15 | 16 | const getPluginRules = (pluginName) => { 17 | // eslint-disable-next-line import/no-dynamic-require 18 | const { rules } = require( 19 | pluginName.startsWith('@') 20 | ? pluginName + '/eslint-plugin' 21 | : 'eslint-plugin-' + pluginName, 22 | ); 23 | 24 | return Object.fromEntries( 25 | Object.entries(rules).map(([ruleName, ruleConfiguration]) => { 26 | return [pluginName + '/' + ruleName, ruleConfiguration]; 27 | }), 28 | ); 29 | }; 30 | 31 | const configurationNames = [ 32 | 'airbnb', 33 | 'google', 34 | 'standard', 35 | 'canonical', 36 | 'canonical/ava', 37 | 'canonical/browser', 38 | 'canonical/jest', 39 | 'canonical/json', 40 | 'canonical/lodash', 41 | 'canonical/mocha', 42 | 'canonical/module', 43 | 'canonical/node', 44 | 'canonical/react', 45 | 'canonical/typescript', 46 | 'canonical/yaml', 47 | ]; 48 | 49 | const getLoadedRules = async () => { 50 | const usedPluginNames = []; 51 | 52 | for (const configurationName of configurationNames) { 53 | const configurationUsedPluginNames = await getConfigurationPluginNames({ 54 | extends: [configurationName], 55 | root: true, 56 | }); 57 | 58 | for (const configurationUsedPluginName of configurationUsedPluginNames) { 59 | if (!usedPluginNames.includes(configurationUsedPluginNames)) { 60 | usedPluginNames.push(configurationUsedPluginName); 61 | } 62 | } 63 | } 64 | 65 | let loadedRules = { 66 | ...Object.fromEntries(builtinRules), 67 | }; 68 | 69 | for (const usedPluginName of usedPluginNames) { 70 | loadedRules = { 71 | ...loadedRules, 72 | ...getPluginRules(usedPluginName), 73 | }; 74 | } 75 | 76 | return Object.fromEntries( 77 | Object.entries(loadedRules).sort((a, b) => { 78 | return a[0].localeCompare(b[0]); 79 | }), 80 | ); 81 | }; 82 | 83 | /** 84 | * Determines what rules are going to be used for a given ESLint configuration. 85 | */ 86 | const getConfigurationRules = async (configuration) => { 87 | const engine = new ESLint({ 88 | baseConfig: configuration, 89 | useEslintrc: false, 90 | }); 91 | 92 | const calculatedConfiguration = 93 | await engine.calculateConfigForFile('./compare'); 94 | 95 | return calculatedConfiguration.rules; 96 | }; 97 | 98 | const getRuleLink = (ruleName) => { 99 | if (!ruleName.includes('/')) { 100 | return ( 101 | '[`' + 102 | ruleName + 103 | '`](https://eslint.org/docs/latest/rules/' + 104 | ruleName + 105 | ')' 106 | ); 107 | } 108 | 109 | if (ruleName.startsWith('fp/')) { 110 | return ( 111 | '[`' + 112 | ruleName + 113 | '`](https://github.com/jfmengels/eslint-plugin-fp/blob/master/docs/rules/' + 114 | ruleName.replace(/^fp\//u, '') + 115 | '.md)' 116 | ); 117 | } 118 | 119 | if (ruleName.startsWith('ava/')) { 120 | return ( 121 | '[`' + 122 | ruleName + 123 | '`](https://github.com/avajs/eslint-plugin-ava/blob/main/docs/rules/' + 124 | ruleName.replace(/^ava\//u, '') + 125 | '.md)' 126 | ); 127 | } 128 | 129 | if (ruleName.startsWith('canonical/')) { 130 | return ( 131 | '[`' + 132 | ruleName + 133 | '`](https://github.com/gajus/eslint-plugin-canonical#eslint-plugin-canonical-rules-' + 134 | ruleName.replace(/^canonical\//u, '') + 135 | ')' 136 | ); 137 | } 138 | 139 | if (ruleName.startsWith('eslint-comments/')) { 140 | return ( 141 | '[`' + 142 | ruleName + 143 | '`](https://github.com/mysticatea/eslint-plugin-eslint-comments/blob/master/docs/rules/' + 144 | ruleName.replace(/^eslint-comments\//u, '') + 145 | '.md)' 146 | ); 147 | } 148 | 149 | if (ruleName.startsWith('unicorn/')) { 150 | return ( 151 | '[`' + 152 | ruleName + 153 | '`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/' + 154 | ruleName.replace(/^unicorn\//u, '') + 155 | '.md)' 156 | ); 157 | } 158 | 159 | if (ruleName.startsWith('jsdoc/')) { 160 | return ( 161 | '[`' + 162 | ruleName + 163 | '`](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/' + 164 | ruleName.replace(/^jsdoc\//u, '') + 165 | '.md)' 166 | ); 167 | } 168 | 169 | if (ruleName.startsWith('import/')) { 170 | return ( 171 | '[`' + 172 | ruleName + 173 | '`](https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/' + 174 | ruleName.replace(/^import\//u, '') + 175 | '.md)' 176 | ); 177 | } 178 | 179 | if (ruleName.startsWith('react/')) { 180 | return ( 181 | '[`' + 182 | ruleName + 183 | '`](https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/' + 184 | ruleName.replace(/^react\//u, '') + 185 | '.md)' 186 | ); 187 | } 188 | 189 | if (ruleName.startsWith('promise/')) { 190 | return ( 191 | '[`' + 192 | ruleName + 193 | '`](https://github.com/eslint-community/eslint-plugin-promise/blob/main/docs/rules' + 194 | ruleName.replace(/^promise\//u, '') + 195 | '.md)' 196 | ); 197 | } 198 | 199 | if (ruleName.startsWith('lodash/')) { 200 | return ( 201 | '[`' + 202 | ruleName + 203 | '`](https://github.com/wix-incubator/eslint-plugin-lodash/blob/master/docs/rules/' + 204 | ruleName.replace(/^lodash\//u, '') + 205 | '.md)' 206 | ); 207 | } 208 | 209 | if (ruleName.startsWith('mocha/')) { 210 | return ( 211 | '[`' + 212 | ruleName + 213 | '`](https://github.com/lo1tuma/eslint-plugin-mocha/blob/main/docs/rules/' + 214 | ruleName.replace(/^mocha\//u, '') + 215 | '.md)' 216 | ); 217 | } 218 | 219 | if (ruleName.startsWith('n/')) { 220 | return ( 221 | '[`' + 222 | ruleName + 223 | '`](https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/' + 224 | ruleName.replace(/^n\//u, '') + 225 | '.md)' 226 | ); 227 | } 228 | 229 | if (ruleName.startsWith('node/')) { 230 | return ( 231 | '[`' + 232 | ruleName + 233 | '`](https://github.com/mysticatea/eslint-plugin-node/blob/master/docs/rules/' + 234 | ruleName.replace(/^node\//u, '') + 235 | '.md)' 236 | ); 237 | } 238 | 239 | if (ruleName.startsWith('jsx-a11y/')) { 240 | return ( 241 | '[`' + 242 | ruleName + 243 | '`](https://github.com/infofarmer/eslint-plugin-jsx-a11y/blob/main/docs/rules/' + 244 | ruleName.replace(/^jsx-a11y\//u, '') + 245 | '.md)' 246 | ); 247 | } 248 | 249 | if (ruleName.startsWith('jest/')) { 250 | return ( 251 | '[`' + 252 | ruleName + 253 | '`](https://github.com/jest-community/eslint-plugin-jest/blob/main/docs/rules/' + 254 | ruleName.replace(/^jest\//u, '') + 255 | '.md)' 256 | ); 257 | } 258 | 259 | if (ruleName.startsWith('jsonc/')) { 260 | return ( 261 | '[`' + 262 | ruleName + 263 | '`](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/' + 264 | ruleName.replace(/^jsonc\//u, '') + 265 | '.html)' 266 | ); 267 | } 268 | 269 | if (ruleName.startsWith('@typescript-eslint/')) { 270 | return ( 271 | '[`' + 272 | ruleName + 273 | '`](https://typescript-eslint.io/rules/' + 274 | ruleName.replace(/^@typescript-eslint\//u, '') + 275 | ')' 276 | ); 277 | } 278 | 279 | if (ruleName.startsWith('yml/')) { 280 | return ( 281 | '[`' + 282 | ruleName + 283 | '`](https://ota-meshi.github.io/eslint-plugin-yml/rules/' + 284 | ruleName.replace(/^yml\//u, '') + 285 | '.html)' 286 | ); 287 | } 288 | 289 | return '`' + ruleName + '`'; 290 | }; 291 | 292 | const isRuleEnabled = (ruleValue) => { 293 | if (ruleValue === 1 || ruleValue === 'warn') { 294 | return true; 295 | } 296 | 297 | if (ruleValue === 2 || ruleValue === 'error') { 298 | return true; 299 | } 300 | 301 | return false; 302 | }; 303 | 304 | const normalizeConfiguration = (configuration) => { 305 | if (!configuration) { 306 | return ['off']; 307 | } 308 | 309 | const nextConfiguration = [...configuration]; 310 | 311 | if (typeof nextConfiguration[0] === 'number') { 312 | if (nextConfiguration[0] === 0) { 313 | nextConfiguration[0] = 'off'; 314 | } else if (nextConfiguration[0] === 1) { 315 | nextConfiguration[0] = 'warn'; 316 | } else if (nextConfiguration[0] === 2) { 317 | nextConfiguration[0] = 'error'; 318 | } 319 | } 320 | 321 | return nextConfiguration; 322 | }; 323 | 324 | const describeRuleValue = (ruleValue) => { 325 | if (ruleValue === undefined) { 326 | return '👻'; 327 | } 328 | 329 | if (ruleValue === 0 || ruleValue === 'off') { 330 | return '❌'; 331 | } 332 | 333 | if (ruleValue === 1 || ruleValue === 'warn') { 334 | return '⚠️'; 335 | } 336 | 337 | if (ruleValue === 2 || ruleValue === 'error') { 338 | return '🚨'; 339 | } 340 | 341 | return false; 342 | }; 343 | 344 | const getRuleConfiguration = (ruleset, ruleName) => { 345 | const ruleValueDescription = describeRuleValue(ruleset[ruleName]); 346 | 347 | if (ruleValueDescription) { 348 | return ruleValueDescription; 349 | } 350 | 351 | return describeRuleValue(ruleset[ruleName][0]); 352 | }; 353 | 354 | module.exports = { 355 | getConfigurationRules, 356 | getLoadedRules, 357 | getRuleConfiguration, 358 | getRuleLink, 359 | isRuleEnabled, 360 | normalizeConfiguration, 361 | }; 362 | -------------------------------------------------------------------------------- /configurations/auto.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | 3 | test('foo', (t) => { 4 | t.pass(); 5 | }); 6 | -------------------------------------------------------------------------------- /configurations/auto.ts: -------------------------------------------------------------------------------- 1 | import * as canonical from './canonical.js'; 2 | import * as graphql from './graphql.js'; 3 | import * as jsdoc from './jsdoc.js'; 4 | import * as json from './json.js'; 5 | import * as prettier from './prettier.js'; 6 | import * as react from './react.js'; 7 | import * as regexp from './regexp.js'; 8 | import * as typescript from './typescript.js'; 9 | import * as yaml from './yaml.js'; 10 | import tseslint from 'typescript-eslint'; 11 | 12 | export default tseslint.config( 13 | jsdoc.recommended, 14 | canonical.recommended, 15 | typescript.recommended, 16 | regexp.recommended, 17 | react.recommended, 18 | prettier.recommended, 19 | json.recommended, 20 | yaml.recommended, 21 | graphql.recommended, 22 | ); 23 | -------------------------------------------------------------------------------- /configurations/ava.ts: -------------------------------------------------------------------------------- 1 | import ava from 'eslint-plugin-ava'; 2 | import unicorn from 'eslint-plugin-unicorn'; 3 | import tseslint from 'typescript-eslint'; 4 | 5 | export const recommended = tseslint.config({ 6 | files: ['**/*.test.{js,ts,tsx}'], 7 | plugins: { 8 | ava, 9 | unicorn, 10 | }, 11 | rules: { 12 | 'ava/assertion-arguments': 2, 13 | 'ava/hooks-order': 2, 14 | 'ava/max-asserts': [2, 5], 15 | 'ava/no-async-fn-without-await': 2, 16 | 'ava/no-duplicate-modifiers': 2, 17 | 'ava/no-identical-title': 2, 18 | 'ava/no-ignored-test-files': 2, 19 | 'ava/no-import-test-files': 0, 20 | 'ava/no-incorrect-deep-equal': 2, 21 | 'ava/no-inline-assertions': 2, 22 | 'ava/no-nested-tests': 2, 23 | 'ava/no-only-test': 2, 24 | 'ava/no-skip-assert': 2, 25 | 'ava/no-skip-test': 2, 26 | 'ava/no-todo-implementation': 2, 27 | 'ava/no-todo-test': 2, 28 | 'ava/no-unknown-modifiers': 2, 29 | 'ava/prefer-async-await': 2, 30 | 'ava/prefer-power-assert': 0, 31 | 'ava/prefer-t-regex': 2, 32 | 'ava/test-title': 2, 33 | 'ava/test-title-format': 0, 34 | 'ava/use-t': 2, 35 | 'ava/use-t-throws-async-well': 2, 36 | 'ava/use-t-well': 2, 37 | 'ava/use-test': 2, 38 | 'ava/use-true-false': 2, 39 | 'id-length': [ 40 | 2, 41 | { 42 | exceptions: ['_', '$', 'a', 'b', 'x', 'y', 't'], 43 | min: 2, 44 | }, 45 | ], 46 | 'unicorn/consistent-function-scoping': 0, 47 | }, 48 | }); 49 | -------------------------------------------------------------------------------- /configurations/browser.ts: -------------------------------------------------------------------------------- 1 | import unicorn from 'eslint-plugin-unicorn'; 2 | import globals from 'globals'; 3 | import tseslint from 'typescript-eslint'; 4 | 5 | export const recommended = tseslint.config({ 6 | languageOptions: { 7 | globals: { 8 | ...globals.browser, 9 | }, 10 | }, 11 | plugins: { 12 | unicorn, 13 | }, 14 | rules: { 15 | 'unicorn/prefer-dom-node-append': 2, 16 | 'unicorn/prefer-dom-node-dataset': 2, 17 | 'unicorn/prefer-dom-node-remove': 2, 18 | 'unicorn/prefer-dom-node-text-content': 2, 19 | 'unicorn/prefer-keyboard-event-key': 2, 20 | 'unicorn/prefer-modern-dom-apis': 2, 21 | }, 22 | }); 23 | -------------------------------------------------------------------------------- /configurations/canonical.ts: -------------------------------------------------------------------------------- 1 | import stylisticPlugin from '@stylistic/eslint-plugin'; 2 | import canonicalPlugin from 'eslint-plugin-canonical'; 3 | import eslintComments from 'eslint-plugin-eslint-comments'; 4 | import importPlugin from 'eslint-plugin-import'; 5 | import perfectionist from 'eslint-plugin-perfectionist'; 6 | import promisePlugin from 'eslint-plugin-promise'; 7 | import unicornPlugin from 'eslint-plugin-unicorn'; 8 | import tseslint from 'typescript-eslint'; 9 | 10 | export const recommended = tseslint.config({ 11 | files: ['**/*.{js,jsx,cjs,mjs,ts,tsx}'], 12 | plugins: { 13 | '@stylistic': stylisticPlugin, 14 | canonical: canonicalPlugin, 15 | 'eslint-comments': eslintComments, 16 | import: importPlugin, 17 | perfectionist, 18 | promise: promisePlugin, 19 | unicorn: unicornPlugin, 20 | }, 21 | rules: { 22 | ...perfectionist.configs['recommended-natural'].rules, 23 | '@stylistic/array-bracket-newline': [ 24 | 2, 25 | { 26 | minItems: 1, 27 | multiline: true, 28 | }, 29 | ], 30 | '@stylistic/array-bracket-spacing': [2, 'never'], 31 | '@stylistic/array-element-newline': [ 32 | 2, 33 | { 34 | minItems: 1, 35 | multiline: true, 36 | }, 37 | ], 38 | '@stylistic/arrow-parens': [2, 'always'], 39 | '@stylistic/arrow-spacing': [ 40 | 2, 41 | { 42 | after: true, 43 | before: true, 44 | }, 45 | ], 46 | '@stylistic/block-spacing': [2, 'always'], 47 | '@stylistic/brace-style': [ 48 | 2, 49 | '1tbs', 50 | { 51 | allowSingleLine: false, 52 | }, 53 | ], 54 | '@stylistic/comma-dangle': [ 55 | 2, 56 | { 57 | arrays: 'always-multiline', 58 | exports: 'always-multiline', 59 | functions: 'always-multiline', 60 | imports: 'always-multiline', 61 | objects: 'always-multiline', 62 | }, 63 | ], 64 | '@stylistic/comma-spacing': [ 65 | 2, 66 | { 67 | after: true, 68 | before: false, 69 | }, 70 | ], 71 | '@stylistic/comma-style': [2, 'last'], 72 | '@stylistic/computed-property-spacing': [2, 'never'], 73 | '@stylistic/dot-location': [2, 'property'], 74 | '@stylistic/eol-last': 2, 75 | '@stylistic/func-call-spacing': [2, 'never'], 76 | '@stylistic/function-call-argument-newline': [2, 'consistent'], 77 | '@stylistic/function-call-spacing': [2, 'never'], 78 | '@stylistic/generator-star-spacing': [ 79 | 2, 80 | { 81 | after: true, 82 | before: false, 83 | }, 84 | ], 85 | '@stylistic/implicit-arrow-linebreak': [2, 'beside'], 86 | '@stylistic/indent': [2, 2], 87 | '@stylistic/jsx-quotes': [2, 'prefer-single'], 88 | '@stylistic/key-spacing': [ 89 | 2, 90 | { 91 | afterColon: true, 92 | beforeColon: false, 93 | }, 94 | ], 95 | '@stylistic/keyword-spacing': [ 96 | 2, 97 | { 98 | after: true, 99 | before: true, 100 | }, 101 | ], 102 | '@stylistic/line-comment-position': [ 103 | 2, 104 | { 105 | position: 'above', 106 | }, 107 | ], 108 | '@stylistic/linebreak-style': [2, 'unix'], 109 | '@stylistic/lines-around-comment': 0, 110 | '@stylistic/lines-between-class-members': [2, 'always'], 111 | '@stylistic/max-len': 0, 112 | '@stylistic/multiline-comment-style': 0, 113 | '@stylistic/multiline-ternary': 0, 114 | '@stylistic/new-parens': 2, 115 | '@stylistic/newline-per-chained-call': 0, 116 | '@stylistic/no-confusing-arrow': 2, 117 | '@stylistic/no-extra-parens': 2, 118 | '@stylistic/no-extra-semi': 2, 119 | '@stylistic/no-floating-decimal': 2, 120 | '@stylistic/no-mixed-operators': 0, 121 | '@stylistic/no-mixed-spaces-and-tabs': 2, 122 | '@stylistic/no-multi-spaces': 2, 123 | '@stylistic/no-multiple-empty-lines': [ 124 | 2, 125 | { 126 | max: 1, 127 | maxBOF: 0, 128 | maxEOF: 1, 129 | }, 130 | ], 131 | '@stylistic/no-tabs': 2, 132 | '@stylistic/no-trailing-spaces': 2, 133 | '@stylistic/no-whitespace-before-property': 2, 134 | '@stylistic/nonblock-statement-body-position': [2, 'below'], 135 | '@stylistic/object-curly-newline': [ 136 | 2, 137 | { 138 | ExportDeclaration: 'always', 139 | ImportDeclaration: 'always', 140 | ObjectExpression: { 141 | minProperties: 1, 142 | multiline: true, 143 | }, 144 | ObjectPattern: { 145 | minProperties: 1, 146 | multiline: true, 147 | }, 148 | }, 149 | ], 150 | '@stylistic/object-curly-spacing': 0, 151 | '@stylistic/object-property-newline': [ 152 | 2, 153 | { 154 | allowAllPropertiesOnSameLine: false, 155 | }, 156 | ], 157 | '@stylistic/one-var-declaration-per-line': 2, 158 | '@stylistic/operator-linebreak': [2, 'after'], 159 | '@stylistic/padded-blocks': [2, 'never'], 160 | '@stylistic/padding-line-between-statements': [ 161 | 2, 162 | { 163 | blankLine: 'always', 164 | next: '*', 165 | prev: 'multiline-block-like', 166 | }, 167 | ], 168 | '@stylistic/quote-props': [ 169 | 2, 170 | 'as-needed', 171 | { 172 | numbers: true, 173 | }, 174 | ], 175 | '@stylistic/quotes': [2, 'single'], 176 | '@stylistic/rest-spread-spacing': [2, 'never'], 177 | '@stylistic/semi': [2, 'always'], 178 | '@stylistic/semi-spacing': [ 179 | 2, 180 | { 181 | after: true, 182 | before: false, 183 | }, 184 | ], 185 | '@stylistic/space-before-blocks': [2, 'always'], 186 | '@stylistic/space-before-function-paren': [2, 'always'], 187 | '@stylistic/space-in-parens': [2, 'never'], 188 | '@stylistic/space-infix-ops': 2, 189 | '@stylistic/space-unary-ops': [ 190 | 2, 191 | { 192 | nonwords: false, 193 | words: true, 194 | }, 195 | ], 196 | '@stylistic/spaced-comment': [2, 'always'], 197 | '@stylistic/switch-colon-spacing': [ 198 | 2, 199 | { 200 | after: true, 201 | before: false, 202 | }, 203 | ], 204 | '@stylistic/template-curly-spacing': [2, 'never'], 205 | '@stylistic/template-tag-spacing': [2, 'never'], 206 | '@stylistic/wrap-iife': [2, 'inside'], 207 | '@stylistic/wrap-regex': 0, 208 | '@stylistic/yield-star-spacing': [ 209 | 2, 210 | { 211 | after: true, 212 | before: false, 213 | }, 214 | ], 215 | 'accessor-pairs': 2, 216 | 'array-callback-return': 2, 217 | 'arrow-body-style': [2, 'always'], 218 | 'block-scoped-var': 2, 219 | camelcase: 0, 220 | 'canonical/destructuring-property-newline': [ 221 | 2, 222 | { 223 | allowAllPropertiesOnSameLine: false, 224 | }, 225 | ], 226 | 'canonical/export-specifier-newline': 2, 227 | 'canonical/filename-match-exported': 2, 228 | 'canonical/filename-match-regex': [ 229 | 1, 230 | { 231 | ignoreExporting: false, 232 | // https://regex101.com/r/wTCJVg/1 233 | regex: '^[A-Za-z]+(?:[A-Za-z0-9]*\\.[A-Za-z0-9]+)*\\d*$', 234 | }, 235 | ], 236 | 'canonical/filename-no-index': 0, 237 | 'canonical/id-match': [ 238 | 2, 239 | '(^[A-Za-z]+(?:[A-Z\\d][a-z\\d]*)*\\d*$)|(^[A-Z]+(_[A-Z\\d]+)*(_\\d$)*$)|(^(_|\\$)$)', 240 | { 241 | ignoreDestructuring: true, 242 | ignoreNamedImports: true, 243 | onlyDeclarations: true, 244 | properties: true, 245 | }, 246 | ], 247 | 'canonical/import-specifier-newline': 2, 248 | 'canonical/no-import-namespace-destructure': 2, 249 | 'canonical/no-restricted-strings': 0, 250 | 'canonical/no-use-extend-native': 2, 251 | 'capitalized-comments': 0, 252 | 'class-methods-use-this': 0, 253 | complexity: [2, 30], 254 | 'consistent-return': 2, 255 | 'consistent-this': [2, 'self'], 256 | 'constructor-super': 2, 257 | curly: 2, 258 | 'default-case': 0, 259 | 'default-case-last': 0, 260 | 'default-param-last': 2, 261 | 'dot-notation': 2, 262 | eqeqeq: 2, 263 | 'eslint-comments/disable-enable-pair': [ 264 | 2, 265 | { 266 | allowWholeFile: true, 267 | }, 268 | ], 269 | 'eslint-comments/no-aggregating-enable': 2, 270 | 'eslint-comments/no-duplicate-disable': 2, 271 | 'eslint-comments/no-restricted-disable': 0, 272 | 'eslint-comments/no-unlimited-disable': 2, 273 | 'eslint-comments/no-unused-disable': 0, 274 | 'eslint-comments/no-unused-enable': 0, 275 | 'eslint-comments/no-use': 0, 276 | 'eslint-comments/require-description': 0, 277 | 'for-direction': 2, 278 | 'func-name-matching': 2, 279 | 'func-names': [2, 'never'], 280 | 'func-style': [2, 'expression'], 281 | 'function-paren-newline': [2, 'consistent'], 282 | 'getter-return': 2, 283 | 'grouped-accessor-pairs': [2, 'getBeforeSet'], 284 | 'guard-for-in': 2, 285 | 'id-denylist': 0, 286 | 'id-length': [ 287 | 2, 288 | { 289 | exceptions: ['_', '$', 'a', 'b', 'x', 'y', 'z'], 290 | min: 2, 291 | }, 292 | ], 293 | 'id-match': 0, 294 | 'import/consistent-type-specifier-style': [2, 'prefer-inline'], 295 | 'import/default': 2, 296 | 'import/dynamic-import-chunkname': 0, 297 | 'import/export': 2, 298 | 'import/exports-last': 0, 299 | 'import/extensions': 0, 300 | 'import/first': 2, 301 | 'import/group-exports': 0, 302 | 'import/max-dependencies': 0, 303 | 'import/named': 0, 304 | 'import/namespace': 0, 305 | 'import/newline-after-import': 2, 306 | 'import/no-absolute-path': 2, 307 | 'import/no-amd': 2, 308 | 'import/no-anonymous-default-export': 0, 309 | 'import/no-commonjs': 0, 310 | 'import/no-cycle': 2, 311 | 'import/no-default-export': 0, 312 | 'import/no-deprecated': 1, 313 | 'import/no-duplicates': [ 314 | 2, 315 | { 316 | considerQueryString: true, 317 | 'prefer-inline': true, 318 | }, 319 | ], 320 | 'import/no-dynamic-require': 2, 321 | 'import/no-extraneous-dependencies': [ 322 | 2, 323 | { 324 | devDependencies: true, 325 | optionalDependencies: true, 326 | peerDependencies: true, 327 | }, 328 | ], 329 | 'import/no-import-module-exports': 0, 330 | 'import/no-internal-modules': 0, 331 | 'import/no-mutable-exports': 2, 332 | 'import/no-named-as-default': 2, 333 | 'import/no-named-as-default-member': 2, 334 | 'import/no-named-default': 2, 335 | 'import/no-named-export': 0, 336 | 'import/no-namespace': 0, 337 | 'import/no-nodejs-modules': 0, 338 | 'import/no-relative-packages': 0, 339 | 'import/no-relative-parent-imports': 0, 340 | 'import/no-restricted-paths': 0, 341 | 'import/no-self-import': 2, 342 | 'import/no-unassigned-import': 2, 343 | 'import/no-unresolved': 0, 344 | 'import/no-unused-modules': 0, 345 | 'import/no-useless-path-segments': [ 346 | 2, 347 | { 348 | noUselessIndex: true, 349 | }, 350 | ], 351 | 'import/no-webpack-loader-syntax': 2, 352 | 'import/order': 0, 353 | 'import/prefer-default-export': 0, 354 | 'import/unambiguous': 0, 355 | 'init-declarations': 0, 356 | 'max-classes-per-file': 0, 357 | 'max-depth': 0, 358 | 'max-lines': 0, 359 | 'max-lines-per-function': 0, 360 | 'max-nested-callbacks': 0, 361 | 'max-params': 0, 362 | 'max-statements': 0, 363 | 'max-statements-per-line': [ 364 | 2, 365 | { 366 | max: 1, 367 | }, 368 | ], 369 | 'new-cap': 0, 370 | 'no-alert': 2, 371 | 'no-array-constructor': 2, 372 | 'no-async-promise-executor': 2, 373 | 'no-await-in-loop': 0, 374 | 'no-bitwise': 2, 375 | 'no-caller': 2, 376 | 'no-case-declarations': 2, 377 | 'no-class-assign': 2, 378 | 'no-compare-neg-zero': 2, 379 | 'no-cond-assign': 2, 380 | 'no-console': 2, 381 | 'no-const-assign': 2, 382 | 'no-constant-condition': 0, 383 | 'no-constructor-return': 2, 384 | 'no-continue': 0, 385 | 'no-control-regex': 2, 386 | 'no-debugger': 2, 387 | 'no-delete-var': 2, 388 | 'no-div-regex': 2, 389 | 'no-dupe-args': 2, 390 | 'no-dupe-class-members': 2, 391 | 'no-dupe-else-if': 2, 392 | 'no-dupe-keys': 2, 393 | 'no-duplicate-case': 2, 394 | 'no-duplicate-imports': 0, 395 | 'no-else-return': 0, 396 | 'no-empty': 2, 397 | 'no-empty-character-class': 2, 398 | 'no-empty-pattern': 2, 399 | 'no-eq-null': 2, 400 | 'no-eval': 2, 401 | 'no-ex-assign': 2, 402 | 'no-extend-native': 2, 403 | 'no-extra-bind': 2, 404 | 'no-extra-boolean-cast': 2, 405 | 'no-extra-label': 2, 406 | 407 | 'no-fallthrough': 0, 408 | 'no-func-assign': 2, 409 | 'no-global-assign': 2, 410 | 'no-implicit-coercion': 2, 411 | 'no-implicit-globals': 2, 412 | 'no-implied-eval': 2, 413 | 'no-import-assign': 2, 414 | 'no-inline-comments': 2, 415 | 'no-inner-declarations': 2, 416 | 'no-invalid-regexp': 2, 417 | 'no-invalid-this': 0, 418 | 'no-irregular-whitespace': 2, 419 | 'no-iterator': 2, 420 | 'no-label-var': 2, 421 | 'no-labels': 2, 422 | 'no-lone-blocks': 2, 423 | 'no-lonely-if': 2, 424 | 'no-loop-func': 2, 425 | 'no-loss-of-precision': 2, 426 | 'no-magic-numbers': 0, 427 | 'no-misleading-character-class': 2, 428 | 'no-multi-assign': 2, 429 | 'no-multi-str': 2, 430 | 'no-negated-condition': 2, 431 | 'no-nested-ternary': 0, 432 | 'no-new': 2, 433 | 'no-new-func': 2, 434 | 'no-new-object': 2, 435 | 'no-new-symbol': 2, 436 | 'no-new-wrappers': 2, 437 | 'no-nonoctal-decimal-escape': 2, 438 | 'no-obj-calls': 2, 439 | 'no-octal': 2, 440 | 'no-octal-escape': 2, 441 | 'no-param-reassign': [ 442 | 2, 443 | { 444 | props: false, 445 | }, 446 | ], 447 | 'no-plusplus': 0, 448 | 'no-promise-executor-return': 2, 449 | 'no-proto': 2, 450 | 'no-prototype-builtins': 2, 451 | 'no-redeclare': [ 452 | 2, 453 | { 454 | builtinGlobals: true, 455 | }, 456 | ], 457 | 'no-regex-spaces': 2, 458 | 'no-restricted-exports': 0, 459 | 'no-restricted-globals': 0, 460 | 'no-restricted-imports': 0, 461 | 'no-restricted-properties': 0, 462 | 'no-restricted-syntax': 0, 463 | 'no-return-assign': 2, 464 | 'no-script-url': 2, 465 | 'no-self-assign': 2, 466 | 'no-self-compare': 2, 467 | 'no-sequences': 2, 468 | 'no-setter-return': 2, 469 | 'no-shadow': [ 470 | 2, 471 | { 472 | builtinGlobals: false, 473 | hoist: 'all', 474 | }, 475 | ], 476 | 'no-shadow-restricted-names': 2, 477 | 'no-sparse-arrays': 2, 478 | 'no-template-curly-in-string': 2, 479 | 'no-ternary': 0, 480 | 'no-this-before-super': 2, 481 | 'no-throw-literal': 2, 482 | 'no-undef': 2, 483 | 'no-undef-init': 2, 484 | 'no-undefined': 0, 485 | 'no-underscore-dangle': 0, 486 | 'no-unexpected-multiline': 2, 487 | 'no-unmodified-loop-condition': 2, 488 | 'no-unneeded-ternary': 2, 489 | 'no-unreachable': 0, 490 | 'no-unreachable-loop': 2, 491 | 'no-unsafe-finally': 2, 492 | 'no-unsafe-negation': 2, 493 | 'no-unsafe-optional-chaining': 2, 494 | 'no-unused-expressions': 2, 495 | 'no-unused-labels': 2, 496 | 'no-unused-private-class-members': 0, 497 | 'no-unused-vars': 2, 498 | 'no-use-before-define': [ 499 | 2, 500 | { 501 | classes: true, 502 | functions: false, 503 | variables: true, 504 | }, 505 | ], 506 | 'no-useless-backreference': 2, 507 | 'no-useless-call': 2, 508 | 'no-useless-catch': 2, 509 | 'no-useless-computed-key': 2, 510 | 'no-useless-concat': 2, 511 | 'no-useless-constructor': 2, 512 | 'no-useless-escape': 2, 513 | 'no-useless-rename': [ 514 | 2, 515 | { 516 | ignoreDestructuring: false, 517 | ignoreExport: false, 518 | ignoreImport: false, 519 | }, 520 | ], 521 | 'no-useless-return': 2, 522 | 'no-var': 2, 523 | 'no-void': [ 524 | 2, 525 | { 526 | allowAsStatement: true, 527 | }, 528 | ], 529 | 'no-warning-comments': [ 530 | 1, 531 | { 532 | location: 'start', 533 | terms: ['todo', '@toto'], 534 | }, 535 | ], 536 | 'no-with': 2, 537 | 'object-shorthand': [2, 'always'], 538 | 'one-var': [2, 'never'], 539 | 'operator-assignment': [2, 'always'], 540 | 'perfectionist/sort-imports': [ 541 | 2, 542 | { 543 | groups: [], 544 | ignoreCase: true, 545 | maxLineLength: undefined, 546 | newlinesBetween: 'never', 547 | type: 'natural', 548 | }, 549 | ], 550 | 'prefer-arrow-callback': 2, 551 | 'prefer-const': 2, 552 | 'prefer-destructuring': 0, 553 | 'prefer-exponentiation-operator': 2, 554 | 'prefer-named-capture-group': 0, 555 | 'prefer-numeric-literals': 2, 556 | 'prefer-object-spread': 2, 557 | 'prefer-promise-reject-errors': 2, 558 | 'prefer-regex-literals': [ 559 | 2, 560 | { 561 | disallowRedundantWrapping: true, 562 | }, 563 | ], 564 | 'prefer-rest-params': 2, 565 | 'prefer-spread': 2, 566 | 'prefer-template': 0, 567 | 'promise/param-names': 2, 568 | 'promise/prefer-await-to-callbacks': 0, 569 | 'promise/prefer-await-to-then': 2, 570 | 'promise/valid-params': 2, 571 | radix: 2, 572 | 'require-atomic-updates': 2, 573 | 'require-await': 0, 574 | 'require-unicode-regexp': 2, 575 | 'require-yield': 2, 576 | 'semi-style': [2, 'last'], 577 | 'sort-imports': 0, 578 | 'sort-keys': 0, 579 | 'sort-vars': 2, 580 | strict: [2, 'never'], 581 | 'symbol-description': 2, 582 | 'unicode-bom': [2, 'never'], 583 | 'unicorn/better-regex': 2, 584 | 'unicorn/catch-error-name': [ 585 | 'error', 586 | { 587 | name: 'error', 588 | }, 589 | ], 590 | 'unicorn/consistent-destructuring': 0, 591 | 'unicorn/consistent-function-scoping': 2, 592 | 'unicorn/custom-error-definition': 0, 593 | 'unicorn/empty-brace-spaces': 2, 594 | 'unicorn/error-message': 2, 595 | 'unicorn/escape-case': 2, 596 | 'unicorn/expiring-todo-comments': 0, 597 | 'unicorn/explicit-length-check': 0, 598 | 'unicorn/filename-case': 0, 599 | 'unicorn/import-style': 0, 600 | 'unicorn/new-for-builtins': 2, 601 | 'unicorn/no-abusive-eslint-disable': 2, 602 | 'unicorn/no-array-callback-reference': 0, 603 | 'unicorn/no-array-for-each': 2, 604 | 'unicorn/no-array-method-this-argument': 2, 605 | 'unicorn/no-array-reduce': [ 606 | 'error', 607 | { 608 | allowSimpleOperations: true, 609 | }, 610 | ], 611 | 'unicorn/no-array-reverse': 2, 612 | 'unicorn/no-await-expression-member': 0, 613 | 'unicorn/no-console-spaces': 0, 614 | 'unicorn/no-document-cookie': 2, 615 | 'unicorn/no-empty-file': 2, 616 | 'unicorn/no-for-loop': 2, 617 | 'unicorn/no-hex-escape': 2, 618 | 'unicorn/no-instanceof-array': 2, 619 | 'unicorn/no-invalid-remove-event-listener': 2, 620 | 'unicorn/no-keyword-prefix': 0, 621 | 'unicorn/no-lonely-if': 2, 622 | 'unicorn/no-nested-ternary': 2, 623 | 'unicorn/no-new-array': 2, 624 | 'unicorn/no-new-buffer': 2, 625 | 'unicorn/no-null': 0, 626 | 'unicorn/no-object-as-default-parameter': 2, 627 | 'unicorn/no-process-exit': 0, 628 | 'unicorn/no-static-only-class': 2, 629 | 'unicorn/no-thenable': 2, 630 | 'unicorn/no-this-assignment': 2, 631 | 'unicorn/no-unnecessary-array-flat-depth': 2, 632 | 'unicorn/no-unnecessary-array-splice-count': 2, 633 | 'unicorn/no-unreadable-array-destructuring': 0, 634 | 'unicorn/no-unreadable-iife': 2, 635 | 'unicorn/no-unused-properties': 2, 636 | 'unicorn/no-useless-error-capture-stack-trace': 2, 637 | 'unicorn/no-useless-fallback-in-spread': 2, 638 | 'unicorn/no-useless-length-check': 2, 639 | 'unicorn/no-useless-promise-resolve-reject': 2, 640 | 'unicorn/no-useless-spread': 2, 641 | 'unicorn/no-useless-switch-case': 0, 642 | 'unicorn/no-useless-undefined': 0, 643 | 'unicorn/no-zero-fractions': 2, 644 | 'unicorn/number-literal-case': 2, 645 | 'unicorn/numeric-separators-style': [ 646 | 2, 647 | { 648 | number: { 649 | groupLength: 3, 650 | minimumDigits: 0, 651 | }, 652 | }, 653 | ], 654 | 'unicorn/prefer-add-event-listener': 0, 655 | 'unicorn/prefer-array-find': 2, 656 | 'unicorn/prefer-array-flat': 2, 657 | 'unicorn/prefer-array-flat-map': 2, 658 | 'unicorn/prefer-array-index-of': 2, 659 | 'unicorn/prefer-array-some': 2, 660 | 'unicorn/prefer-at': 0, 661 | 'unicorn/prefer-blob-reading-methods': 2, 662 | 'unicorn/prefer-class-fields': 2, 663 | 'unicorn/prefer-code-point': 2, 664 | 'unicorn/prefer-date-now': 2, 665 | 'unicorn/prefer-default-parameters': 2, 666 | 'unicorn/prefer-export-from': 2, 667 | 'unicorn/prefer-import-meta-properties': 2, 668 | 'unicorn/prefer-includes': 2, 669 | 'unicorn/prefer-json-parse-buffer': 2, 670 | 'unicorn/prefer-math-trunc': 2, 671 | 'unicorn/prefer-modern-math-apis': 2, 672 | 'unicorn/prefer-native-coercion-functions': 2, 673 | 'unicorn/prefer-negative-index': 0, 674 | 'unicorn/prefer-number-properties': 2, 675 | 'unicorn/prefer-object-from-entries': 2, 676 | 'unicorn/prefer-optional-catch-binding': 2, 677 | 'unicorn/prefer-prototype-methods': 0, 678 | 'unicorn/prefer-query-selector': 2, 679 | 'unicorn/prefer-reflect-apply': 2, 680 | 'unicorn/prefer-regexp-test': 2, 681 | 'unicorn/prefer-set-has': 0, 682 | 'unicorn/prefer-single-call': 0, 683 | 'unicorn/prefer-spread': 0, 684 | 'unicorn/prefer-string-replace-all': 2, 685 | 'unicorn/prefer-string-slice': 2, 686 | 'unicorn/prefer-string-starts-ends-with': 2, 687 | 'unicorn/prefer-string-trim-start-end': 2, 688 | 'unicorn/prefer-switch': 0, 689 | 'unicorn/prefer-ternary': 0, 690 | 'unicorn/prefer-top-level-await': 0, 691 | 'unicorn/prefer-type-error': 2, 692 | 'unicorn/prevent-abbreviations': [ 693 | 2, 694 | { 695 | checkProperties: false, 696 | replacements: { 697 | args: false, 698 | pkg: false, 699 | props: false, 700 | ref: false, 701 | rel: false, 702 | }, 703 | }, 704 | ], 705 | 'unicorn/relative-url-style': [2, 'never'], 706 | 'unicorn/require-array-join-separator': 2, 707 | 'unicorn/require-module-specifiers': 2, 708 | 'unicorn/require-number-to-fixed-digits-argument': 2, 709 | 'unicorn/require-post-message-target-origin': 2, 710 | 'unicorn/string-content': 0, 711 | 'unicorn/template-indent': 2, 712 | 'unicorn/text-encoding-identifier-case': 2, 713 | 'unicorn/throw-new-error': 2, 714 | 'use-isnan': 2, 715 | 'valid-typeof': [ 716 | 2, 717 | { 718 | requireStringLiterals: true, 719 | }, 720 | ], 721 | 'vars-on-top': 2, 722 | yoda: [2, 'never'], 723 | }, 724 | settings: { 725 | 'import/extensions': ['.js', '.jsx', '.mjs', '.ts', '.tsx'], 726 | }, 727 | }); 728 | -------------------------------------------------------------------------------- /configurations/graphql.ts: -------------------------------------------------------------------------------- 1 | import * as graphqlPlugin from '@graphql-eslint/eslint-plugin'; 2 | import tseslint from 'typescript-eslint'; 3 | 4 | export const recommended = tseslint.config({ 5 | files: ['**/*.graphql'], 6 | languageOptions: { 7 | parser: graphqlPlugin, 8 | }, 9 | plugins: { 10 | '@graphql-eslint': graphqlPlugin, 11 | }, 12 | rules: { 13 | '@graphql-eslint/alphabetize': [ 14 | 2, 15 | { 16 | arguments: [ 17 | 'FieldDefinition', 18 | 'Field', 19 | 'DirectiveDefinition', 20 | 'Directive', 21 | ], 22 | fields: [ 23 | 'ObjectTypeDefinition', 24 | 'InterfaceTypeDefinition', 25 | 'InputObjectTypeDefinition', 26 | ], 27 | selections: ['OperationDefinition', 'FragmentDefinition'], 28 | values: ['EnumTypeDefinition'], 29 | variables: ['OperationDefinition'], 30 | }, 31 | ], 32 | '@graphql-eslint/fields-on-correct-type': 2, 33 | '@graphql-eslint/fragments-on-composite-type': 2, 34 | '@graphql-eslint/input-name': 2, 35 | '@graphql-eslint/known-argument-names': 2, 36 | '@graphql-eslint/known-directives': 2, 37 | '@graphql-eslint/known-type-names': 2, 38 | '@graphql-eslint/lone-anonymous-operation': 2, 39 | '@graphql-eslint/lone-schema-definition': 2, 40 | '@graphql-eslint/match-document-filename': [ 41 | 2, 42 | { 43 | fileExtension: '.graphql', 44 | }, 45 | ], 46 | '@graphql-eslint/naming-convention': [ 47 | 2, 48 | { 49 | allowLeadingUnderscore: true, 50 | EnumTypeDefinition: 'PascalCase', 51 | EnumValueDefinition: 'UPPER_CASE', 52 | FieldDefinition: 'camelCase', 53 | InputObjectTypeDefinition: 'PascalCase', 54 | InputValueDefinition: 'camelCase', 55 | InterfaceTypeDefinition: 'PascalCase', 56 | ObjectTypeDefinition: 'PascalCase', 57 | ScalarTypeDefinition: 'PascalCase', 58 | UnionTypeDefinition: 'PascalCase', 59 | }, 60 | ], 61 | '@graphql-eslint/no-anonymous-operations': 2, 62 | '@graphql-eslint/no-case-insensitive-enum-values-duplicates': 2, 63 | '@graphql-eslint/no-deprecated': 2, 64 | '@graphql-eslint/no-duplicate-fields': 2, 65 | '@graphql-eslint/no-fragment-cycles': 2, 66 | '@graphql-eslint/no-hashtag-description': 2, 67 | '@graphql-eslint/no-scalar-result-type-on-mutation': 2, 68 | '@graphql-eslint/no-typename-prefix': 2, 69 | '@graphql-eslint/no-unreachable-types': 2, 70 | '@graphql-eslint/one-field-subscriptions': 2, 71 | '@graphql-eslint/overlapping-fields-can-be-merged': 2, 72 | '@graphql-eslint/possible-fragment-spread': 2, 73 | '@graphql-eslint/possible-type-extension': 2, 74 | '@graphql-eslint/provided-required-arguments': 2, 75 | '@graphql-eslint/require-deprecation-reason': 2, 76 | '@graphql-eslint/scalar-leafs': 2, 77 | '@graphql-eslint/unique-argument-names': 2, 78 | '@graphql-eslint/unique-directive-names': 2, 79 | '@graphql-eslint/unique-directive-names-per-location': 2, 80 | '@graphql-eslint/unique-enum-value-names': 2, 81 | '@graphql-eslint/unique-field-definition-names': 2, 82 | '@graphql-eslint/unique-input-field-names': 2, 83 | '@graphql-eslint/unique-operation-types': 2, 84 | '@graphql-eslint/unique-type-names': 2, 85 | '@graphql-eslint/unique-variable-names': 2, 86 | '@graphql-eslint/value-literals-of-correct-type': 2, 87 | '@graphql-eslint/variables-are-input-types': 2, 88 | '@graphql-eslint/variables-in-allowed-position': 2, 89 | }, 90 | }); 91 | -------------------------------------------------------------------------------- /configurations/index.ts: -------------------------------------------------------------------------------- 1 | export * as ava from './ava.js'; 2 | export * as browser from './browser.js'; 3 | export * as canonical from './canonical.js'; 4 | export * as graphql from './graphql.js'; 5 | export * as jest from './jest.js'; 6 | export * as jsdoc from './jsdoc.js'; 7 | export * as json from './json.js'; 8 | export * as jsxA11y from './jsx-a11y.js'; 9 | export * as lodash from './lodash.js'; 10 | export * as mocha from './mocha.js'; 11 | export * as moduleRules from './module.js'; 12 | export * as next from './next.js'; 13 | export * as node from './node.js'; 14 | export * as prettier from './prettier.js'; 15 | export * as react from './react.js'; 16 | export * as regexp from './regexp.js'; 17 | export * as typescript from './typescript.js'; 18 | export * as vitest from './vitest.js'; 19 | export * as yaml from './yaml.js'; 20 | export * as zod from './zod.js'; 21 | -------------------------------------------------------------------------------- /configurations/jest.ts: -------------------------------------------------------------------------------- 1 | import jestPlugin from 'eslint-plugin-jest'; 2 | import tseslint from 'typescript-eslint'; 3 | 4 | export const recommended = tseslint.config({ 5 | languageOptions: { 6 | globals: jestPlugin.environments.globals.globals, 7 | }, 8 | plugins: { 9 | jest: jestPlugin, 10 | }, 11 | rules: { 12 | 'jest/consistent-test-it': 2, 13 | 'jest/expect-expect': 2, 14 | 'jest/max-nested-describe': [ 15 | 2, 16 | { 17 | max: 5, 18 | }, 19 | ], 20 | 'jest/no-alias-methods': 2, 21 | 'jest/no-commented-out-tests': 2, 22 | 'jest/no-conditional-expect': 2, 23 | 'jest/no-conditional-in-test': 2, 24 | 'jest/no-deprecated-functions': 2, 25 | 'jest/no-disabled-tests': 1, 26 | 'jest/no-done-callback': 2, 27 | 'jest/no-duplicate-hooks': 2, 28 | 'jest/no-export': 2, 29 | 'jest/no-focused-tests': 2, 30 | 'jest/no-hooks': 0, 31 | 'jest/no-identical-title': 2, 32 | 'jest/no-interpolation-in-snapshots': 2, 33 | 'jest/no-jasmine-globals': 2, 34 | 'jest/no-large-snapshots': 0, 35 | 'jest/no-mocks-import': 2, 36 | 'jest/no-restricted-matchers': 0, 37 | 'jest/no-standalone-expect': 2, 38 | 'jest/no-test-prefixes': 2, 39 | 'jest/no-test-return-statement': 2, 40 | 'jest/prefer-called-with': 2, 41 | 'jest/prefer-comparison-matcher': 2, 42 | 'jest/prefer-expect-assertions': [ 43 | 2, 44 | { 45 | onlyFunctionsWithAsyncKeyword: true, 46 | }, 47 | ], 48 | 'jest/prefer-expect-resolves': 2, 49 | 'jest/prefer-hooks-on-top': 2, 50 | 'jest/prefer-lowercase-title': 0, 51 | 'jest/prefer-snapshot-hint': 2, 52 | 'jest/prefer-spy-on': 2, 53 | 'jest/prefer-strict-equal': 0, 54 | 'jest/prefer-to-be': 2, 55 | 'jest/prefer-to-contain': 2, 56 | 'jest/prefer-to-have-length': 2, 57 | 'jest/prefer-todo': 2, 58 | 'jest/require-hook': 0, 59 | 'jest/require-to-throw-message': 2, 60 | 'jest/require-top-level-describe': 2, 61 | 'jest/unbound-method': 0, 62 | 'jest/valid-describe-callback': 2, 63 | 'jest/valid-expect': 2, 64 | 'jest/valid-expect-in-promise': 2, 65 | 'jest/valid-title': 2, 66 | }, 67 | }); 68 | -------------------------------------------------------------------------------- /configurations/jsdoc.ts: -------------------------------------------------------------------------------- 1 | import jsdocPlugin from 'eslint-plugin-jsdoc'; 2 | import tseslint from 'typescript-eslint'; 3 | 4 | export const recommended = tseslint.config({ 5 | files: ['**/*.{js,jsx,cjs,mjs,ts,tsx}'], 6 | plugins: { 7 | jsdoc: jsdocPlugin, 8 | }, 9 | rules: { 10 | 'jsdoc/check-access': 2, 11 | 'jsdoc/check-alignment': 2, 12 | 'jsdoc/check-examples': 0, 13 | 'jsdoc/check-indentation': 0, 14 | 'jsdoc/check-line-alignment': 0, 15 | 'jsdoc/check-param-names': 2, 16 | 'jsdoc/check-property-names': 2, 17 | 'jsdoc/check-syntax': 2, 18 | 'jsdoc/check-tag-names': [ 19 | 2, 20 | { 21 | definedTags: ['jest-environment', 'jest-environment-options'], 22 | }, 23 | ], 24 | 'jsdoc/check-types': 2, 25 | 'jsdoc/check-values': 2, 26 | 'jsdoc/empty-tags': 2, 27 | 'jsdoc/implements-on-classes': 2, 28 | 'jsdoc/match-description': 0, 29 | 'jsdoc/match-name': 0, 30 | 'jsdoc/multiline-blocks': [ 31 | 2, 32 | { 33 | noMultilineBlocks: false, 34 | noSingleLineBlocks: true, 35 | }, 36 | ], 37 | 'jsdoc/no-bad-blocks': 2, 38 | 'jsdoc/no-defaults': 2, 39 | 'jsdoc/no-missing-syntax': 0, 40 | 'jsdoc/no-multi-asterisks': 2, 41 | 'jsdoc/no-restricted-syntax': 0, 42 | 'jsdoc/no-types': 0, 43 | 'jsdoc/no-undefined-types': 2, 44 | 'jsdoc/require-asterisk-prefix': 2, 45 | 'jsdoc/require-description': 0, 46 | 'jsdoc/require-description-complete-sentence': 0, 47 | 'jsdoc/require-example': 0, 48 | 'jsdoc/require-file-overview': 0, 49 | 'jsdoc/require-hyphen-before-param-description': 0, 50 | 'jsdoc/require-jsdoc': 0, 51 | 'jsdoc/require-param': 0, 52 | 'jsdoc/require-param-description': 0, 53 | 'jsdoc/require-param-name': 2, 54 | 'jsdoc/require-param-type': 0, 55 | 'jsdoc/require-property': 2, 56 | 'jsdoc/require-property-description': 2, 57 | 'jsdoc/require-property-name': 2, 58 | 'jsdoc/require-property-type': 2, 59 | 'jsdoc/require-returns': 0, 60 | 'jsdoc/require-returns-check': 0, 61 | 'jsdoc/require-returns-description': 0, 62 | 'jsdoc/require-returns-type': 0, 63 | 'jsdoc/require-throws': 0, 64 | 'jsdoc/require-yields': 0, 65 | 'jsdoc/require-yields-check': 0, 66 | 'jsdoc/tag-lines': [2, 'never'], 67 | 'jsdoc/valid-types': 2, 68 | }, 69 | }); 70 | -------------------------------------------------------------------------------- /configurations/json.ts: -------------------------------------------------------------------------------- 1 | import jsoncPlugin from 'eslint-plugin-jsonc'; 2 | import tseslint from 'typescript-eslint'; 3 | 4 | export const recommended = tseslint.config({ 5 | files: ['**/*.json'], 6 | languageOptions: { 7 | parser: jsoncPlugin, 8 | }, 9 | plugins: { 10 | jsonc: jsoncPlugin, 11 | }, 12 | rules: { 13 | 'jsonc/array-bracket-newline': [ 14 | 2, 15 | { 16 | minItems: 1, 17 | multiline: true, 18 | }, 19 | ], 20 | 'jsonc/array-bracket-spacing': 2, 21 | 'jsonc/array-element-newline': [2, 'always'], 22 | 'jsonc/auto': 0, 23 | 'jsonc/comma-dangle': 2, 24 | 'jsonc/comma-style': [2, 'last'], 25 | 'jsonc/indent': [2, 2], 26 | 'jsonc/key-name-casing': 0, 27 | 'jsonc/key-spacing': 2, 28 | 'jsonc/no-bigint-literals': 2, 29 | 'jsonc/no-binary-expression': 2, 30 | 'jsonc/no-binary-numeric-literals': 2, 31 | 'jsonc/no-comments': 2, 32 | 'jsonc/no-dupe-keys': 2, 33 | 'jsonc/no-escape-sequence-in-identifier': 2, 34 | 'jsonc/no-floating-decimal': 2, 35 | 'jsonc/no-hexadecimal-numeric-literals': 2, 36 | 'jsonc/no-infinity': 2, 37 | 'jsonc/no-multi-str': 2, 38 | 'jsonc/no-nan': 2, 39 | 'jsonc/no-number-props': 2, 40 | 'jsonc/no-numeric-separators': 2, 41 | 'jsonc/no-octal': 2, 42 | 'jsonc/no-octal-escape': 2, 43 | 'jsonc/no-octal-numeric-literals': 2, 44 | 'jsonc/no-parenthesized': 2, 45 | 'jsonc/no-plus-sign': 2, 46 | 'jsonc/no-regexp-literals': 2, 47 | 'jsonc/no-sparse-arrays': 2, 48 | 'jsonc/no-template-literals': 2, 49 | 'jsonc/no-undefined-value': 2, 50 | 'jsonc/no-unicode-codepoint-escapes': 2, 51 | 'jsonc/no-useless-escape': 2, 52 | 'jsonc/object-curly-newline': 2, 53 | 'jsonc/object-curly-spacing': 2, 54 | 'jsonc/object-property-newline': 2, 55 | 'jsonc/quote-props': 2, 56 | 'jsonc/quotes': 2, 57 | 'jsonc/sort-array-values': 0, 58 | 'jsonc/sort-keys': [ 59 | 2, 60 | 'asc', 61 | { 62 | caseSensitive: false, 63 | natural: true, 64 | }, 65 | ], 66 | 'jsonc/space-unary-ops': 2, 67 | 'jsonc/valid-json-number': 2, 68 | 'jsonc/vue-custom-block/no-parsing-error': 2, 69 | }, 70 | }); 71 | -------------------------------------------------------------------------------- /configurations/jsx-a11y.ts: -------------------------------------------------------------------------------- 1 | import jsxA11yPlugin from 'eslint-plugin-jsx-a11y'; 2 | import tseslint from 'typescript-eslint'; 3 | 4 | export const recommended = tseslint.config({ 5 | plugins: { 6 | 'jsx-a11y': jsxA11yPlugin, 7 | }, 8 | rules: { 9 | 'jsx-a11y/alt-text': 2, 10 | 'jsx-a11y/anchor-has-content': 2, 11 | 'jsx-a11y/anchor-is-valid': 2, 12 | 'jsx-a11y/aria-activedescendant-has-tabindex': 2, 13 | 'jsx-a11y/aria-props': 2, 14 | 'jsx-a11y/aria-proptypes': 2, 15 | 'jsx-a11y/aria-role': 2, 16 | 'jsx-a11y/aria-unsupported-elements': 2, 17 | 'jsx-a11y/autocomplete-valid': 2, 18 | 'jsx-a11y/click-events-have-key-events': 2, 19 | 'jsx-a11y/control-has-associated-label': 0, 20 | 'jsx-a11y/heading-has-content': 2, 21 | 'jsx-a11y/html-has-lang': 2, 22 | 'jsx-a11y/iframe-has-title': 2, 23 | 'jsx-a11y/img-redundant-alt': 2, 24 | 'jsx-a11y/interactive-supports-focus': [ 25 | 2, 26 | { 27 | tabbable: [ 28 | 'button', 29 | 'checkbox', 30 | 'link', 31 | 'searchbox', 32 | 'spinbutton', 33 | 'switch', 34 | 'textbox', 35 | ], 36 | }, 37 | ], 38 | 'jsx-a11y/label-has-associated-control': 2, 39 | 'jsx-a11y/lang': 0, 40 | 'jsx-a11y/media-has-caption': 2, 41 | 'jsx-a11y/mouse-events-have-key-events': 2, 42 | 'jsx-a11y/no-access-key': 2, 43 | 'jsx-a11y/no-autofocus': 0, 44 | 'jsx-a11y/no-distracting-elements': 2, 45 | 'jsx-a11y/no-interactive-element-to-noninteractive-role': [ 46 | 2, 47 | { 48 | canvas: ['img'], 49 | tr: ['none', 'presentation'], 50 | }, 51 | ], 52 | 'jsx-a11y/no-noninteractive-element-interactions': [ 53 | 2, 54 | { 55 | alert: ['onKeyUp', 'onKeyDown', 'onKeyPress'], 56 | body: ['onError', 'onLoad'], 57 | dialog: ['onKeyUp', 'onKeyDown', 'onKeyPress'], 58 | handlers: [ 59 | 'onClick', 60 | 'onError', 61 | 'onLoad', 62 | 'onMouseDown', 63 | 'onMouseUp', 64 | 'onKeyPress', 65 | 'onKeyDown', 66 | 'onKeyUp', 67 | ], 68 | iframe: ['onError', 'onLoad'], 69 | img: ['onError', 'onLoad'], 70 | }, 71 | ], 72 | 'jsx-a11y/no-noninteractive-element-to-interactive-role': [ 73 | 2, 74 | { 75 | fieldset: ['radiogroup', 'presentation'], 76 | li: ['menuitem', 'option', 'row', 'tab', 'treeitem'], 77 | ol: [ 78 | 'listbox', 79 | 'menu', 80 | 'menubar', 81 | 'radiogroup', 82 | 'tablist', 83 | 'tree', 84 | 'treegrid', 85 | ], 86 | table: ['grid'], 87 | td: ['gridcell'], 88 | ul: [ 89 | 'listbox', 90 | 'menu', 91 | 'menubar', 92 | 'radiogroup', 93 | 'tablist', 94 | 'tree', 95 | 'treegrid', 96 | ], 97 | }, 98 | ], 99 | 'jsx-a11y/no-noninteractive-tabindex': [ 100 | 2, 101 | { 102 | allowExpressionValues: true, 103 | roles: ['tabpanel'], 104 | tags: [], 105 | }, 106 | ], 107 | 'jsx-a11y/no-redundant-roles': 2, 108 | 'jsx-a11y/no-static-element-interactions': [ 109 | 2, 110 | { 111 | allowExpressionValues: true, 112 | handlers: [ 113 | 'onClick', 114 | 'onMouseDown', 115 | 'onMouseUp', 116 | 'onKeyPress', 117 | 'onKeyDown', 118 | 'onKeyUp', 119 | ], 120 | }, 121 | ], 122 | 'jsx-a11y/role-has-required-aria-props': 2, 123 | 'jsx-a11y/role-supports-aria-props': 2, 124 | 'jsx-a11y/scope': 2, 125 | 'jsx-a11y/tabindex-no-positive': 2, 126 | }, 127 | }); 128 | -------------------------------------------------------------------------------- /configurations/lodash.ts: -------------------------------------------------------------------------------- 1 | import lodashPlugin from 'eslint-plugin-lodash'; 2 | import tseslint from 'typescript-eslint'; 3 | 4 | export const recommended = tseslint.config({ 5 | plugins: { 6 | lodash: lodashPlugin, 7 | }, 8 | rules: { 9 | 'lodash/callback-binding': 2, 10 | 'lodash/chain-style': [2, 'explicit'], 11 | 'lodash/chaining': [2, 'always'], 12 | 'lodash/collection-method-value': 2, 13 | 'lodash/collection-ordering': 2, 14 | 'lodash/collection-return': 2, 15 | 'lodash/consistent-compose': [2, 'flow'], 16 | 'lodash/identity-shorthand': [2, 'always'], 17 | 'lodash/import-scope': 0, 18 | 'lodash/matches-prop-shorthand': 2, 19 | 'lodash/matches-shorthand': [2, 'always', 3], 20 | 'lodash/no-commit': 2, 21 | 'lodash/no-double-unwrap': 2, 22 | 'lodash/no-extra-args': 2, 23 | 'lodash/no-unbound-this': 2, 24 | 'lodash/path-style': 0, 25 | 'lodash/prefer-compact': 2, 26 | 'lodash/prefer-constant': 0, 27 | 'lodash/prefer-filter': [2, 3], 28 | 'lodash/prefer-find': 2, 29 | 'lodash/prefer-flat-map': 2, 30 | 'lodash/prefer-get': [2, 3], 31 | 'lodash/prefer-immutable-method': 2, 32 | 'lodash/prefer-includes': 2, 33 | 'lodash/prefer-invoke-map': 0, 34 | 'lodash/prefer-is-nil': 2, 35 | 'lodash/prefer-lodash-chain': 2, 36 | 'lodash/prefer-lodash-method': 0, 37 | 'lodash/prefer-lodash-typecheck': 2, 38 | 'lodash/prefer-map': 2, 39 | 'lodash/prefer-matches': 2, 40 | 'lodash/prefer-noop': 0, 41 | 'lodash/prefer-over-quantifier': 2, 42 | 'lodash/prefer-reject': [1, 3], 43 | 'lodash/prefer-some': [ 44 | 2, 45 | { 46 | includeNative: false, 47 | }, 48 | ], 49 | 'lodash/prefer-startswith': 0, 50 | 'lodash/prefer-thru': 2, 51 | 'lodash/prefer-times': 2, 52 | 'lodash/prefer-wrapper-method': 2, 53 | 'lodash/preferred-alias': 2, 54 | 'lodash/prop-shorthand': 2, 55 | 'lodash/unwrap': 2, 56 | }, 57 | }); 58 | -------------------------------------------------------------------------------- /configurations/mocha.ts: -------------------------------------------------------------------------------- 1 | import mochaPlugin from 'eslint-plugin-mocha'; 2 | import globals from 'globals'; 3 | import tseslint from 'typescript-eslint'; 4 | 5 | export const recommended = tseslint.config({ 6 | languageOptions: { 7 | globals: globals.mocha, 8 | }, 9 | plugins: { 10 | mocha: mochaPlugin, 11 | }, 12 | rules: { 13 | 'mocha/handle-done-callback': 2, 14 | 'mocha/max-top-level-suites': [ 15 | 2, 16 | { 17 | limit: 1, 18 | }, 19 | ], 20 | 'mocha/no-async-describe': 2, 21 | 'mocha/no-empty-description': 2, 22 | 'mocha/no-exclusive-tests': 2, 23 | 'mocha/no-exports': 2, 24 | 'mocha/no-global-tests': 2, 25 | 'mocha/no-hooks': 0, 26 | 'mocha/no-hooks-for-single-case': 2, 27 | 'mocha/no-identical-title': 2, 28 | 'mocha/no-mocha-arrows': 0, 29 | 'mocha/no-nested-tests': 2, 30 | 'mocha/no-pending-tests': 2, 31 | 'mocha/no-return-and-callback': 2, 32 | 'mocha/no-return-from-async': 2, 33 | 'mocha/no-setup-in-describe': 2, 34 | 'mocha/no-sibling-hooks': 2, 35 | 'mocha/no-skipped-tests': 2, 36 | 'mocha/no-synchronous-tests': 0, 37 | 'mocha/no-top-level-hooks': 2, 38 | 'mocha/prefer-arrow-callback': 0, 39 | 'mocha/valid-suite-description': 0, 40 | 'mocha/valid-test-description': 0, 41 | }, 42 | }); 43 | -------------------------------------------------------------------------------- /configurations/module.ts: -------------------------------------------------------------------------------- 1 | import unicornPlugin from 'eslint-plugin-unicorn'; 2 | import tseslint from 'typescript-eslint'; 3 | 4 | export const recommended = tseslint.config({ 5 | plugins: { 6 | unicorn: unicornPlugin, 7 | }, 8 | rules: { 9 | 'unicorn/prefer-module': 2, 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /configurations/next.ts: -------------------------------------------------------------------------------- 1 | import nextPlugin from '@next/eslint-plugin-next'; 2 | import tseslint from 'typescript-eslint'; 3 | 4 | export const recommended = tseslint.config({ 5 | plugins: { 6 | next: nextPlugin, 7 | }, 8 | rules: { 9 | 'next/google-font-display': 1, 10 | 'next/google-font-preconnect': 1, 11 | 'next/inline-script-id': 2, 12 | 'next/link-passhref': 1, 13 | 'next/next-script-for-ga': 1, 14 | 'next/no-css-tags': 1, 15 | 'next/no-document-import-in-page': 2, 16 | 'next/no-duplicate-head': 2, 17 | 'next/no-head-import-in-document': 2, 18 | 'next/no-html-link-for-pages': 1, 19 | 'next/no-img-element': 1, 20 | 'next/no-page-custom-font': 1, 21 | 'next/no-script-in-document': 2, 22 | 'next/no-script-in-head': 2, 23 | 'next/no-server-import-in-page': 2, 24 | 'next/no-sync-scripts': 1, 25 | 'next/no-title-in-document-head': 1, 26 | 'next/no-typos': 1, 27 | 'next/no-unwanted-polyfillio': 1, 28 | }, 29 | }); 30 | -------------------------------------------------------------------------------- /configurations/node.ts: -------------------------------------------------------------------------------- 1 | import importPlugin from 'eslint-plugin-import'; 2 | import nPlugin from 'eslint-plugin-n'; 3 | import unicornPlugin from 'eslint-plugin-unicorn'; 4 | import globals from 'globals'; 5 | import tseslint from 'typescript-eslint'; 6 | 7 | export const recommended = tseslint.config({ 8 | languageOptions: { 9 | globals: { 10 | ...globals.node, 11 | }, 12 | }, 13 | plugins: { 14 | import: importPlugin, 15 | // eslint-disable-next-line id-length 16 | n: nPlugin, 17 | unicorn: unicornPlugin, 18 | }, 19 | rules: { 20 | 'n/callback-return': 2, 21 | 'n/exports-style': [2, 'module.exports'], 22 | 'n/file-extension-in-import': 0, 23 | 'n/global-require': 2, 24 | 'n/handle-callback-err': 2, 25 | 'n/no-callback-literal': 2, 26 | 'n/no-deprecated-api': 2, 27 | 'n/no-exports-assign': 2, 28 | 'n/no-extraneous-import': 2, 29 | 'n/no-extraneous-require': 2, 30 | 'n/no-missing-import': 0, 31 | 'n/no-missing-require': 0, 32 | 'n/no-mixed-requires': 0, 33 | 'n/no-new-require': 2, 34 | 'n/no-path-concat': 2, 35 | 'n/no-process-env': 2, 36 | 'n/no-process-exit': 2, 37 | 'n/no-restricted-import': 0, 38 | 'n/no-restricted-require': 0, 39 | 'n/no-sync': 0, 40 | 'n/no-unpublished-bin': 2, 41 | 'n/no-unpublished-import': 0, 42 | 'n/no-unpublished-require': 0, 43 | 'n/no-unsupported-features/es-builtins': 0, 44 | 'n/no-unsupported-features/es-syntax': 0, 45 | 'n/no-unsupported-features/node-builtins': 0, 46 | 'n/prefer-global/buffer': 2, 47 | 'n/prefer-global/console': 2, 48 | 'n/prefer-global/process': 2, 49 | 'n/prefer-global/text-decoder': 2, 50 | 'n/prefer-global/text-encoder': 2, 51 | 'n/prefer-global/url': 2, 52 | 'n/prefer-global/url-search-params': 2, 53 | 'n/prefer-promises/dns': 2, 54 | 'n/prefer-promises/fs': 2, 55 | 'n/process-exit-as-throw': 2, 56 | 'n/shebang': [ 57 | 2, 58 | { 59 | convertPath: { 60 | 'src/**/*.js': ['^src/(.+?)\\.js$', 'dist/$1.js'], 61 | }, 62 | }, 63 | ], 64 | 'unicorn/prefer-node-protocol': 2, 65 | }, 66 | }); 67 | -------------------------------------------------------------------------------- /configurations/prettier.ts: -------------------------------------------------------------------------------- 1 | import prettierPlugin from 'eslint-plugin-prettier'; 2 | import tseslint from 'typescript-eslint'; 3 | 4 | export const recommended = tseslint.config({ 5 | files: ['**/*.{js,jsx,cjs,mjs,ts,tsx}'], 6 | plugins: { 7 | prettier: prettierPlugin, 8 | }, 9 | rules: { 10 | '@stylistic/array-bracket-newline': 0, 11 | '@stylistic/array-element-newline': 0, 12 | '@stylistic/brace-style': 0, 13 | '@stylistic/comma-dangle': 0, 14 | '@stylistic/generator-star-spacing': 0, 15 | '@stylistic/implicit-arrow-linebreak': 0, 16 | '@stylistic/indent': 0, 17 | '@stylistic/jsx-quotes': 0, 18 | '@stylistic/line-comment-position': 0, 19 | '@stylistic/linebreak-style': 0, 20 | '@stylistic/member-delimiter-style': 0, 21 | '@stylistic/no-confusing-arrow': 0, 22 | '@stylistic/no-extra-parens': 0, 23 | '@stylistic/no-trailing-spaces': 0, 24 | '@stylistic/nonblock-statement-body-position': 0, 25 | '@stylistic/object-curly-newline': 0, 26 | '@stylistic/object-property-newline': 0, 27 | '@stylistic/operator-linebreak': 0, 28 | '@stylistic/quotes': 0, 29 | '@stylistic/space-before-function-paren': 0, 30 | '@stylistic/space-in-parens': 0, 31 | '@typescript-eslint/indent': 0, 32 | 'arrow-body-style': 0, 33 | 'canonical/destructuring-property-newline': 0, 34 | 'canonical/export-specifier-newline': 0, 35 | 'canonical/import-specifier-newline': 0, 36 | 'function-paren-newline': 0, 37 | 'no-inline-comments': 0, 38 | 'prefer-arrow-callback': 0, 39 | 'prettier/prettier': [ 40 | 2, 41 | { 42 | arrowParens: 'always', 43 | bracketSameLine: false, 44 | bracketSpacing: true, 45 | endOfLine: 'lf', 46 | printWidth: 80, 47 | proseWrap: 'preserve', 48 | quoteProps: 'as-needed', 49 | semi: true, 50 | singleAttributePerLine: true, 51 | singleQuote: true, 52 | tabWidth: 2, 53 | trailingComma: 'all', 54 | useTabs: false, 55 | }, 56 | { 57 | usePrettierrc: false, 58 | }, 59 | ], 60 | 'react/jsx-curly-newline': 0, 61 | 'react/jsx-indent': 0, 62 | 'react/jsx-indent-props': 0, 63 | 'react/jsx-newline': 0, 64 | 'unicorn/no-nested-ternary': 0, 65 | 'unicorn/template-indent': 0, 66 | }, 67 | }); 68 | -------------------------------------------------------------------------------- /configurations/react.ts: -------------------------------------------------------------------------------- 1 | import canonicalPlugin from 'eslint-plugin-canonical'; 2 | import reactPlugin from 'eslint-plugin-react'; 3 | import reactHooksPlugin from 'eslint-plugin-react-hooks'; 4 | import globals from 'globals'; 5 | import tseslint from 'typescript-eslint'; 6 | 7 | export const recommended = tseslint.config({ 8 | files: ['**/*.{jsx,tsx}'], 9 | languageOptions: { 10 | globals: { 11 | ...globals.browser, 12 | }, 13 | parserOptions: { 14 | ecmaFeatures: { 15 | jsx: true, 16 | }, 17 | }, 18 | }, 19 | plugins: { 20 | canonical: canonicalPlugin, 21 | react: reactPlugin, 22 | 'react-hooks': reactHooksPlugin, 23 | }, 24 | rules: { 25 | 'canonical/sort-react-dependencies': 2, 26 | 'react-hooks/exhaustive-deps': 2, 27 | 'react-hooks/rules-of-hooks': 2, 28 | 'react/boolean-prop-naming': 0, 29 | 'react/button-has-type': 2, 30 | 'react/default-props-match-prop-types': 2, 31 | 'react/destructuring-assignment': 0, 32 | 'react/display-name': 0, 33 | 'react/forbid-component-props': 2, 34 | 'react/forbid-dom-props': 0, 35 | 'react/forbid-elements': 0, 36 | 'react/forbid-foreign-prop-types': 0, 37 | 'react/forbid-prop-types': 0, 38 | 'react/function-component-definition': [ 39 | 2, 40 | { 41 | namedComponents: 'arrow-function', 42 | unnamedComponents: 'arrow-function', 43 | }, 44 | ], 45 | 'react/hook-use-state': 2, 46 | 'react/iframe-missing-sandbox': 2, 47 | 'react/jsx-boolean-value': [2, 'never'], 48 | 'react/jsx-child-element-spacing': 0, 49 | 'react/jsx-closing-bracket-location': [2, 'line-aligned'], 50 | 'react/jsx-closing-tag-location': 0, 51 | 'react/jsx-curly-brace-presence': [ 52 | 2, 53 | { 54 | children: 'never', 55 | props: 'never', 56 | }, 57 | ], 58 | 'react/jsx-curly-newline': 2, 59 | 'react/jsx-curly-spacing': [ 60 | 2, 61 | 'never', 62 | { 63 | allowMultiline: true, 64 | }, 65 | ], 66 | 'react/jsx-equals-spacing': [2, 'never'], 67 | 'react/jsx-filename-extension': 0, 68 | 'react/jsx-first-prop-new-line': [2, 'multiline-multiprop'], 69 | 'react/jsx-fragments': [2, 'syntax'], 70 | 'react/jsx-handler-names': [ 71 | 2, 72 | { 73 | checkInlineFunction: false, 74 | checkLocalVariables: false, 75 | eventHandlerPrefix: 'handle', 76 | eventHandlerPropPrefix: 'on', 77 | }, 78 | ], 79 | 'react/jsx-indent': [2, 2], 80 | 'react/jsx-indent-props': [2, 2], 81 | 'react/jsx-key': [ 82 | 2, 83 | { 84 | checkFragmentShorthand: true, 85 | checkKeyMustBeforeSpread: true, 86 | }, 87 | ], 88 | 'react/jsx-max-depth': 0, 89 | 'react/jsx-max-props-per-line': [ 90 | 2, 91 | { 92 | maximum: 3, 93 | when: 'multiline', 94 | }, 95 | ], 96 | 'react/jsx-newline': [ 97 | 2, 98 | { 99 | prevent: true, 100 | }, 101 | ], 102 | 'react/jsx-no-bind': [ 103 | 2, 104 | { 105 | allowArrowFunctions: true, 106 | allowBind: false, 107 | ignoreRefs: true, 108 | }, 109 | ], 110 | 'react/jsx-no-comment-textnodes': 2, 111 | 'react/jsx-no-constructed-context-values': 2, 112 | 'react/jsx-no-duplicate-props': 2, 113 | 'react/jsx-no-literals': 0, 114 | 'react/jsx-no-script-url': 2, 115 | 'react/jsx-no-target-blank': 2, 116 | 'react/jsx-no-undef': 2, 117 | 'react/jsx-no-useless-fragment': [ 118 | 2, 119 | { 120 | allowExpressions: true, 121 | }, 122 | ], 123 | 'react/jsx-one-expression-per-line': 0, 124 | 'react/jsx-pascal-case': [ 125 | 2, 126 | { 127 | ignore: [ 128 | 'h{}', 129 | 'h2', 130 | 'h3', 131 | 'h4', 132 | 'h5', 133 | 'h6', 134 | 'p', 135 | 'a', 136 | 'ul', 137 | 'ol', 138 | 'li', 139 | 'img', 140 | 'div', 141 | 'span', 142 | 'dl', 143 | 'dt', 144 | 'dd', 145 | ], 146 | }, 147 | ], 148 | 'react/jsx-props-no-multi-spaces': 2, 149 | 'react/jsx-props-no-spreading': 0, 150 | 'react/jsx-sort-props': 0, 151 | 'react/jsx-tag-spacing': [ 152 | 2, 153 | { 154 | afterOpening: 'never', 155 | beforeSelfClosing: 'always', 156 | closingSlash: 'never', 157 | }, 158 | ], 159 | 'react/jsx-uses-react': 2, 160 | 'react/jsx-uses-vars': 2, 161 | 'react/jsx-wrap-multilines': 0, 162 | 'react/no-access-state-in-setstate': 2, 163 | 'react/no-adjacent-inline-elements': 0, 164 | 'react/no-array-index-key': 2, 165 | 'react/no-arrow-function-lifecycle': 2, 166 | 'react/no-children-prop': 2, 167 | 'react/no-danger': 2, 168 | 'react/no-danger-with-children': 2, 169 | 'react/no-deprecated': 2, 170 | 'react/no-did-mount-set-state': 2, 171 | 'react/no-did-update-set-state': 2, 172 | 'react/no-direct-mutation-state': 2, 173 | 'react/no-find-dom-node': 2, 174 | 'react/no-invalid-html-attribute': 2, 175 | 'react/no-is-mounted': 2, 176 | 'react/no-multi-comp': 0, 177 | 'react/no-namespace': 2, 178 | 'react/no-redundant-should-component-update': 2, 179 | 'react/no-render-return-value': 2, 180 | 'react/no-set-state': 2, 181 | 'react/no-string-refs': 2, 182 | 'react/no-this-in-sfc': 2, 183 | 'react/no-typos': 2, 184 | 'react/no-unescaped-entities': 0, 185 | 'react/no-unknown-property': 2, 186 | 'react/no-unsafe': 2, 187 | 'react/no-unstable-nested-components': 2, 188 | 'react/no-unused-class-component-methods': 2, 189 | 'react/no-unused-prop-types': 2, 190 | 'react/no-unused-state': 2, 191 | 'react/no-will-update-set-state': 2, 192 | 'react/prefer-es6-class': 2, 193 | 'react/prefer-exact-props': 0, 194 | 'react/prefer-read-only-props': 2, 195 | 'react/prefer-stateless-function': [ 196 | 2, 197 | { 198 | ignorePureComponents: true, 199 | }, 200 | ], 201 | 'react/prop-types': 2, 202 | 'react/react-in-jsx-scope': 0, 203 | 'react/require-default-props': 0, 204 | 'react/require-optimization': 0, 205 | 'react/require-render-return': 2, 206 | 'react/self-closing-comp': 2, 207 | 'react/sort-comp': 0, 208 | 'react/sort-default-props': 2, 209 | 'react/sort-prop-types': 2, 210 | 'react/state-in-constructor': [2, 'always'], 211 | 'react/static-property-placement': 0, 212 | 'react/style-prop-object': 2, 213 | 'react/void-dom-elements-no-children': 2, 214 | }, 215 | settings: { 216 | react: { 217 | version: 'detect', 218 | }, 219 | }, 220 | }); 221 | -------------------------------------------------------------------------------- /configurations/regexp.ts: -------------------------------------------------------------------------------- 1 | import regexpPlugin from 'eslint-plugin-regexp'; 2 | import tseslint from 'typescript-eslint'; 3 | 4 | /** 5 | * https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/configs/recommended.ts 6 | */ 7 | export const recommended = tseslint.config({ 8 | files: ['**/*.{js,jsx,cjs,mjs,ts,tsx}'], 9 | plugins: { 10 | regexp: regexpPlugin, 11 | }, 12 | rules: { 13 | 'no-control-regex': 2, 14 | 'no-empty-character-class': 0, 15 | 'no-invalid-regexp': 'off', 16 | 'no-misleading-character-class': 2, 17 | 'no-regex-spaces': 2, 18 | 'no-useless-backreference': 'off', 19 | 'prefer-regex-literals': 2, 20 | 'regexp/confusing-quantifier': 'warn', 21 | 'regexp/control-character-escape': 2, 22 | 'regexp/match-any': 2, 23 | 'regexp/negation': 2, 24 | 'regexp/no-dupe-characters-character-class': 2, 25 | 'regexp/no-dupe-disjunctions': 2, 26 | 'regexp/no-empty-alternative': 'warn', 27 | 'regexp/no-empty-capturing-group': 2, 28 | 'regexp/no-empty-group': 2, 29 | 'regexp/no-empty-lookarounds-assertion': 2, 30 | 'regexp/no-escape-backspace': 2, 31 | 'regexp/no-invalid-regexp': 2, 32 | 'regexp/no-invisible-character': 2, 33 | 'regexp/no-lazy-ends': 'warn', 34 | 'regexp/no-legacy-features': 2, 35 | 'regexp/no-non-standard-flag': 2, 36 | 'regexp/no-obscure-range': 2, 37 | 'regexp/no-optional-assertion': 2, 38 | 'regexp/no-potentially-useless-backreference': 'warn', 39 | 'regexp/no-super-linear-backtracking': 2, 40 | 'regexp/no-trivially-nested-assertion': 2, 41 | 'regexp/no-trivially-nested-quantifier': 2, 42 | 'regexp/no-unused-capturing-group': 2, 43 | 'regexp/no-useless-assertions': 2, 44 | 'regexp/no-useless-backreference': 2, 45 | 'regexp/no-useless-character-class': 2, 46 | 'regexp/no-useless-dollar-replacements': 2, 47 | 'regexp/no-useless-escape': 2, 48 | 'regexp/no-useless-flag': 'warn', 49 | 'regexp/no-useless-lazy': 2, 50 | 'regexp/no-useless-non-capturing-group': 2, 51 | 'regexp/no-useless-quantifier': 2, 52 | 'regexp/no-useless-range': 2, 53 | 'regexp/no-useless-two-nums-quantifier': 2, 54 | 'regexp/no-zero-quantifier': 2, 55 | 'regexp/optimal-lookaround-quantifier': 'warn', 56 | 'regexp/optimal-quantifier-concatenation': 2, 57 | 'regexp/prefer-character-class': 2, 58 | 'regexp/prefer-d': 2, 59 | 'regexp/prefer-plus-quantifier': 2, 60 | 'regexp/prefer-predefined-assertion': 2, 61 | 'regexp/prefer-question-quantifier': 2, 62 | 'regexp/prefer-range': 2, 63 | 'regexp/prefer-star-quantifier': 2, 64 | 'regexp/prefer-unicode-codepoint-escapes': 2, 65 | 'regexp/prefer-w': 2, 66 | 'regexp/sort-flags': 2, 67 | 'regexp/strict': 2, 68 | }, 69 | }); 70 | -------------------------------------------------------------------------------- /configurations/typescript-compatibility.ts: -------------------------------------------------------------------------------- 1 | import tseslint from 'typescript-eslint'; 2 | 3 | export const recommended = tseslint.config({ 4 | files: ['**/*.{ts,tsx}'], 5 | rules: { 6 | '@typescript-eslint/default-param-last': 2, 7 | '@typescript-eslint/indent': [2, 2], 8 | '@typescript-eslint/init-declarations': 0, 9 | '@typescript-eslint/no-array-constructor': 2, 10 | '@typescript-eslint/no-dupe-class-members': 2, 11 | '@typescript-eslint/no-invalid-this': 0, 12 | '@typescript-eslint/no-loop-func': 2, 13 | '@typescript-eslint/no-loss-of-precision': 2, 14 | '@typescript-eslint/no-magic-numbers': 0, 15 | '@typescript-eslint/no-redeclare': [ 16 | 2, 17 | { 18 | builtinGlobals: true, 19 | }, 20 | ], 21 | '@typescript-eslint/no-restricted-imports': 0, 22 | '@typescript-eslint/no-shadow': [ 23 | 2, 24 | { 25 | builtinGlobals: false, 26 | hoist: 'all', 27 | }, 28 | ], 29 | '@typescript-eslint/no-unused-expressions': [ 30 | 2, 31 | { 32 | allowShortCircuit: false, 33 | allowTaggedTemplates: false, 34 | allowTernary: false, 35 | enforceForJSX: false, 36 | }, 37 | ], 38 | '@typescript-eslint/no-unused-vars': 2, 39 | '@typescript-eslint/no-use-before-define': [ 40 | 2, 41 | { 42 | classes: true, 43 | functions: false, 44 | variables: true, 45 | }, 46 | ], 47 | '@typescript-eslint/no-useless-constructor': 2, 48 | '@typescript-eslint/require-await': 0, 49 | 'block-spacing': 0, 50 | 'default-param-last': 0, 51 | 'dot-notation': 0, 52 | 'func-call-spacing': 0, 53 | indent: 0, 54 | 'init-declarations': 0, 55 | 'no-array-constructor': 0, 56 | 'no-dupe-class-members': 0, 57 | 'no-duplicate-imports': 0, 58 | 'no-empty-function': 0, 59 | 'no-implied-eval': 0, 60 | 'no-invalid-this': 0, 61 | 'no-loop-func': 0, 62 | 'no-loss-of-precision': 0, 63 | 'no-magic-numbers': 0, 64 | 'no-redeclare': 0, 65 | 'no-restricted-imports': 0, 66 | 'no-shadow': 0, 67 | 'no-throw-literal': 0, 68 | 'no-unused-expressions': 0, 69 | 'no-unused-vars': 0, 70 | 'no-use-before-define': 0, 71 | 'no-useless-constructor': 0, 72 | 'react/prop-types': 0, 73 | 'require-await': 0, 74 | }, 75 | }); 76 | -------------------------------------------------------------------------------- /configurations/typescript-type-checking.ts: -------------------------------------------------------------------------------- 1 | import typescriptEslintPlugin from '@typescript-eslint/eslint-plugin'; 2 | import tseslint from 'typescript-eslint'; 3 | 4 | export const recommended = tseslint.config({ 5 | plugins: { 6 | '@typescript-eslint': typescriptEslintPlugin, 7 | }, 8 | rules: { 9 | '@typescript-eslint/await-thenable': 2, 10 | '@typescript-eslint/consistent-type-exports': [ 11 | 2, 12 | { 13 | fixMixedExportsWithInlineTypeSpecifier: true, 14 | }, 15 | ], 16 | '@typescript-eslint/dot-notation': 2, 17 | '@typescript-eslint/no-base-to-string': [ 18 | 2, 19 | { 20 | ignoredTypeNames: ['RegExp'], 21 | }, 22 | ], 23 | '@typescript-eslint/no-confusing-void-expression': [ 24 | 2, 25 | { 26 | ignoreArrowShorthand: true, 27 | ignoreVoidOperator: false, 28 | }, 29 | ], 30 | '@typescript-eslint/no-floating-promises': [ 31 | 2, 32 | { 33 | ignoreIIFE: true, 34 | ignoreVoid: true, 35 | }, 36 | ], 37 | '@typescript-eslint/no-for-in-array': 2, 38 | '@typescript-eslint/no-implied-eval': 2, 39 | '@typescript-eslint/no-meaningless-void-operator': [ 40 | 2, 41 | { 42 | checkNever: true, 43 | }, 44 | ], 45 | '@typescript-eslint/no-misused-promises': [ 46 | 2, 47 | { 48 | checksConditionals: true, 49 | checksVoidReturn: { 50 | arguments: true, 51 | attributes: false, 52 | properties: true, 53 | returns: true, 54 | variables: true, 55 | }, 56 | }, 57 | ], 58 | '@typescript-eslint/no-unnecessary-qualifier': 2, 59 | '@typescript-eslint/prefer-includes': 2, 60 | '@typescript-eslint/prefer-nullish-coalescing': [ 61 | 2, 62 | { 63 | ignoreConditionalTests: true, 64 | ignoreMixedLogicalExpressions: true, 65 | }, 66 | ], 67 | '@typescript-eslint/prefer-optional-chain': 2, 68 | '@typescript-eslint/prefer-readonly': [ 69 | 2, 70 | { 71 | onlyInlineLambdas: true, 72 | }, 73 | ], 74 | '@typescript-eslint/prefer-reduce-type-parameter': 2, 75 | '@typescript-eslint/prefer-regexp-exec': 2, 76 | '@typescript-eslint/prefer-return-this-type': 2, 77 | '@typescript-eslint/prefer-string-starts-ends-with': 2, 78 | '@typescript-eslint/promise-function-async': 2, 79 | '@typescript-eslint/require-array-sort-compare': [ 80 | 2, 81 | { 82 | ignoreStringArrays: false, 83 | }, 84 | ], 85 | '@typescript-eslint/return-await': [2, 'always'], 86 | '@typescript-eslint/switch-exhaustiveness-check': 2, 87 | '@typescript-eslint/unbound-method': [ 88 | 2, 89 | { 90 | ignoreStatic: true, 91 | }, 92 | ], 93 | }, 94 | }); 95 | -------------------------------------------------------------------------------- /configurations/typescript.ts: -------------------------------------------------------------------------------- 1 | import * as typescriptCompatibility from './typescript-compatibility.js'; 2 | import stylisticPlugin from '@stylistic/eslint-plugin'; 3 | import typescriptEslintPlugin from '@typescript-eslint/eslint-plugin'; 4 | import * as eslintParser from '@typescript-eslint/parser'; 5 | import canonicalPlugin from 'eslint-plugin-canonical'; 6 | import functionalPlugin from 'eslint-plugin-functional'; 7 | import tseslint from 'typescript-eslint'; 8 | 9 | export const recommended = tseslint.config({ 10 | files: ['**/*.{ts,tsx}'], 11 | languageOptions: { 12 | parser: eslintParser, 13 | parserOptions: { 14 | project: true, 15 | }, 16 | }, 17 | plugins: { 18 | '@stylistic': stylisticPlugin, 19 | '@typescript-eslint': typescriptEslintPlugin, 20 | canonical: canonicalPlugin, 21 | functional: functionalPlugin, 22 | }, 23 | rules: { 24 | '@stylistic/member-delimiter-style': [ 25 | 2, 26 | { 27 | multiline: { 28 | delimiter: 'comma', 29 | requireLast: true, 30 | }, 31 | overrides: { 32 | interface: { 33 | multiline: { 34 | delimiter: 'semi', 35 | requireLast: true, 36 | }, 37 | }, 38 | }, 39 | singleline: { 40 | delimiter: 'comma', 41 | requireLast: false, 42 | }, 43 | }, 44 | ], 45 | '@stylistic/type-annotation-spacing': [ 46 | 2, 47 | { 48 | after: true, 49 | before: false, 50 | overrides: { 51 | arrow: { 52 | after: true, 53 | before: true, 54 | }, 55 | }, 56 | }, 57 | ], 58 | '@typescript-eslint/adjacent-overload-signatures': 2, 59 | '@typescript-eslint/array-type': [ 60 | 2, 61 | { 62 | default: 'array-simple', 63 | }, 64 | ], 65 | '@typescript-eslint/ban-ts-comment': [ 66 | 2, 67 | { 68 | 'ts-check': true, 69 | 'ts-expect-error': 'allow-with-description', 70 | 'ts-ignore': false, 71 | 'ts-nocheck': false, 72 | }, 73 | ], 74 | '@typescript-eslint/ban-tslint-comment': 2, 75 | '@typescript-eslint/ban-types': 0, 76 | '@typescript-eslint/class-literal-property-style': [2, 'fields'], 77 | '@typescript-eslint/consistent-indexed-object-style': 0, 78 | '@typescript-eslint/consistent-type-assertions': [ 79 | 2, 80 | { 81 | assertionStyle: 'as', 82 | objectLiteralTypeAssertions: 'allow', 83 | }, 84 | ], 85 | '@typescript-eslint/consistent-type-definitions': [2, 'type'], 86 | '@typescript-eslint/consistent-type-imports': [ 87 | 2, 88 | { 89 | prefer: 'type-imports', 90 | }, 91 | ], 92 | '@typescript-eslint/default-param-last': 2, 93 | '@typescript-eslint/explicit-function-return-type': 0, 94 | '@typescript-eslint/explicit-member-accessibility': 0, 95 | '@typescript-eslint/explicit-module-boundary-types': 0, 96 | '@typescript-eslint/member-ordering': 0, 97 | '@typescript-eslint/method-signature-style': [2, 'property'], 98 | '@typescript-eslint/naming-convention': [ 99 | 2, 100 | { 101 | format: ['camelCase', 'UPPER_CASE', 'PascalCase'], 102 | selector: 'variable', 103 | }, 104 | ], 105 | '@typescript-eslint/no-confusing-non-null-assertion': 2, 106 | '@typescript-eslint/no-dynamic-delete': 2, 107 | '@typescript-eslint/no-empty-function': 0, 108 | '@typescript-eslint/no-empty-interface': [ 109 | 'error', 110 | { 111 | allowSingleExtends: false, 112 | }, 113 | ], 114 | '@typescript-eslint/no-explicit-any': [ 115 | 2, 116 | { 117 | ignoreRestArgs: true, 118 | }, 119 | ], 120 | '@typescript-eslint/no-extra-non-null-assertion': 2, 121 | '@typescript-eslint/no-extraneous-class': 2, 122 | '@typescript-eslint/no-inferrable-types': [ 123 | 2, 124 | { 125 | ignoreParameters: true, 126 | ignoreProperties: true, 127 | }, 128 | ], 129 | '@typescript-eslint/no-invalid-void-type': [ 130 | 2, 131 | { 132 | allowAsThisParameter: true, 133 | allowInGenericTypeArguments: true, 134 | }, 135 | ], 136 | '@typescript-eslint/no-misused-new': 2, 137 | '@typescript-eslint/no-namespace': [ 138 | 2, 139 | { 140 | allowDeclarations: true, 141 | }, 142 | ], 143 | '@typescript-eslint/no-non-null-asserted-nullish-coalescing': 2, 144 | '@typescript-eslint/no-non-null-asserted-optional-chain': 2, 145 | '@typescript-eslint/no-non-null-assertion': 2, 146 | '@typescript-eslint/no-parameter-properties': 0, 147 | '@typescript-eslint/no-require-imports': 2, 148 | '@typescript-eslint/no-restricted-imports': 0, 149 | '@typescript-eslint/no-this-alias': [ 150 | 2, 151 | { 152 | allowDestructuring: true, 153 | allowedNames: ['self'], 154 | }, 155 | ], 156 | '@typescript-eslint/no-unnecessary-boolean-literal-compare': 0, 157 | '@typescript-eslint/no-unnecessary-condition': 0, 158 | '@typescript-eslint/no-unnecessary-type-arguments': 0, 159 | '@typescript-eslint/no-unnecessary-type-assertion': 0, 160 | '@typescript-eslint/no-unnecessary-type-constraint': 2, 161 | '@typescript-eslint/no-unsafe-argument': 0, 162 | '@typescript-eslint/no-unsafe-assignment': 0, 163 | '@typescript-eslint/no-unsafe-call': 0, 164 | '@typescript-eslint/no-unsafe-member-access': 0, 165 | '@typescript-eslint/no-unsafe-return': 0, 166 | '@typescript-eslint/no-var-requires': 2, 167 | '@typescript-eslint/non-nullable-type-assertion-style': 0, 168 | '@typescript-eslint/prefer-as-const': 2, 169 | '@typescript-eslint/prefer-enum-initializers': 2, 170 | '@typescript-eslint/prefer-for-of': 2, 171 | '@typescript-eslint/prefer-function-type': 2, 172 | '@typescript-eslint/prefer-literal-enum-member': 2, 173 | '@typescript-eslint/prefer-namespace-keyword': 2, 174 | '@typescript-eslint/prefer-ts-expect-error': 2, 175 | '@typescript-eslint/restrict-plus-operands': 0, 176 | '@typescript-eslint/restrict-template-expressions': 0, 177 | '@typescript-eslint/strict-boolean-expressions': 0, 178 | '@typescript-eslint/triple-slash-reference': [ 179 | 2, 180 | { 181 | lib: 'never', 182 | path: 'never', 183 | types: 'never', 184 | }, 185 | ], 186 | '@typescript-eslint/typedef': 0, 187 | '@typescript-eslint/unified-signatures': 2, 188 | 'canonical/prefer-inline-type-import': 2, 189 | 'default-param-last': 0, 190 | 'import/no-dynamic-require': 0, 191 | 'jsdoc/require-property-type': 0, 192 | 'n/global-require': 0, 193 | 'n/no-missing-import': 0, 194 | 'no-duplicate-imports': 0, 195 | 'no-undef': 0, 196 | 'no-use-before-define': 0, 197 | 'spaced-comment': [ 198 | 2, 199 | 'always', 200 | { 201 | markers: ['/'], 202 | }, 203 | ], 204 | ...typescriptCompatibility.recommended[0].rules, 205 | }, 206 | settings: { 207 | 'import/extensions': ['.ts', '.tsx'], 208 | 'import/external-module-folders': ['node_modules', 'node_modules/@types'], 209 | 'import/parsers': { 210 | '@typescript-eslint/parser': ['.ts', '.tsx'], 211 | }, 212 | 'import/resolver': { 213 | typescript: { 214 | extensions: ['.ts', '.tsx'], 215 | }, 216 | }, 217 | jsdoc: { 218 | mode: 'typescript', 219 | }, 220 | node: { 221 | tryExtensions: ['.ts', '.tsx', '.js', '.json'], 222 | }, 223 | }, 224 | }); 225 | -------------------------------------------------------------------------------- /configurations/vitest.ts: -------------------------------------------------------------------------------- 1 | import vitestPlugin from '@vitest/eslint-plugin'; 2 | import tseslint from 'typescript-eslint'; 3 | 4 | export const recommended = tseslint.config({ 5 | plugins: { 6 | vitest: vitestPlugin, 7 | }, 8 | rules: { 9 | 'vitest/expect-expect': 2, 10 | 'vitest/lower-case-title': 0, 11 | 'vitest/max-nested-describe': 2, 12 | 'vitest/no-conditional-tests': 2, 13 | 'vitest/no-disabled-tests': 2, 14 | 'vitest/no-focused-tests': [ 15 | 2, 16 | { 17 | fixable: false, 18 | }, 19 | ], 20 | 'vitest/no-identical-title': 2, 21 | }, 22 | }); 23 | -------------------------------------------------------------------------------- /configurations/yaml.ts: -------------------------------------------------------------------------------- 1 | import yamlPlugin from 'eslint-plugin-yml'; 2 | import tseslint from 'typescript-eslint'; 3 | import yamlEslintParser from 'yaml-eslint-parser'; 4 | 5 | export const recommended = tseslint.config({ 6 | files: ['**/*.yaml'], 7 | languageOptions: { 8 | parser: yamlEslintParser, 9 | }, 10 | plugins: { 11 | yml: yamlPlugin, 12 | }, 13 | rules: { 14 | 'yml/block-mapping': 2, 15 | 'yml/block-mapping-question-indicator-newline': 2, 16 | 'yml/block-sequence': [2, 'always'], 17 | 'yml/block-sequence-hyphen-indicator-newline': 2, 18 | 'yml/flow-mapping-curly-newline': 2, 19 | 'yml/flow-mapping-curly-spacing': 2, 20 | 'yml/flow-sequence-bracket-newline': 2, 21 | 'yml/flow-sequence-bracket-spacing': 2, 22 | 'yml/indent': [2, 2], 23 | 'yml/key-name-casing': 0, 24 | 'yml/key-spacing': 2, 25 | 'yml/no-empty-document': 2, 26 | 'yml/no-empty-key': 2, 27 | 'yml/no-empty-mapping-value': 2, 28 | 'yml/no-empty-sequence-entry': 2, 29 | 'yml/no-irregular-whitespace': 2, 30 | 'yml/no-multiple-empty-lines': [ 31 | 2, 32 | { 33 | max: 0, 34 | }, 35 | ], 36 | 'yml/no-tab-indent': 2, 37 | 'yml/plain-scalar': 0, 38 | 'yml/quotes': [ 39 | 2, 40 | { 41 | avoidEscape: true, 42 | prefer: 'single', 43 | }, 44 | ], 45 | 'yml/require-string-key': 2, 46 | 'yml/sort-keys': [ 47 | 2, 48 | 'asc', 49 | { 50 | caseSensitive: false, 51 | natural: true, 52 | }, 53 | ], 54 | 'yml/sort-sequence-values': 0, 55 | 'yml/spaced-comment': 2, 56 | 'yml/vue-custom-block/no-parsing-error': 2, 57 | }, 58 | }); 59 | -------------------------------------------------------------------------------- /configurations/zod.ts: -------------------------------------------------------------------------------- 1 | import zodPlugin from 'eslint-plugin-zod'; 2 | import tseslint from 'typescript-eslint'; 3 | 4 | export const recommended = tseslint.config({ 5 | plugins: { 6 | zod: zodPlugin, 7 | }, 8 | rules: { 9 | 'zod/prefer-enum': 2, 10 | 'zod/require-strict': 2, 11 | }, 12 | }); 13 | -------------------------------------------------------------------------------- /eslint.config.ts: -------------------------------------------------------------------------------- 1 | import auto from './configurations/auto.js'; 2 | import * as ava from './configurations/ava.js'; 3 | import * as node from './configurations/node.js'; 4 | import tseslint from 'typescript-eslint'; 5 | 6 | export default tseslint.config( 7 | node.recommended, 8 | ...auto, 9 | ava.recommended, 10 | { 11 | rules: { 12 | 'n/global-require': 0, 13 | }, 14 | }, 15 | { 16 | ignores: ['**/package-lock.json', '**/pnpm-lock.yaml', '**/dist', '**/.*'], 17 | }, 18 | ); 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": { 3 | "email": "gajus@gajus.com", 4 | "name": "Gajus Kuizinas", 5 | "url": "http://gajus.com" 6 | }, 7 | "ava": { 8 | "extensions": { 9 | "ts": "module" 10 | }, 11 | "files": [ 12 | "configurations/auto.test.ts" 13 | ], 14 | "nodeArguments": [ 15 | "--import=tsimp/import" 16 | ] 17 | }, 18 | "dependencies": { 19 | "@graphql-eslint/eslint-plugin": "^4.4.0", 20 | "@next/eslint-plugin-next": "^15.5.0", 21 | "@stylistic/eslint-plugin": "^4.2.0", 22 | "@typescript-eslint/eslint-plugin": "^8.40.0", 23 | "@typescript-eslint/parser": "^8.40.0", 24 | "@vitest/eslint-plugin": "^1.3.4", 25 | "eslint-config-prettier": "^10.1.8", 26 | "eslint-import-resolver-typescript": "^4.4.4", 27 | "eslint-plugin-ava": "^15.1.0", 28 | "eslint-plugin-canonical": "^5.1.3", 29 | "eslint-plugin-eslint-comments": "^3.2.0", 30 | "eslint-plugin-fp": "^2.3.0", 31 | "eslint-plugin-functional": "^9.0.2", 32 | "eslint-plugin-import": "npm:eslint-plugin-import-x@^4.16.1", 33 | "eslint-plugin-jest": "^28.11.0", 34 | "eslint-plugin-jsdoc": "^50.6.8", 35 | "eslint-plugin-jsonc": "^2.20.1", 36 | "eslint-plugin-jsx-a11y": "^6.10.2", 37 | "eslint-plugin-lodash": "^8.0.0", 38 | "eslint-plugin-mocha": "^10.5.0", 39 | "eslint-plugin-modules-newline": "0.0.6", 40 | "eslint-plugin-n": "^17.21.3", 41 | "eslint-plugin-perfectionist": "^4.15.0", 42 | "eslint-plugin-prettier": "^5.5.4", 43 | "eslint-plugin-promise": "^7.2.1", 44 | "eslint-plugin-react": "^7.37.5", 45 | "eslint-plugin-react-hooks": "^6.1.0", 46 | "eslint-plugin-regexp": "^2.10.0", 47 | "eslint-plugin-unicorn": "^60.0.0", 48 | "eslint-plugin-yml": "^1.18.0", 49 | "eslint-plugin-zod": "^1.4.0", 50 | "globals": "^16.3.0", 51 | "graphql": "^16.11.0", 52 | "prettier": "^3.6.2", 53 | "ramda": "^0.30.1", 54 | "typescript-eslint": "^8.40.0", 55 | "yaml-eslint-parser": "^1.3.0" 56 | }, 57 | "description": "Canonical ESLint Shareable Config", 58 | "devDependencies": { 59 | "@semantic-release/commit-analyzer": "^13.0.1", 60 | "@semantic-release/github": "^11.0.4", 61 | "@semantic-release/npm": "^12.0.2", 62 | "ava": "^6.4.1", 63 | "eslint": "^9.34.0", 64 | "got": "^14.4.7", 65 | "husky": "^9.1.7", 66 | "semantic-release": "^24.2.7", 67 | "tsimp": "^2.0.12", 68 | "typescript": "^5.9.2" 69 | }, 70 | "engines": { 71 | "node": ">=16.0.0" 72 | }, 73 | "exports": { 74 | ".": { 75 | "import": "./dist/configurations/index.js", 76 | "types": "./dist/configurations/index.d.ts" 77 | }, 78 | "./*": { 79 | "import": "./dist/configurations/*.js", 80 | "types": "./dist/configurations/*.d.ts" 81 | } 82 | }, 83 | "files": [ 84 | "dist" 85 | ], 86 | "keywords": [ 87 | "eslint", 88 | "config", 89 | "canonical" 90 | ], 91 | "license": "BSD-3-Clause", 92 | "main": "./eslintrc.js", 93 | "name": "eslint-config-canonical", 94 | "peerDependencies": { 95 | "eslint": "^9" 96 | }, 97 | "repository": { 98 | "type": "git", 99 | "url": "https://github.com/gajus/eslint-config-canonical" 100 | }, 101 | "scripts": { 102 | "build": "tsc", 103 | "compare": "(cd compare; node compare.js)", 104 | "find-deprecated": "(cd compare; node find-deprecated.js)", 105 | "generate-typescript-compatibility-rules": "node bin/generate-typescript-compatibility-rules.js > configurations/typescript-compatibility.js && eslint configurations/typescript-compatibility.js --fix", 106 | "lint": "eslint --report-unused-disable-directives .", 107 | "setup-dev": "(npm link; cd compare; npm install; npm link eslint-config-canonical; node compare.js)", 108 | "test": "ava --verbose --serial" 109 | }, 110 | "type": "module", 111 | "version": "1.0.0" 112 | } 113 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "declaration": true, 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "lib": [ 8 | "es2021" 9 | ], 10 | "module": "NodeNext", 11 | "moduleResolution": "NodeNext", 12 | "noImplicitAny": false, 13 | "noImplicitReturns": true, 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": false, 16 | "outDir": "dist", 17 | "skipLibCheck": true, 18 | "strict": true, 19 | "target": "ES2024" 20 | }, 21 | "exclude": [ 22 | "dist", 23 | "node_modules" 24 | ], 25 | "include": [ 26 | "eslint.config.ts", 27 | "configurations" 28 | ] 29 | } --------------------------------------------------------------------------------