├── .gitignore ├── .release-it.json ├── README.md ├── __tests__ ├── index.js └── index.ts ├── eslint.config.mjs ├── index.mjs ├── lefthook.yml ├── package.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .DS_Store 3 | 4 | yarn-debug.log 5 | 6 | node_modules/ 7 | .history/ 8 | -------------------------------------------------------------------------------- /.release-it.json: -------------------------------------------------------------------------------- 1 | { 2 | "git": { 3 | "commitMessage": "chore: release ${version}", 4 | "tagName": "v${version}" 5 | }, 6 | "npm": { 7 | "publish": true 8 | }, 9 | "github": { 10 | "release": true 11 | }, 12 | "plugins": { 13 | "@release-it/conventional-changelog": { 14 | "preset": "angular" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # eslint-config-satya164 2 | 3 | This is my personal ESLint config. I try to avoid rules which are purely stylistic and based on personal opinions. I'm tryin to keep it non-intrusive and aimed towards catching actual errors. 4 | 5 | ## Features 6 | 7 | The config includes these plugins by default: 8 | 9 | - [eslint-comments](https://eslint-community.github.io/eslint-plugin-eslint-comments/) 10 | - [import-x](https://github.com/un-ts/eslint-plugin-import-x) 11 | - [jest](https://github.com/jest-community/eslint-plugin-jest) 12 | - [prettier](https://github.com/prettier/eslint-plugin-prettier) 13 | - [promise](https://github.com/eslint-community/eslint-plugin-promise) 14 | - [react-hooks](https://github.com/facebook/react/tree/main/packages/eslint-plugin-react-hooks) 15 | - [react-hooks](https://reactjs.org/docs/hooks-rules.html) 16 | - [react](https://github.com/Rel1cx/eslint-react) 17 | - [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint) 18 | 19 | The config uses the `overrides` feature of ESLint to automatically adjust the config based on the filename. For example, typescript support is enabled for `.ts` and `.tsx` files, the `jest` environment is set for test files and more. 20 | 21 | Prettier is used for formatting. 22 | 23 | ## Usage 24 | 25 | First, install the required packages: 26 | 27 | ```sh 28 | yarn add --dev prettier eslint eslint-config-satya164 29 | ``` 30 | 31 | If you're using TypeScript, also install the TypeScript compiler: 32 | 33 | ```sh 34 | yarn add --dev typescript 35 | ``` 36 | 37 | Now use the config in your config file: 38 | 39 | ```js 40 | // eslint.config.mjs 41 | import { defineConfig } from 'eslint/config'; 42 | import { recommended, react, vitest } from 'eslint-config-satya164'; 43 | 44 | export default defineConfig( 45 | // The base config 46 | recommended, 47 | 48 | // Optional configs for tools (i.e. react, vitest, jest) 49 | react, 50 | vitest, 51 | 52 | { 53 | // Your additional config here 54 | } 55 | ); 56 | ``` 57 | 58 | You can also enable type-aware rules: 59 | 60 | ```js 61 | // eslint.config.mjs 62 | import { defineConfig } from 'eslint/config'; 63 | import { recommended, typechecked } from 'eslint-config-satya164'; 64 | 65 | export default defineConfig( 66 | // The base config 67 | recommended, 68 | 69 | // Type-aware rules 70 | typechecked, 71 | 72 | { 73 | languageOptions: { 74 | // Needed for type-aware rules 75 | parserOptions: { 76 | project: true, 77 | // Path to folder containing tsconfig.json 78 | tsconfigRootDir: import.meta.dirname, 79 | }, 80 | }, 81 | }, 82 | ); 83 | ``` 84 | 85 | Also see [Typed Linting](https://typescript-eslint.io/troubleshooting/typed-linting) docs for more details. 86 | 87 | Using the config requires ESLint 9's [flat config format](https://eslint.org/docs/latest/use/configure/configuration-file). 88 | 89 | To lint your files, you can add the following script to your `package.json`: 90 | 91 | ```json 92 | "scripts": { 93 | "lint": "eslint \"**/*.{js,ts,tsx}\"" 94 | } 95 | ``` 96 | 97 | To show lint errors in your editor, you'll need to configure your editor. To configure [VSCode](https://code.visualstudio.com), add the following in `settings.json`: 98 | 99 | ```json 100 | "eslint.validate": [ 101 | { 102 | "language": "javascript", 103 | "autoFix": true 104 | }, 105 | { 106 | "language": "javascriptreact", 107 | "autoFix": true 108 | }, 109 | { 110 | "language": "typescript", 111 | "autoFix": true 112 | }, 113 | { 114 | "language": "typescriptreact", 115 | "autoFix": true 116 | } 117 | ], 118 | ``` 119 | 120 | On Mac OS, you can open `settings.json` file from `Code` > `Preferences` > `Settings` or via the keyboard shortcut ⌘,. 121 | 122 | This config sets `autoFix` to `true` to automatically fix lint errors on save. You can set it to `false` if you don't want this behaviour. 123 | 124 | Happy linting 🎉 125 | -------------------------------------------------------------------------------- /__tests__/index.js: -------------------------------------------------------------------------------- 1 | export default function fizzbuzz() { 2 | for (let i = 1; i < 101; i++) { 3 | if (i % 15 === 0) { 4 | return 'FizzBuzz'; 5 | } else if (i % 3 === 0) { 6 | return 'Fizz'; 7 | } else if (i % 5 === 0) { 8 | return 'Buzz'; 9 | } else { 10 | return i; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /__tests__/index.ts: -------------------------------------------------------------------------------- 1 | export default function fizzbuzz() { 2 | for (let i = 1; i < 101; i++) { 3 | if (i % 15 === 0) { 4 | return 'FizzBuzz'; 5 | } else if (i % 3 === 0) { 6 | return 'Fizz'; 7 | } else if (i % 5 === 0) { 8 | return 'Buzz'; 9 | } else { 10 | return i; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'eslint/config'; 2 | import { jest, react, recommended, vitest } from './index.mjs'; 3 | 4 | export default defineConfig({ 5 | extends: [recommended, react, jest, vitest], 6 | }); 7 | -------------------------------------------------------------------------------- /index.mjs: -------------------------------------------------------------------------------- 1 | import comments from '@eslint-community/eslint-plugin-eslint-comments/configs'; 2 | import reactPlugin from '@eslint-react/eslint-plugin'; 3 | import js from '@eslint/js'; 4 | import vitestPlugin from '@vitest/eslint-plugin'; 5 | import * as tsResolver from 'eslint-import-resolver-typescript'; 6 | import importX from 'eslint-plugin-import-x'; 7 | import jestPlugin from 'eslint-plugin-jest'; 8 | import prettier from 'eslint-plugin-prettier/recommended'; 9 | import promise from 'eslint-plugin-promise'; 10 | import hooks from 'eslint-plugin-react-hooks'; 11 | import globals from 'globals'; 12 | import tseslint from 'typescript-eslint'; 13 | 14 | export const recommended = tseslint.config( 15 | js.configs.recommended, 16 | comments.recommended, 17 | importX.flatConfigs.recommended, 18 | promise.configs['flat/recommended'], 19 | prettier, 20 | { 21 | languageOptions: { 22 | ecmaVersion: 'latest', 23 | sourceType: 'module', 24 | parserOptions: { 25 | ecmaFeatures: { 26 | jsx: true, 27 | }, 28 | }, 29 | }, 30 | 31 | settings: { 32 | 'import-x/ignore': ['node_modules'], 33 | }, 34 | 35 | rules: { 36 | 'eqeqeq': ['error', 'smart'], 37 | 'array-callback-return': 'error', 38 | 'default-case': [ 39 | 'error', 40 | { 41 | commentPattern: '^no default$', 42 | }, 43 | ], 44 | 'new-parens': 'error', 45 | 'no-array-constructor': 'error', 46 | 'no-caller': 'error', 47 | 'no-case-declarations': 'error', 48 | 'no-cond-assign': ['error', 'except-parens'], 49 | 'no-constant-binary-expression': 'error', 50 | 'no-constructor-return': 'error', 51 | 'no-delete-var': 'error', 52 | 'no-empty': 'error', 53 | 'no-eval': 'error', 54 | 'no-extend-native': 'error', 55 | 'no-extra-bind': 'error', 56 | 'no-extra-boolean-cast': 'error', 57 | 'no-extra-label': 'error', 58 | 'no-extra-semi': 'error', 59 | 'no-func-assign': 'error', 60 | 'no-global-assign': 'error', 61 | 'no-implied-eval': 'error', 62 | 'no-iterator': 'error', 63 | 'no-label-var': 'error', 64 | 'no-labels': [ 65 | 'error', 66 | { 67 | allowLoop: true, 68 | allowSwitch: false, 69 | }, 70 | ], 71 | 'no-lone-blocks': 'error', 72 | 'no-loop-func': 'error', 73 | 'no-multi-str': 'error', 74 | 'no-new-func': 'error', 75 | 'no-new-object': 'error', 76 | 'no-new-wrappers': 'error', 77 | 'no-octal-escape': 'error', 78 | 'no-octal': 'error', 79 | 'no-promise-executor-return': 'error', 80 | 'no-redeclare': 'error', 81 | 'no-regex-spaces': 'error', 82 | 'no-self-compare': 'error', 83 | 'no-shadow-restricted-names': 'error', 84 | 'no-template-curly-in-string': 'error', 85 | 'no-throw-literal': 'error', 86 | 'no-unused-labels': 'error', 87 | 'no-unused-private-class-members': 'error', 88 | 'no-use-before-define': [ 89 | 'error', 90 | { 91 | functions: false, 92 | classes: false, 93 | variables: false, 94 | }, 95 | ], 96 | 'no-useless-computed-key': 'error', 97 | 'no-useless-concat': 'error', 98 | 'no-useless-constructor': 'error', 99 | 'no-useless-escape': 'error', 100 | 'no-useless-rename': 'error', 101 | 'no-with': 'error', 102 | 'require-atomic-updates': 'error', 103 | 'require-yield': 'error', 104 | 'unicode-bom': 'error', 105 | 106 | '@eslint-community/eslint-comments/disable-enable-pair': [ 107 | 'error', 108 | { 109 | allowWholeFile: true, 110 | }, 111 | ], 112 | '@eslint-community/eslint-comments/no-unused-disable': 'error', 113 | 114 | 'import-x/export': 'error', 115 | 'import-x/extensions': ['error', { js: 'never', json: 'always' }], 116 | 'import-x/imports-first': 'error', 117 | 'import-x/no-absolute-path': 'error', 118 | 'import-x/no-amd': 'error', 119 | 'import-x/no-commonjs': 'error', 120 | 'import-x/no-duplicates': 'error', 121 | 'import-x/no-empty-named-blocks': 'error', 122 | 'import-x/no-extraneous-dependencies': 'error', 123 | 'import-x/no-unresolved': 'off', 124 | 'import-x/no-self-import': 'error', 125 | 'import-x/no-useless-path-segments': 'error', 126 | 'import-x/no-relative-packages': 'error', 127 | 128 | '@typescript-eslint/no-require-import': 'off', 129 | 130 | 'prettier/prettier': [ 131 | 'error', 132 | { 133 | quoteProps: 'consistent', 134 | singleQuote: true, 135 | tabWidth: 2, 136 | trailingComma: 'es5', 137 | useTabs: false, 138 | }, 139 | ], 140 | }, 141 | }, 142 | { 143 | files: ['**/*.{js,jsx,cjs,mjs'], 144 | 145 | rules: { 146 | 'import-x/no-cycle': 'error', 147 | 'import-x/default': 'error', 148 | 'import-x/named': 'error', 149 | 'import-x/namespace': 'error', 150 | 'import-x/no-named-as-default': 'error', 151 | 'import-x/no-named-as-default-member': 'error', 152 | 'import-x/no-deprecated': 'error', 153 | }, 154 | }, 155 | { 156 | files: ['**/*.{mjs,mts}'], 157 | 158 | rules: { 159 | 'import-x/extensions': ['error', 'ignorePackages'], 160 | }, 161 | }, 162 | { 163 | files: ['**/*.{ts,tsx}'], 164 | 165 | extends: [tseslint.configs.recommended, importX.flatConfigs.typescript], 166 | 167 | settings: { 168 | 'import-x/resolver': { 169 | name: 'typescript-resolver', 170 | resolver: tsResolver, 171 | }, 172 | }, 173 | 174 | rules: { 175 | '@typescript-eslint/adjacent-overload-signatures': 'error', 176 | '@typescript-eslint/array-type': 'error', 177 | '@typescript-eslint/consistent-type-assertions': [ 178 | 'error', 179 | { 180 | assertionStyle: 'as', 181 | }, 182 | ], 183 | '@typescript-eslint/no-dynamic-delete': 'error', 184 | '@typescript-eslint/no-empty-interface': [ 185 | 'error', 186 | { 187 | allowSingleExtends: true, 188 | }, 189 | ], 190 | '@typescript-eslint/no-extra-non-null-assertion': 'error', 191 | '@typescript-eslint/no-extraneous-class': 'error', 192 | '@typescript-eslint/no-unused-vars': [ 193 | 'error', 194 | { 195 | vars: 'all', 196 | args: 'after-used', 197 | caughtErrors: 'none', 198 | argsIgnorePattern: '^_', 199 | }, 200 | ], 201 | '@typescript-eslint/no-use-before-define': [ 202 | 'error', 203 | { 204 | functions: false, 205 | classes: false, 206 | variables: false, 207 | typedefs: false, 208 | }, 209 | ], 210 | '@typescript-eslint/no-useless-constructor': 'error', 211 | '@typescript-eslint/prefer-for-of': 'error', 212 | '@typescript-eslint/prefer-function-type': 'error', 213 | '@typescript-eslint/prefer-namespace-keyword': 'error', 214 | '@typescript-eslint/unified-signatures': 'error', 215 | 216 | 'default-case': 'off', 217 | 'no-dupe-class-members': 'off', 218 | 'no-redeclare': 'off', 219 | 'no-undef': 'off', 220 | 'no-unused-vars': 'off', 221 | 'no-use-before-define': 'off', 222 | }, 223 | }, 224 | { 225 | files: ['**/*.config.{ts,mts,js,cjs,mjs}', '**/.*rc.{ts,mts,js,cjs,mjs}'], 226 | 227 | languageOptions: { 228 | globals: { 229 | ...globals.node, 230 | }, 231 | }, 232 | 233 | rules: { 234 | 'import-x/no-default-export': 'off', 235 | 236 | 'import-x/no-extraneous-dependencies': [ 237 | 'error', 238 | { 239 | devDependencies: true, 240 | }, 241 | ], 242 | }, 243 | }, 244 | { 245 | files: ['**/*.config.{js,cjs}', '**/.*rc.{js,cjs}'], 246 | 247 | languageOptions: { 248 | globals: { 249 | ...globals.node, 250 | }, 251 | }, 252 | 253 | rules: { 254 | 'import-x/no-commonjs': 'off', 255 | }, 256 | } 257 | ); 258 | 259 | export const react = tseslint.config( 260 | hooks.configs['recommended-latest'], 261 | reactPlugin.configs.recommended, 262 | { 263 | settings: { 264 | react: { 265 | version: 'detect', 266 | }, 267 | }, 268 | 269 | rules: { 270 | '@eslint-react/ensure-forward-ref-using-ref': 'error', 271 | '@eslint-react/jsx-key-before-spread': 'error', 272 | '@eslint-react/naming-convention/component-name': 'error', 273 | '@eslint-react/naming-convention/context-name': 'off', 274 | '@eslint-react/naming-convention/filename-extension': [ 275 | 'error', 276 | { 277 | allow: 'always', 278 | }, 279 | ], 280 | '@eslint-react/no-array-index-key': 'error', 281 | '@eslint-react/no-children-count': 'off', 282 | '@eslint-react/no-children-for-each': 'off', 283 | '@eslint-react/no-children-map': 'off', 284 | '@eslint-react/no-children-only': 'off', 285 | '@eslint-react/no-children-to-array': 'off', 286 | '@eslint-react/no-clone-element': 'off', 287 | '@eslint-react/no-comment-textnodes': 'error', 288 | '@eslint-react/no-context-provider': 'off', 289 | '@eslint-react/no-create-ref': 'off', 290 | '@eslint-react/no-default-props': 'off', 291 | '@eslint-react/no-forward-ref': 'off', 292 | '@eslint-react/no-implicit-key': 'error', 293 | '@eslint-react/no-missing-component-display-name': 'error', 294 | '@eslint-react/no-nested-components': 'error', 295 | '@eslint-react/no-set-state-in-component-did-mount': 'error', 296 | '@eslint-react/no-set-state-in-component-did-update': 'error', 297 | '@eslint-react/no-set-state-in-component-will-update': 'error', 298 | '@eslint-react/no-unsafe-component-will-mount': 'error', 299 | '@eslint-react/no-unsafe-component-will-receive-props': 'error', 300 | '@eslint-react/no-unsafe-component-will-update': 'error', 301 | '@eslint-react/no-unused-class-component-members': 'error', 302 | '@eslint-react/no-unused-state': 'error', 303 | '@eslint-react/no-use-context': 'off', 304 | '@eslint-react/no-useless-fragment': 'off', 305 | 306 | '@eslint-react/dom/no-children-in-void-dom-elements': 'error', 307 | '@eslint-react/dom/no-dangerously-set-innerhtml': 'error', 308 | '@eslint-react/dom/no-dangerously-set-innerhtml-with-children': 'error', 309 | '@eslint-react/dom/no-find-dom-node': 'error', 310 | '@eslint-react/dom/no-missing-button-type': 'error', 311 | '@eslint-react/dom/no-namespace': 'error', 312 | '@eslint-react/dom/no-render-return-value': 'error', 313 | '@eslint-react/dom/no-script-url': 'error', 314 | '@eslint-react/dom/no-unsafe-iframe-sandbox': 'error', 315 | 316 | '@eslint-react/hooks-extra/ensure-custom-hooks-using-other-hooks': 317 | 'error', 318 | '@eslint-react/hooks-extra/no-direct-set-state-in-use-effect': 'error', 319 | '@eslint-react/hooks-extra/no-direct-set-state-in-use-layout-effect': 320 | 'error', 321 | '@eslint-react/hooks-extra/prefer-use-state-lazy-initialization': 'error', 322 | 323 | 'react-hooks/rules-of-hooks': 'error', 324 | 'react-hooks/exhaustive-deps': 'error', 325 | }, 326 | } 327 | ); 328 | 329 | export const typechecked = tseslint.config({ 330 | files: ['**/*.{ts,tsx}'], 331 | 332 | extends: [tseslint.configs.strictTypeCheckedOnly], 333 | 334 | languageOptions: { 335 | parserOptions: { 336 | project: true, 337 | }, 338 | }, 339 | 340 | rules: { 341 | '@typescript-eslint/consistent-type-exports': 'error', 342 | '@typescript-eslint/no-base-to-string': 'off', 343 | '@typescript-eslint/no-unnecessary-type-conversion': 'error', 344 | '@typescript-eslint/no-unsafe-type-assertion': 'error', 345 | '@typescript-eslint/promise-function-async': 'error', 346 | '@typescript-eslint/require-array-sort-compare': 'error', 347 | '@typescript-eslint/strict-boolean-expressions': 'error', 348 | '@typescript-eslint/switch-exhaustiveness-check': 'error', 349 | '@typescript-eslint/no-unnecessary-condition': [ 350 | 'error', 351 | { 352 | allowConstantLoopConditions: 'only-allowed-literals', 353 | }, 354 | ], 355 | }, 356 | }); 357 | 358 | export const vitest = tseslint.config(vitestPlugin.configs.recommended, { 359 | files: ['**/*.{spec,test}.{js,ts,tsx}', '**/__tests__/**/*.{js,ts,tsx}'], 360 | 361 | rules: { 362 | 'vitest/consistent-test-it': ['error', { fn: 'test' }], 363 | 'vitest/expect-expect': 'error', 364 | 'vitest/no-disabled-tests': 'error', 365 | 'vitest/no-duplicate-hooks': 'error', 366 | 'vitest/no-test-prefixes': 'error', 367 | 'vitest/no-test-return-statement': 'error', 368 | 'vitest/prefer-to-be': 'error', 369 | 'vitest/prefer-todo': 'error', 370 | 'vitest/require-to-throw-message': 'error', 371 | }, 372 | }); 373 | 374 | export const jest = tseslint.config(jestPlugin.configs['flat/recommended'], { 375 | files: ['**/*.{spec,test}.{js,ts,tsx}', '**/__tests__/**/*.{js,ts,tsx}'], 376 | 377 | languageOptions: { 378 | globals: { 379 | ...globals.jest, 380 | ...jestPlugin.environments.globals.globals, 381 | }, 382 | }, 383 | 384 | rules: { 385 | ...jestPlugin.configs['flat/recommended'].rules, 386 | 387 | 'import-x/no-extraneous-dependencies': [ 388 | 'error', 389 | { 390 | devDependencies: true, 391 | }, 392 | ], 393 | 394 | 'jest/consistent-test-it': ['error', { fn: 'test' }], 395 | 'jest/expect-expect': 'error', 396 | 'jest/no-disabled-tests': 'error', 397 | 'jest/no-duplicate-hooks': 'error', 398 | 'jest/no-test-prefixes': 'error', 399 | 'jest/no-test-return-statement': 'error', 400 | 'jest/prefer-to-be': 'error', 401 | 'jest/prefer-todo': 'error', 402 | 'jest/require-to-throw-message': 'error', 403 | }, 404 | }); 405 | -------------------------------------------------------------------------------- /lefthook.yml: -------------------------------------------------------------------------------- 1 | pre-commit: 2 | parallel: true 3 | commands: 4 | test: 5 | glob: "*.{json,js,jsx,ts,tsx}" 6 | run: yarn test 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-config-satya164", 3 | "version": "5.1.3", 4 | "description": "Personal ESLint Config of @satya164", 5 | "license": "MIT", 6 | "main": "index.mjs", 7 | "files": [ 8 | "index.mjs" 9 | ], 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/satya164/eslint-config-satya164.git" 13 | }, 14 | "author": "Satyajit Sahoo (https://github.com/satya164/)", 15 | "publishConfig": { 16 | "registry": "https://registry.npmjs.org/" 17 | }, 18 | "scripts": { 19 | "release": "release-it --only-version", 20 | "test": "eslint --ext '.js,.ts,.tsx' __tests__/**" 21 | }, 22 | "dependencies": { 23 | "@eslint-community/eslint-plugin-eslint-comments": "^4.4.1", 24 | "@eslint-react/eslint-plugin": "^1.38.0", 25 | "@eslint/js": "^9.23.0", 26 | "@vitest/eslint-plugin": "^1.1.44", 27 | "eslint-config-prettier": "^10.1.1", 28 | "eslint-import-resolver-typescript": "^4.2.4", 29 | "eslint-plugin-import-x": "^4.9.3", 30 | "eslint-plugin-jest": "^28.11.0", 31 | "eslint-plugin-prettier": "^5.4.0", 32 | "eslint-plugin-promise": "^7.2.1", 33 | "eslint-plugin-react-hooks": "^5.2.0", 34 | "globals": "^16.1.0", 35 | "typescript-eslint": "^8.32.0" 36 | }, 37 | "devDependencies": { 38 | "@evilmartians/lefthook": "^1.11.12", 39 | "@release-it/conventional-changelog": "^10.0.1", 40 | "eslint": "^9.26.0", 41 | "jest": "^29.7.0", 42 | "prettier": "^3.5.3", 43 | "release-it": "^19.0.2", 44 | "typescript": "^5.8.3" 45 | }, 46 | "peerDependencies": { 47 | "eslint": "*", 48 | "prettier": "*" 49 | } 50 | } 51 | --------------------------------------------------------------------------------