├── .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 |
--------------------------------------------------------------------------------