@monholm/eslint-config 2.0.2 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,129 @@
1
+ # @monholm/eslint-config
2
+
3
+ Shared ESLint configurations using ESLint 9's flat config format.
4
+
5
+ ## Formatting
6
+
7
+ This ESLint config does not include Prettier rules. We recommend:
8
+
9
+ - Setting up format-on-save in your editor with Prettier
10
+ - Running `prettier --check` in CI
11
+
12
+ ## Getting Started
13
+
14
+ All configs in this package require TypeScript, meaning they can't be used in pure JavaScript projects.
15
+
16
+ ### Installation
17
+
18
+ ```bash
19
+ pnpm add -D @monholm/eslint-config eslint typescript typescript-eslint @eslint/js eslint-plugin-import @stylistic/eslint-plugin
20
+ ```
21
+
22
+ ### TypeScript Configuration
23
+
24
+ The ESLint configuration uses `projectService` which automatically finds and uses your `tsconfig.json`. For this to work correctly, **all files being linted must be included in the tsconfig**.
25
+
26
+ The recommended approach is to use your root `tsconfig.json` for linting by omitting the `include` option, which will make TypeScript target all files by default. Then create a separate `tsconfig.build.json` for compilation that includes only the source files.
27
+
28
+ `tsconfig.json` (used for linting and type checking)
29
+
30
+ ```json
31
+ {
32
+ "extends": "@monholm/tsconfig",
33
+ "exclude": ["node_modules", "dist"],
34
+ "compilerOptions": {"noEmit": true}
35
+ }
36
+ ```
37
+
38
+ `tsconfig.build.json` (used for building)
39
+
40
+ ```json
41
+ {
42
+ "extends": "./tsconfig.json",
43
+ "include": ["src"],
44
+ "compilerOptions": {"outDir": "dist", "noEmit": false}
45
+ }
46
+ ```
47
+
48
+ Then build with `tsc -p tsconfig.build.json` and type check with `tsc`.
49
+
50
+ If you need to use a different tsconfig setup, please reference the typescript-eslint documentation on how to set up `projectService` instead of using the example eslint config provided later in this readme.
51
+
52
+ ### Available Configs
53
+
54
+ This package uses [subpath exports](https://nodejs.org/api/packages.html#subpath-exports) in its `package.json`. You should import the configs as follows:
55
+
56
+ - **Base config:**
57
+ ```js
58
+ import {baseConfig} from '@monholm/eslint-config';
59
+ ```
60
+ - **Node.js config:**
61
+ ```js
62
+ import {nodeConfig} from '@monholm/eslint-config/node';
63
+ ```
64
+ - **React config:**
65
+ ```js
66
+ import {reactConfig} from '@monholm/eslint-config/react';
67
+ ```
68
+
69
+ ### Setup
70
+
71
+ #### Base Config (TypeScript)
72
+
73
+ `eslint.config.js`
74
+
75
+ ```js
76
+ import {baseConfig} from '@monholm/eslint-config';
77
+ import {defineConfig} from 'eslint/config';
78
+
79
+ export default defineConfig([
80
+ baseConfig,
81
+ {languageOptions: {parserOptions: {projectService: true}}},
82
+ ]);
83
+ ```
84
+
85
+ #### Node.js
86
+
87
+ This config extends from the base config. You'll need to install the Node.js ESLint plugin:
88
+
89
+ ```bash
90
+ pnpm add -D eslint-plugin-n
91
+ ```
92
+
93
+ For `eslint-plugin-n` to work correctly, **you must specify the `engines` field in your `package.json`** so it knows your target Node.js version:
94
+
95
+ ```json
96
+ {"engines": {"node": ">= 20"}}
97
+ ```
98
+
99
+ `eslint.config.js`
100
+
101
+ ```js
102
+ import {nodeConfig} from '@monholm/eslint-config/node';
103
+ import {defineConfig} from 'eslint/config';
104
+
105
+ export default defineConfig([
106
+ nodeConfig,
107
+ {languageOptions: {parserOptions: {projectService: true}}},
108
+ ]);
109
+ ```
110
+
111
+ #### React
112
+
113
+ This config extends from the base config. You'll need to install the React ESLint plugins:
114
+
115
+ ```bash
116
+ pnpm add -D eslint-plugin-react eslint-plugin-react-hooks
117
+ ```
118
+
119
+ `eslint.config.js`
120
+
121
+ ```js
122
+ import {reactConfig} from '@monholm/eslint-config/react';
123
+ import {defineConfig} from 'eslint/config';
124
+
125
+ export default defineConfig([
126
+ reactConfig,
127
+ {languageOptions: {parserOptions: {projectService: true}}},
128
+ ]);
129
+ ```
@@ -0,0 +1,2 @@
1
+ export declare const baseConfig: import("eslint/config").Config[];
2
+ //# sourceMappingURL=base.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../src/configs/base.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,UAAU,kCAQtB,CAAC"}
@@ -0,0 +1,10 @@
1
+ import eslint from '@eslint/js';
2
+ import tseslint from 'typescript-eslint';
3
+ import importPlugin from 'eslint-plugin-import';
4
+ import stylisticEslint from '@stylistic/eslint-plugin';
5
+ import { defineConfig } from 'eslint/config';
6
+ import { eslintRules } from '../rules/eslint.js';
7
+ import { tsEslintRules } from '../rules/tsEslint.js';
8
+ import { importRules } from '../rules/import.js';
9
+ import { stylisticEslintRules } from '../rules/stylisticEslint.js';
10
+ export const baseConfig = defineConfig(eslint.configs.recommended, tseslint.configs.strictTypeChecked, tseslint.configs.stylisticTypeChecked, { rules: eslintRules }, { rules: tsEslintRules }, { plugins: { import: importPlugin }, rules: importRules }, { plugins: { '@stylistic': stylisticEslint }, rules: stylisticEslintRules });
@@ -0,0 +1,2 @@
1
+ export declare const nodeConfig: import("eslint/config").Config[];
2
+ //# sourceMappingURL=node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/configs/node.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,UAAU,kCAGrB,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from 'eslint/config';
2
+ import nodePlugin from 'eslint-plugin-n';
3
+ import { nodeRules } from '../rules/node.js';
4
+ import { baseConfig } from './base.js';
5
+ export const nodeConfig = defineConfig(baseConfig, {
6
+ plugins: { n: nodePlugin },
7
+ rules: nodeRules,
8
+ });
@@ -0,0 +1,2 @@
1
+ export declare const reactConfig: import("eslint/config").Config[];
2
+ //# sourceMappingURL=react.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../../src/configs/react.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,WAAW,kCAIvB,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from 'eslint/config';
2
+ import reactPlugin from 'eslint-plugin-react';
3
+ import reactHooksPlugin from 'eslint-plugin-react-hooks';
4
+ import { reactRules } from '../rules/react.js';
5
+ import { reactHooksRules } from '../rules/reactHooks.js';
6
+ import { baseConfig } from './base.js';
7
+ export const reactConfig = defineConfig(baseConfig, { plugins: { react: reactPlugin }, rules: reactRules }, { plugins: { 'react-hooks': reactHooksPlugin }, rules: reactHooksRules });
@@ -0,0 +1,3 @@
1
+ import type { Config } from 'eslint/config';
2
+ export declare const eslintRules: NonNullable<Config['rules']>;
3
+ //# sourceMappingURL=eslint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eslint.d.ts","sourceRoot":"","sources":["../../src/rules/eslint.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,eAAe,CAAC;AAE1C,eAAO,MAAM,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,CAkJpD,CAAC"}
@@ -0,0 +1,142 @@
1
+ export const eslintRules = {
2
+ 'arrow-body-style': ['error', 'as-needed'],
3
+ 'default-case-last': 'error',
4
+ 'default-case': 'error',
5
+ 'dot-notation': 'error',
6
+ 'func-names': 'error',
7
+ 'grouped-accessor-pairs': ['error', 'getBeforeSet'],
8
+ 'new-cap': 'error',
9
+ 'no-alert': 'error',
10
+ 'no-await-in-loop': 'error',
11
+ 'no-bitwise': 'error',
12
+ 'no-caller': 'error',
13
+ 'no-continue': 'error',
14
+ 'no-div-regex': 'error',
15
+ 'no-duplicate-imports': 'error',
16
+ 'no-else-return': 'error',
17
+ 'no-eval': 'error',
18
+ 'no-extend-native': 'error',
19
+ 'no-extra-bind': 'error',
20
+ 'no-extra-label': 'error',
21
+ 'no-implicit-coercion': ['error', { allow: ['!!'] }],
22
+ 'no-implicit-globals': 'error',
23
+ 'no-implied-eval': 'error',
24
+ 'no-inner-declarations': [
25
+ 'error',
26
+ 'both',
27
+ { blockScopedFunctions: 'disallow' },
28
+ ],
29
+ 'no-iterator': 'error',
30
+ 'no-label-var': 'error',
31
+ 'no-labels': 'error',
32
+ 'no-lone-blocks': 'error',
33
+ 'no-lonely-if': 'error',
34
+ 'no-multi-assign': 'error',
35
+ 'no-multi-str': 'error',
36
+ 'no-negated-condition': 'error',
37
+ 'no-nested-ternary': 'error',
38
+ 'no-new-func': 'error',
39
+ 'no-new-wrappers': 'error',
40
+ 'no-new': 'error',
41
+ 'no-object-constructor': 'error',
42
+ 'no-octal-escape': 'error',
43
+ 'no-param-reassign': 'error',
44
+ 'no-plusplus': 'error',
45
+ 'no-promise-executor-return': 'error',
46
+ 'no-proto': 'error',
47
+ 'no-return-assign': 'error',
48
+ 'no-script-url': 'error',
49
+ 'no-sequences': 'error',
50
+ 'no-throw-literal': 'error',
51
+ 'no-undef-init': 'error',
52
+ 'no-unmodified-loop-condition': 'error',
53
+ 'no-unneeded-ternary': ['error', { defaultAssignment: false }],
54
+ 'no-unreachable-loop': 'error',
55
+ 'no-useless-call': 'error',
56
+ 'no-useless-computed-key': 'error',
57
+ 'no-useless-concat': 'error',
58
+ 'no-useless-constructor': 'error',
59
+ 'no-useless-rename': 'error',
60
+ 'no-useless-return': 'error',
61
+ 'no-var': 'error',
62
+ 'no-void': ['error', { allowAsStatement: false }],
63
+ 'no-warning-comments': 'error',
64
+ 'object-shorthand': 'error',
65
+ 'prefer-arrow-callback': 'error',
66
+ 'prefer-const': ['error', { destructuring: 'all' }],
67
+ 'prefer-exponentiation-operator': 'error',
68
+ 'prefer-named-capture-group': 'error',
69
+ 'prefer-numeric-literals': 'error',
70
+ 'prefer-object-has-own': 'error',
71
+ 'prefer-object-spread': 'error',
72
+ 'prefer-promise-reject-errors': 'error',
73
+ 'prefer-regex-literals': ['error', { disallowRedundantWrapping: true }],
74
+ 'prefer-rest-params': 'error',
75
+ 'prefer-spread': 'error',
76
+ 'prefer-template': 'error',
77
+ 'preserve-caught-error': ['error', { requireCatchParameter: true }],
78
+ 'require-unicode-regexp': 'error',
79
+ 'symbol-description': 'error',
80
+ curly: ['error', 'multi-line', 'consistent'],
81
+ eqeqeq: 'error',
82
+ radix: 'error',
83
+ strict: ['error', 'global'],
84
+ yoda: 'error',
85
+ 'no-restricted-properties': [
86
+ 'error',
87
+ {
88
+ object: 'global',
89
+ property: 'isFinite',
90
+ message: 'Please use `Number.isFinite` instead.',
91
+ },
92
+ {
93
+ object: 'self',
94
+ property: 'isFinite',
95
+ message: 'Please use `Number.isFinite` instead.',
96
+ },
97
+ {
98
+ object: 'window',
99
+ property: 'isFinite',
100
+ message: 'Please use `Number.isFinite` instead.',
101
+ },
102
+ {
103
+ object: 'global',
104
+ property: 'isNaN',
105
+ message: 'Please use `Number.isNaN` instead.',
106
+ },
107
+ {
108
+ object: 'self',
109
+ property: 'isNaN',
110
+ message: 'Please use `Number.isNaN` instead.',
111
+ },
112
+ {
113
+ object: 'window',
114
+ property: 'isNaN',
115
+ message: 'Please use `Number.isNaN` instead.',
116
+ },
117
+ ],
118
+ 'no-restricted-syntax': [
119
+ 'error',
120
+ {
121
+ selector: 'ForInStatement',
122
+ message: 'for..in loops iterate over the entire prototype chain, which is most likely an error. Use `for..of` or `Object.values` instead.',
123
+ },
124
+ {
125
+ selector: 'MemberExpression[object.name="JSON"][property.name="parse"]',
126
+ message: '`JSON.parse` returns `any` type. Use `jsonParse` from `@monholm/util` instead',
127
+ },
128
+ {
129
+ selector: 'MemberExpression[object.name="Array"][property.name="isArray"]',
130
+ message: '`Array.isArray` returns `any[]` type. Use `isArray` from `@monholm/util` instead',
131
+ },
132
+ {
133
+ selector: 'Identifier[name="Omit"]',
134
+ message: '`Omit` is not type safe (does not show any error if omitting a key that does not exist). Use `Except` from `type-fest` instead',
135
+ },
136
+ ],
137
+ 'no-restricted-globals': [
138
+ 'error',
139
+ { name: 'isFinite', message: 'Use `Number.isFinite` instead.' },
140
+ { name: 'isNaN', message: 'Use `Number.isNaN` instead.' },
141
+ ],
142
+ };
@@ -0,0 +1,3 @@
1
+ import type { Config } from 'eslint/config';
2
+ export declare const importRules: NonNullable<Config['rules']>;
3
+ //# sourceMappingURL=import.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"import.d.ts","sourceRoot":"","sources":["../../src/rules/import.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,eAAe,CAAC;AAE1C,eAAO,MAAM,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,CAiDpD,CAAC"}
@@ -0,0 +1,50 @@
1
+ export const importRules = {
2
+ 'import/first': 'error',
3
+ 'import/no-empty-named-blocks': 'error',
4
+ 'import/no-extraneous-dependencies': [
5
+ 'error',
6
+ {
7
+ // globs where importing from devDependencies is allowed
8
+ devDependencies: [
9
+ 'test/**',
10
+ 'tests/**',
11
+ 'spec/**',
12
+ '**/__tests__/**',
13
+ '**/__mocks__/**',
14
+ '**/tests/**',
15
+ '**/spec/**',
16
+ '**/test/**',
17
+ '**/*{.,_}{test,spec}.{js,jsx,ts,tsx}',
18
+ '**/eslint.config.{js,cjs,mjs}',
19
+ '**/prettier.config.{js,cjs,mjs}',
20
+ '**/vite.config.ts',
21
+ '**/vitest.config.ts',
22
+ '**/playwright.config.ts',
23
+ ],
24
+ optionalDependencies: false,
25
+ bundledDependencies: false,
26
+ },
27
+ ],
28
+ 'import/no-mutable-exports': 'error',
29
+ 'import/no-named-as-default': 'error',
30
+ 'import/no-named-as-default-member': 'error',
31
+ 'import/enforce-node-protocol-usage': ['error', 'always'],
32
+ 'import/no-absolute-path': 'error',
33
+ /**
34
+ * `ignoreExternal` decides if files imported from node_modules are also checked.
35
+ * This goes against the general rule that linting shouldn't happen for dependencies,
36
+ * and the example shown in the `eslint-plugin-import` documentation is unlikely to occur.
37
+ *
38
+ * This has the added benefit of a quicker lint, and in the case of react-native,
39
+ * linting can succeed since no react-native/*.js files (written in flow) have to be parsed
40
+ * by `eslint-plugin-import`.
41
+ */
42
+ 'import/no-cycle': ['error', { maxDepth: '∞', ignoreExternal: true }],
43
+ 'import/no-dynamic-require': 'error',
44
+ 'import/no-relative-packages': 'error',
45
+ 'import/no-self-import': 'error',
46
+ 'import/no-useless-path-segments': 'error',
47
+ 'import/newline-after-import': 'error',
48
+ 'import/no-unassigned-import': 'error',
49
+ 'import/order': 'error',
50
+ };
@@ -0,0 +1,3 @@
1
+ import type { Config } from 'eslint/config';
2
+ export declare const nodeRules: NonNullable<Config['rules']>;
3
+ //# sourceMappingURL=node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/rules/node.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,eAAe,CAAC;AAE1C,eAAO,MAAM,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,CA2BlD,CAAC"}
@@ -0,0 +1,28 @@
1
+ export const nodeRules = {
2
+ 'n/callback-return': 'error',
3
+ 'n/exports-style': 'error',
4
+ 'n/hashbang': 'error',
5
+ 'n/no-deprecated-api': 'error',
6
+ 'n/no-exports-assign': 'error',
7
+ 'n/no-path-concat': 'error',
8
+ 'n/no-process-exit': 'error',
9
+ 'n/no-sync': 'error',
10
+ 'n/no-unpublished-bin': 'error',
11
+ // eslint-disable-next-line @stylistic/multiline-comment-style
12
+ // 'n/no-unpublished-import': 'error', // Flags devDependencies as well (which duplicates import/no-extraneous-dependencies) and have no option to disable it.
13
+ // 'n/no-unpublished-require': 'error', // Flags devDependencies as well (which duplicates import/no-extraneous-dependencies) and have no option to disable it.
14
+ 'n/no-unsupported-features/es-builtins': 'error',
15
+ 'n/no-unsupported-features/es-syntax': 'error',
16
+ 'n/no-unsupported-features/node-builtins': 'error',
17
+ 'n/prefer-global/buffer': 'error',
18
+ 'n/prefer-global/console': 'error',
19
+ 'n/prefer-global/process': 'error',
20
+ 'n/prefer-global/text-decoder': 'error',
21
+ 'n/prefer-global/text-encoder': 'error',
22
+ 'n/prefer-global/url': 'error',
23
+ 'n/prefer-global/url-search-params': 'error',
24
+ // 'n/prefer-node-protocol': 'error', // Duplicate of import/enforce-node-protocol-usage
25
+ 'n/prefer-promises/dns': 'error',
26
+ 'n/prefer-promises/fs': 'error',
27
+ 'n/process-exit-as-throw': 'error',
28
+ };
@@ -0,0 +1,3 @@
1
+ import type { Config } from 'eslint/config';
2
+ export declare const reactRules: NonNullable<Config['rules']>;
3
+ //# sourceMappingURL=react.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../../src/rules/react.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,eAAe,CAAC;AAE1C,eAAO,MAAM,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,CAkInD,CAAC"}
@@ -0,0 +1,131 @@
1
+ export const reactRules = {
2
+ 'react/button-has-type': 'error',
3
+ 'react/checked-requires-onchange-or-readonly': 'error',
4
+ 'react/default-props-match-prop-types': 'error',
5
+ 'react/display-name': 'error',
6
+ 'react/forward-ref-uses-ref': 'error',
7
+ 'react/hook-use-state': 'error',
8
+ 'react/iframe-missing-sandbox': 'error',
9
+ 'react/jsx-boolean-value': 'error',
10
+ 'react/jsx-child-element-spacing': 'error',
11
+ 'react/jsx-closing-tag-location': 'error',
12
+ 'react/jsx-curly-brace-presence': [
13
+ 'error',
14
+ { props: 'never', children: 'never', propElementValues: 'always' },
15
+ ],
16
+ 'react/jsx-equals-spacing': 'error',
17
+ 'react/jsx-filename-extension': [
18
+ 'error',
19
+ {
20
+ allow: 'as-needed',
21
+ extensions: ['.jsx', '.tsx'],
22
+ ignoreFilesWithoutCode: true,
23
+ },
24
+ ],
25
+ 'react/jsx-fragments': 'error',
26
+ 'react/jsx-key': [
27
+ 'error',
28
+ {
29
+ checkFragmentShorthand: true,
30
+ checkKeyMustBeforeSpread: true,
31
+ warnOnDuplicates: true,
32
+ },
33
+ ],
34
+ 'react/jsx-no-bind': [
35
+ 'error',
36
+ {
37
+ ignoreRefs: true,
38
+ allowArrowFunctions: true,
39
+ allowFunctions: false,
40
+ allowBind: false,
41
+ ignoreDOMComponents: true,
42
+ },
43
+ ],
44
+ 'react/jsx-no-comment-textnodes': 'error',
45
+ 'react/jsx-no-constructed-context-values': 'error',
46
+ 'react/jsx-no-duplicate-props': 'error',
47
+ /*
48
+ * jsx-no-leaked-render isn't type aware, meaning it report errors even on boolean expressions.
49
+ * To use it, you have to always use `!!` or `Boolean()` to cast your expressions to booleans,
50
+ * which in turn will cause @typescript-eslint/no-unnecessary-type-conversion to report errors.
51
+ * So instead of using it, we stricten our use of strict-boolean-expressions to disallow anything besides booleans
52
+ * in react projects.
53
+ */
54
+ 'react/jsx-no-leaked-render': 'off',
55
+ '@typescript-eslint/strict-boolean-expressions': [
56
+ 'error',
57
+ {
58
+ allowAny: false,
59
+ allowNullableBoolean: false,
60
+ allowNullableEnum: false,
61
+ allowNullableNumber: false,
62
+ allowNullableObject: false,
63
+ allowNullableString: false,
64
+ allowNumber: false,
65
+ allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing: false,
66
+ allowString: false,
67
+ },
68
+ ],
69
+ 'react/jsx-no-script-url': 'error',
70
+ 'react/jsx-no-target-blank': [
71
+ 'error',
72
+ { enforceDynamicLinks: 'always', warnOnSpreadAttributes: true },
73
+ ],
74
+ 'react/jsx-no-undef': 'error',
75
+ 'react/jsx-no-useless-fragment': 'error',
76
+ 'react/jsx-pascal-case': 'error',
77
+ 'react/jsx-props-no-multi-spaces': 'error',
78
+ 'react/jsx-props-no-spread-multi': 'error',
79
+ 'react/jsx-tag-spacing': [
80
+ 'error',
81
+ {
82
+ closingSlash: 'never',
83
+ beforeSelfClosing: 'always',
84
+ afterOpening: 'never',
85
+ beforeClosing: 'never',
86
+ },
87
+ ],
88
+ 'react/jsx-uses-vars': 'error',
89
+ 'react/no-access-state-in-setstate': 'error',
90
+ 'react/no-array-index-key': 'error',
91
+ 'react/no-arrow-function-lifecycle': 'error',
92
+ 'react/no-children-prop': 'error',
93
+ 'react/no-danger': 'error',
94
+ 'react/no-danger-with-children': 'error',
95
+ 'react/no-deprecated': 'error',
96
+ 'react/no-did-mount-set-state': ['error', 'disallow-in-func'],
97
+ 'react/no-did-update-set-state': ['error', 'disallow-in-func'],
98
+ 'react/no-direct-mutation-state': 'error',
99
+ 'react/no-find-dom-node': 'error',
100
+ 'react/no-invalid-html-attribute': 'error',
101
+ 'react/no-is-mounted': 'error',
102
+ 'react/no-namespace': 'error',
103
+ 'react/no-object-type-as-default-prop': 'error',
104
+ 'react/no-redundant-should-component-update': 'error',
105
+ 'react/no-render-return-value': 'error',
106
+ 'react/no-string-refs': ['error', { noTemplateLiterals: true }],
107
+ 'react/no-this-in-sfc': 'error',
108
+ 'react/no-typos': 'error',
109
+ 'react/no-unescaped-entities': 'error',
110
+ 'react/no-unknown-property': ['error', { requireDataLowercase: true }],
111
+ 'react/no-unstable-nested-components': 'error',
112
+ 'react/no-unused-class-component-methods': 'error',
113
+ 'react/no-unused-prop-types': 'error',
114
+ 'react/no-unused-state': 'error',
115
+ 'react/no-will-update-set-state': ['error', 'disallow-in-func'],
116
+ 'react/prefer-es6-class': 'error',
117
+ 'react/prefer-exact-props': 'error',
118
+ 'react/prefer-stateless-function': 'error',
119
+ 'react/prop-types': 'error',
120
+ 'react/require-render-return': 'error',
121
+ 'react/self-closing-comp': 'error',
122
+ 'react/state-in-constructor': 'error',
123
+ 'react/void-dom-elements-no-children': 'error',
124
+ 'react/function-component-definition': [
125
+ 'error',
126
+ {
127
+ namedComponents: ['function-declaration', 'arrow-function'],
128
+ unnamedComponents: 'arrow-function',
129
+ },
130
+ ],
131
+ };
@@ -0,0 +1,3 @@
1
+ import type { Config } from 'eslint/config';
2
+ export declare const reactHooksRules: NonNullable<Config['rules']>;
3
+ //# sourceMappingURL=reactHooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reactHooks.d.ts","sourceRoot":"","sources":["../../src/rules/reactHooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,eAAe,CAAC;AAE1C,eAAO,MAAM,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,CAoBxD,CAAC"}
@@ -0,0 +1,20 @@
1
+ export const reactHooksRules = {
2
+ 'react-hooks/rules-of-hooks': 'error',
3
+ 'react-hooks/exhaustive-deps': 'error',
4
+ // React Compiler rules
5
+ 'react-hooks/config': 'error',
6
+ 'react-hooks/error-boundaries': 'error',
7
+ 'react-hooks/component-hook-factories': 'error',
8
+ 'react-hooks/gating': 'error',
9
+ 'react-hooks/globals': 'error',
10
+ 'react-hooks/immutability': 'error',
11
+ 'react-hooks/preserve-manual-memoization': 'error',
12
+ 'react-hooks/purity': 'error',
13
+ 'react-hooks/refs': 'error',
14
+ 'react-hooks/set-state-in-effect': 'error',
15
+ 'react-hooks/set-state-in-render': 'error',
16
+ 'react-hooks/static-components': 'error',
17
+ 'react-hooks/unsupported-syntax': 'error',
18
+ 'react-hooks/use-memo': 'error',
19
+ 'react-hooks/incompatible-library': 'error',
20
+ };
@@ -0,0 +1,3 @@
1
+ import type { Config } from 'eslint/config';
2
+ export declare const stylisticEslintRules: NonNullable<Config['rules']>;
3
+ //# sourceMappingURL=stylisticEslint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stylisticEslint.d.ts","sourceRoot":"","sources":["../../src/rules/stylisticEslint.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,eAAe,CAAC;AAE1C,eAAO,MAAM,oBAAoB,EAAE,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,CAmB7D,CAAC"}
@@ -0,0 +1,20 @@
1
+ export const stylisticEslintRules = {
2
+ /*
3
+ * From https://eslint.style/rules/jsx-curly-brace-presence:
4
+ *
5
+ * Note: it is highly recommended that you configure this rule with an object,
6
+ * and that you set "propElementValues" to "always".
7
+ * The ability to omit curly braces around prop values that are JSX elements is obscure,
8
+ * and intentionally undocumented, and should not be relied upon.
9
+ */
10
+ '@stylistic/jsx-curly-brace-presence': [
11
+ 'error',
12
+ { props: 'never', children: 'never', propElementValues: 'always' },
13
+ ],
14
+ '@stylistic/jsx-pascal-case': 'error',
15
+ '@stylistic/jsx-self-closing-comp': 'error',
16
+ '@stylistic/linebreak-style': ['error', 'unix'],
17
+ '@stylistic/multiline-comment-style': 'error',
18
+ '@stylistic/spaced-comment': 'error',
19
+ '@stylistic/wrap-iife': ['error', 'inside'],
20
+ };
@@ -0,0 +1,3 @@
1
+ import type { Config } from 'eslint/config';
2
+ export declare const tsEslintRules: NonNullable<Config['rules']>;
3
+ //# sourceMappingURL=tsEslint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tsEslint.d.ts","sourceRoot":"","sources":["../../src/rules/tsEslint.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,eAAe,CAAC;AAE1C,eAAO,MAAM,aAAa,EAAE,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,CAsDtD,CAAC"}
@@ -0,0 +1,55 @@
1
+ export const tsEslintRules = {
2
+ '@typescript-eslint/consistent-type-exports': [
3
+ 'error',
4
+ { fixMixedExportsWithInlineTypeSpecifier: true },
5
+ ],
6
+ 'class-methods-use-this': 'off',
7
+ '@typescript-eslint/class-methods-use-this': 'error',
8
+ '@typescript-eslint/consistent-type-imports': [
9
+ 'error',
10
+ { fixStyle: 'inline-type-imports' },
11
+ ],
12
+ 'default-param-last': 'off',
13
+ '@typescript-eslint/default-param-last': 'error',
14
+ 'max-params': 'off',
15
+ '@typescript-eslint/max-params': ['error', { max: 3 }],
16
+ '@typescript-eslint/method-signature-style': 'error',
17
+ '@typescript-eslint/no-import-type-side-effects': 'error',
18
+ 'no-loop-func': 'off',
19
+ '@typescript-eslint/no-loop-func': 'error',
20
+ 'no-shadow': 'off',
21
+ '@typescript-eslint/no-shadow': ['error', { hoist: 'all' }],
22
+ '@typescript-eslint/no-unnecessary-qualifier': 'error',
23
+ '@typescript-eslint/no-unsafe-type-assertion': 'error',
24
+ 'no-unused-private-class-members': 'off',
25
+ '@typescript-eslint/no-unused-private-class-members': 'error',
26
+ 'no-use-before-define': 'off',
27
+ '@typescript-eslint/no-use-before-define': [
28
+ 'error',
29
+ { functions: false, classes: true, variables: false },
30
+ ],
31
+ '@typescript-eslint/no-useless-empty-export': 'error',
32
+ 'prefer-destructuring': 'off',
33
+ '@typescript-eslint/prefer-destructuring': 'error',
34
+ '@typescript-eslint/promise-function-async': 'error',
35
+ '@typescript-eslint/require-array-sort-compare': 'error',
36
+ '@typescript-eslint/strict-boolean-expressions': [
37
+ 'error',
38
+ { allowString: false, allowNumber: false },
39
+ ],
40
+ '@typescript-eslint/switch-exhaustiveness-check': 'error',
41
+ '@typescript-eslint/no-unused-vars': [
42
+ 'error',
43
+ { argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
44
+ ],
45
+ '@typescript-eslint/restrict-template-expressions': [
46
+ 'error',
47
+ { allowNumber: true, allowBoolean: true },
48
+ ],
49
+ '@typescript-eslint/no-misused-promises': [
50
+ 'error',
51
+ { checksVoidReturn: { attributes: false } },
52
+ ],
53
+ 'no-unused-expressions': 'off',
54
+ '@typescript-eslint/no-unused-expressions': ['error', { enforceForJSX: true }],
55
+ };
package/package.json CHANGED
@@ -6,30 +6,55 @@
6
6
  "directory": "packages/eslint-config"
7
7
  },
8
8
  "type": "module",
9
- "exports": "./dist/index.js",
10
- "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": "./dist/configs/base.js",
11
+ "./node": "./dist/configs/node.js",
12
+ "./react": "./dist/configs/react.js"
13
+ },
11
14
  "files": [
12
15
  "dist"
13
16
  ],
14
17
  "peerDependencies": {
15
- "eslint": ">= 9.22.0",
18
+ "@eslint/js": ">= 9.35.0",
19
+ "@stylistic/eslint-plugin": ">= 5.6.1",
20
+ "eslint": ">= 9.35.0",
21
+ "eslint-plugin-import": ">= 2.32.0",
22
+ "eslint-plugin-n": ">= 17.23.1",
23
+ "eslint-plugin-react": ">= 7.37.5",
24
+ "eslint-plugin-react-hooks": ">= 6.1.1",
16
25
  "typescript": ">= 5.8.0",
17
- "typescript-eslint": ">= 8.0.0",
18
- "@eslint/js": ">= 9.22.0"
26
+ "typescript-eslint": ">= 8.0.0"
27
+ },
28
+ "peerDependenciesMeta": {
29
+ "eslint-plugin-react": {
30
+ "optional": true
31
+ },
32
+ "eslint-plugin-react-hooks": {
33
+ "optional": true
34
+ },
35
+ "eslint-plugin-n": {
36
+ "optional": true
37
+ }
19
38
  },
20
39
  "devDependencies": {
21
- "eslint": "9.39.1",
22
- "@eslint/js": "9.39.1",
23
- "typescript-eslint": "8.47.0",
24
- "prettier": "3.6.2",
40
+ "@eslint/js": "9.39.2",
41
+ "@stylistic/eslint-plugin": "5.8.0",
42
+ "eslint": "9.39.2",
43
+ "eslint-plugin-import": "2.32.0",
44
+ "eslint-plugin-n": "17.23.2",
45
+ "eslint-plugin-react": "7.37.5",
46
+ "eslint-plugin-react-hooks": "6.1.1",
47
+ "prettier": "3.8.1",
25
48
  "typescript": "5.9.3",
26
- "@monholm/prettier-config": "2.0.1",
27
- "@monholm/tsconfig": "2.1.0"
49
+ "typescript-eslint": "8.55.0",
50
+ "@monholm/prettier-config": "2.0.2",
51
+ "@monholm/tsconfig": "2.2.0"
28
52
  },
29
- "version": "2.0.2",
53
+ "version": "3.0.0",
30
54
  "scripts": {
31
55
  "build": "tsc -p tsconfig.build.json",
32
56
  "check": "tsc",
57
+ "lint": "eslint . --max-warnings 0",
33
58
  "format-check": "prettier . --check --cache-location=_unused",
34
59
  "format": "prettier . --write --cache-location=_unused"
35
60
  }
package/dist/index.d.ts DELETED
@@ -1,2 +0,0 @@
1
- declare const _default: import("eslint/config").Config[];
2
- export default _default;
package/dist/index.js DELETED
@@ -1,4 +0,0 @@
1
- import eslint from '@eslint/js';
2
- import tseslint from 'typescript-eslint';
3
- import { defineConfig } from 'eslint/config';
4
- export default defineConfig(eslint.configs.recommended, tseslint.configs.strictTypeChecked, tseslint.configs.stylisticTypeChecked);