@fullstacksjs/eslint-config 11.8.1 → 11.10.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 CHANGED
@@ -16,29 +16,37 @@
16
16
  $ npm install --save-dev @fullstacksjs/eslint-config eslint prettier
17
17
  ```
18
18
 
19
+ ⚡️ That's it, you don't need to install any eslint-plugin! If you already have some plugins installed, please remove them.
20
+
19
21
  ## Usage
20
22
 
21
- To use the configuration, all you need is to export the generated config by the `init` function. The configuration reads the metadata from your root `package.json` file and automatically adds the rules and plugins that are needed.
23
+ To use the configuration, all you need is to export the generated config by the `defineConfig` function. It automatically enables required plugins with **Auto Module Detection**.
22
24
 
23
25
  ### ESM
24
26
 
25
27
  ```js
26
- import { init } from '@fullstacksjs/eslint-config';
28
+ import { defineConfig } from '@fullstacksjs/eslint-config';
27
29
 
28
- export default init();
30
+ export default defineConfig();
29
31
  ```
30
32
 
31
33
  ### CJS
32
34
 
33
35
  ```js
34
- const { init } = require('@fullstacksjs/eslint-config');
36
+ const { defineConfig } = require('@fullstacksjs/eslint-config');
35
37
 
36
- module.exports = init();
38
+ module.exports = defineConfig();
37
39
  ```
38
40
 
39
- ## Modules API
41
+ ## How Module Detection Works
42
+
43
+ Automatic module detection enables ESLint plugins based on the dependencies listed in your `package.json`. It scans your project to identify which tools you're using, and then activates the corresponding ESLint plugins accordingly.
44
+
45
+ For example, if your `package.json` includes `vitest` as a dependency, the configuration will automatically enable `eslint-plugin-vitest` for you. (You don't need to install the plugin itself)
40
46
 
41
- You can fine-tune module detection by overriding it, the `init` function accepts options as its first argument to control enabled modules.
47
+ ### Modules API
48
+
49
+ You can fine-tune module detection by overriding it, the `defineConfig` function accepts options as its first argument to control enabled modules.
42
50
 
43
51
  ```typescript
44
52
  interface Options {
@@ -69,14 +77,14 @@ interface Options {
69
77
  }
70
78
  ```
71
79
 
72
- ## Extra configuration
80
+ ## What if I want to add my one rules
73
81
 
74
- You can pass any number of arbitrary custom config overrides to `init` function:
82
+ You can pass any number of arbitrary custom config overrides to `defineConfig` function:
75
83
 
76
84
  ```js
77
- import { init } from '@fullstacksjs/eslint-config';
85
+ import { defineConfig } from '@fullstacksjs/eslint-config';
78
86
 
79
- export default init(
87
+ export default defineConfig(
80
88
  {
81
89
  typescript: true,
82
90
  // You can pass extends here
@@ -93,6 +101,26 @@ export default init(
93
101
  })
94
102
  ```
95
103
 
104
+ ## What If I Want to Use a Plugin That Isn't Supported?
105
+
106
+ You can still use any ESLint plugin, even if it's not supported by automatic detection. Simply install the plugin manually and add it to the `defineConfig`. There are no limitations.
107
+
108
+ ```js
109
+ import { defineConfig } from '@fullstacksjs/eslint-config';
110
+ import pluginVue from 'eslint-plugin-vue';
111
+
112
+ export default defineConfig(
113
+ { typescript: true },
114
+ ...pluginVue.configs['flat/recommended']
115
+ )
116
+ ```
117
+
118
+ ## I'm Getting a Next.js Warning: Plugin Was Not Detected
119
+
120
+ This configuration includes built-in support for [Next.js](https://nextjs.org). The warning you're seeing from Next.js is misleading—it simply checks whether the plugin is explicitly listed in your package.json.
121
+
122
+ You can safely ignore this warning. Alternatively, you can update your lint script from next lint to eslint to avoid it entirely.
123
+
96
124
  ## Speed Optimization!
97
125
 
98
126
  Balancing the benefits of linting rules against their performance impact is crucial. Below is a table highlighting the most resource-intensive linting rules encountered in a real-world React project:
@@ -110,20 +138,20 @@ To conditionally disable expensive linting rules, you can modify your configurat
110
138
 
111
139
  List of `expensiveRules` to be affected:
112
140
 
113
- ```
141
+ ```sh
114
142
  @typescript-eslint/no-floating-promises
115
143
  @typescript-eslint/no-misused-promises
116
- import/default
117
- import/export
118
- import/named
144
+ import/default # (disabled in TS env)
145
+ import/export # (disabled in TS env)
146
+ import/named # (disabled in TS env)
147
+ import/no-named-as-default-member # (disabled in TS env)
119
148
  import/namespace
120
149
  import/no-cycle
121
150
  import/no-deprecated
122
- import/no-named-as-default-member
123
151
  ```
124
152
 
125
153
  ```js
126
- export default init({
154
+ export default defineConfig({
127
155
  disableExpensiveRules: !process.env.CI || !process.env.HUSKY // Or anywhere you want
128
156
  prettier: false // So you should run the formatter explicitly.
129
157
  });
@@ -131,49 +159,22 @@ export default init({
131
159
 
132
160
  ## Migration Guide
133
161
 
134
- ### To v11
135
-
136
- v11 drops support for ESLint v8 configuration and only ESLint v9 is supported, which means you should migrate to [ESlint Flat Config](https://eslint.org/docs/latest/extend/plugin-migration-flat-config):
137
-
138
- 1. Move your configs to `eslint.config.js` file.
139
- 2. Use init API.
140
- ```diff
141
- -const { init } = require('@fullstacksjs/eslint-config/init');
142
- +import { init } from '@fullstacksjs/eslint-config';
143
-
144
- -module.exports = init({
145
- - modules: {
146
- - auto: true,
147
- - esm: true,
148
- - typescript: {
149
- - parserProject: ['./tsconfig.eslint.json'],
150
- - resolverProject: ['./tsconfig.json'],
151
- - },
152
- - },
153
- - // your configuration
154
- -});
155
- +export default init({
156
- + esm: true,
157
- + typescript: true,
158
- + },
159
- + // your configuration
160
- +);
161
- ```
162
+ [See Migration Guide](./MIGRATION.md)
162
163
 
163
164
  ## What's included?
164
165
 
165
166
  * [@eslint-react/eslint-plugin](https://eslint-react.xyz/)
166
- * [@next/eslint-config-next](https://nextjs.org/docs/basic-features/eslint#eslint-plugin)
167
+ * [@next/eslint-plugin-next](https://nextjs.org/docs/basic-features/eslint#eslint-plugin)
168
+ * [@stylistic/eslint-plugin](https://eslint.style/packages/default)
167
169
  * [eslint-plugin-cypress](https://github.com/cypress-io/eslint-plugin-cypress)
168
170
  * [eslint-plugin-import-x](https://github.com/un-ts/eslint-plugin-import-x)
169
171
  * [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest)
170
172
  * [eslint-plugin-jest-formatting](https://github.com/dangreenisrael/eslint-plugin-jest-formatting)
171
173
  * [eslint-plugin-jsx-a11y](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y)
172
- * [eslint-plugin-perfectionist](https://perfectionist.dev/)
173
174
  * [eslint-plugin-n](https://github.com/eslint-community/eslint-plugin-n)
175
+ * [eslint-plugin-perfectionist](https://perfectionist.dev/)
174
176
  * [eslint-plugin-playwright](https://github.com/playwright-community/eslint-plugin-playwright)
175
177
  * [eslint-plugin-prettier](https://github.com/prettier/eslint-plugin-prettier)
176
- * [eslint-plugin-perfectionist](https://perfectionist.dev/)
177
178
  * [eslint-plugin-promise](https://github.com/eslint-community/eslint-plugin-promise)
178
179
  * [eslint-plugin-react-hooks](https://www.npmjs.com/package/eslint-plugin-react-hooks)
179
180
  * [eslint-plugin-storybook](https://github.com/storybookjs/eslint-plugin-storybook#readme)
package/cjs/index.js CHANGED
@@ -3,7 +3,8 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.init = init;
6
+ exports.defineConfig = defineConfig;
7
+ exports.init = void 0;
7
8
  var _deepmerge = _interopRequireDefault(require("deepmerge"));
8
9
  var _config = require("eslint/config");
9
10
  var _localPkg = require("local-pkg");
@@ -62,7 +63,7 @@ const defaultOptions = {
62
63
  * @param {...Config} extend
63
64
  * @returns {Config[]}
64
65
  */
65
- function init(initOptions = {}, ...extend) {
66
+ function defineConfig(initOptions = {}, ...extend) {
66
67
  const options = (0, _deepmerge.default)(defaultOptions, initOptions);
67
68
  if (options.tailwind === true) {
68
69
  options.tailwind = {};
@@ -113,3 +114,6 @@ function init(initOptions = {}, ...extend) {
113
114
  if (extend) Array.prototype.push.apply(rules, extend);
114
115
  return (0, _config.defineConfig)(rules);
115
116
  }
117
+
118
+ /** @deprecated Please use `defineConfig` from `@fullstacksjs/eslint-config` instead, this function will be removed in the next major release */
119
+ const init = exports.init = defineConfig;
@@ -17,20 +17,6 @@ const allExtensions = [...jsExtensions, ...tsExtensions];
17
17
  */
18
18
  function imports(options = {}) {
19
19
  const isObject = typeof options.import === 'object';
20
- const opt = options.esm ? 'always' : 'never';
21
- const extensionOptions = {
22
- ignorePackages: true,
23
- js: opt,
24
- jsx: opt,
25
- mjs: opt,
26
- cjs: opt,
27
- ...(options.typescript && {
28
- ts: opt,
29
- tsx: opt,
30
- mts: opt,
31
- cts: opt
32
- })
33
- };
34
20
  const config = {
35
21
  plugins: {
36
22
  import: _eslintPluginImportX.default
@@ -55,7 +41,22 @@ function imports(options = {}) {
55
41
  },
56
42
  rules: {
57
43
  'import/consistent-type-specifier-style': ['warn', 'prefer-top-level'],
58
- // 'import/extensions': ['error', 'ignorePackages', extensionOptions], FIXME: This rule is broken!
44
+ /*
45
+ * FIXME: This rule is broken!
46
+ const opt = options.esm ? 'always' : 'never';
47
+ 'import/extensions': [
48
+ 'error',
49
+ opt,
50
+ {
51
+ ignorePackages: true,
52
+ js: opt,
53
+ jsx: opt,
54
+ mjs: opt,
55
+ cjs: opt,
56
+ ...(options.typescript && { ts: opt, tsx: opt, mts: opt, cts: opt }),
57
+ },
58
+ ],
59
+ */
59
60
  'import/first': 'error',
60
61
  'import/newline-after-import': 'warn',
61
62
  'import/no-absolute-path': 'error',
@@ -24,17 +24,20 @@ function react() {
24
24
  },
25
25
  rules: {
26
26
  '@eslint-react/dom/no-children-in-void-dom-elements': 'warn',
27
- '@eslint-react/dom/no-dangerously-set-innerhtml': 'off',
28
27
  '@eslint-react/dom/no-dangerously-set-innerhtml-with-children': 'error',
28
+ '@eslint-react/dom/no-dangerously-set-innerhtml': 'off',
29
29
  '@eslint-react/dom/no-find-dom-node': 'error',
30
+ '@eslint-react/dom/no-flush-sync': 'off',
31
+ '@eslint-react/dom/no-hydrate': 'warn',
30
32
  '@eslint-react/dom/no-missing-button-type': 'warn',
31
33
  '@eslint-react/dom/no-missing-iframe-sandbox': 'warn',
32
34
  '@eslint-react/dom/no-namespace': 'error',
33
35
  '@eslint-react/dom/no-render-return-value': 'error',
36
+ '@eslint-react/dom/no-render': 'warn',
34
37
  '@eslint-react/dom/no-script-url': 'warn',
35
38
  '@eslint-react/dom/no-unsafe-iframe-sandbox': 'warn',
36
39
  '@eslint-react/dom/no-unsafe-target-blank': 'warn',
37
- '@eslint-react/ensure-forward-ref-using-ref': 'warn',
40
+ '@eslint-react/ensure-forward-ref-using-ref': 'off',
38
41
  '@eslint-react/no-access-state-in-setstate': 'error',
39
42
  '@eslint-react/no-array-index-key': 'warn',
40
43
  '@eslint-react/no-children-count': 'warn',
@@ -52,8 +55,9 @@ function react() {
52
55
  '@eslint-react/no-direct-mutation-state': 'error',
53
56
  '@eslint-react/no-duplicate-key': 'error',
54
57
  '@eslint-react/no-implicit-key': 'error',
58
+ '@eslint-react/no-missing-context-display-name': 'off',
55
59
  '@eslint-react/no-missing-key': 'error',
56
- '@eslint-react/no-nested-components': 'warn',
60
+ '@eslint-react/no-nested-component-definitions': 'warn',
57
61
  '@eslint-react/no-redundant-should-component-update': 'error',
58
62
  '@eslint-react/no-set-state-in-component-did-mount': 'warn',
59
63
  '@eslint-react/no-set-state-in-component-did-update': 'warn',
@@ -66,6 +70,7 @@ function react() {
66
70
  '@eslint-react/no-unstable-default-props': 'error',
67
71
  '@eslint-react/no-unused-class-component-members': 'warn',
68
72
  '@eslint-react/no-unused-state': 'warn',
73
+ '@eslint-react/no-useless-forward-ref': 'warn',
69
74
  '@eslint-react/no-useless-fragment': 'warn',
70
75
  '@eslint-react/prefer-destructuring-assignment': 'warn',
71
76
  '@eslint-react/prefer-shorthand-boolean': 'warn',
@@ -75,11 +80,12 @@ function react() {
75
80
  '@eslint-react/hooks-extra/prefer-use-state-lazy-initialization': 'warn',
76
81
  '@eslint-react/hooks-extra/no-useless-custom-hooks': 'warn',
77
82
  '@eslint-react/naming-convention/component-name': 'warn',
78
- '@eslint-react/naming-convention/filename': 'off',
83
+ '@eslint-react/naming-convention/context-name': 'warn',
79
84
  '@eslint-react/naming-convention/filename-extension': ['error', {
80
85
  allow: 'always',
81
86
  extensions: ['.jsx', '.tsx']
82
87
  }],
88
+ '@eslint-react/naming-convention/filename': 'off',
83
89
  '@eslint-react/naming-convention/use-state': 'off',
84
90
  'react-hooks/exhaustive-deps': 'warn',
85
91
  'react-hooks/rules-of-hooks': 'error',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fullstacksjs/eslint-config",
3
- "version": "11.8.1",
3
+ "version": "11.10.0",
4
4
  "license": "MIT",
5
5
  "author": "fullstacks <fullstacksjs@gmail.com>",
6
6
  "description": "fullstacks eslint config",
@@ -34,22 +34,22 @@
34
34
  }
35
35
  },
36
36
  "dependencies": {
37
- "@eslint-react/eslint-plugin": "1.28.0",
38
- "@next/eslint-plugin-next": "15.1.7",
39
- "@stylistic/eslint-plugin": "4.1.0",
37
+ "@eslint-react/eslint-plugin": "1.37.3",
38
+ "@next/eslint-plugin-next": "15.2.3",
39
+ "@stylistic/eslint-plugin": "4.2.0",
40
40
  "deepmerge": "4.3.1",
41
- "eslint-plugin-cypress": "4.1.0",
42
- "eslint-plugin-import-x": "4.6.1",
41
+ "eslint-plugin-cypress": "4.2.0",
42
+ "eslint-plugin-import-x": "4.9.1",
43
43
  "eslint-plugin-jest": "28.11.0",
44
44
  "eslint-plugin-jest-formatting": "3.1.0",
45
45
  "eslint-plugin-jsx-a11y": "6.10.2",
46
- "eslint-plugin-n": "17.15.1",
47
- "eslint-plugin-perfectionist": "4.9.0",
46
+ "eslint-plugin-n": "17.16.2",
47
+ "eslint-plugin-perfectionist": "4.10.1",
48
48
  "eslint-plugin-playwright": "2.2.0",
49
49
  "eslint-plugin-prettier": "5.2.3",
50
50
  "eslint-plugin-promise": "7.2.1",
51
- "eslint-plugin-react-hooks": "5.1.0",
52
- "eslint-plugin-storybook": "0.11.3",
51
+ "eslint-plugin-react-hooks": "5.2.0",
52
+ "eslint-plugin-storybook": "0.11.6",
53
53
  "eslint-plugin-tailwindcss": "3.18.0",
54
54
  "eslint-plugin-vitest": "0.5.4",
55
55
  "globals": "16.0.0",
@@ -57,14 +57,14 @@
57
57
  "typescript-eslint": "8.27.0"
58
58
  },
59
59
  "devDependencies": {
60
- "@eslint/eslintrc": "3.3.0",
61
- "@eslint/js": "9.22.0",
60
+ "@eslint/eslintrc": "3.3.1",
61
+ "@eslint/js": "9.23.0",
62
62
  "@semantic-release/github": "11.0.1",
63
63
  "@semantic-release/npm": "12.0.1",
64
64
  "@semantic-release/release-notes-generator": "14.0.3",
65
65
  "@types/eslint": "9.6.1",
66
66
  "cspell": "8.17.5",
67
- "eslint": "9.22.0",
67
+ "eslint": "9.23.0",
68
68
  "husky": "9.1.7",
69
69
  "jest": "29.7.0",
70
70
  "lint-staged": "15.5.0",
@@ -77,7 +77,7 @@
77
77
  },
78
78
  "peerDependencies": {
79
79
  "cypress": ">=8",
80
- "eslint": "^9",
80
+ "eslint": ">=9.22",
81
81
  "prettier": "3",
82
82
  "react": ">=16",
83
83
  "typescript": ">=4.7"
package/src/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import merge from 'deepmerge';
2
- import { defineConfig } from 'eslint/config';
2
+ import { defineConfig as eslintConfig } from 'eslint/config';
3
3
  import { isPackageExists } from 'local-pkg';
4
4
 
5
5
  import base from './modules/base.mjs';
@@ -55,7 +55,7 @@ const defaultOptions = {
55
55
  * @param {...Config} extend
56
56
  * @returns {Config[]}
57
57
  */
58
- export function init(initOptions = {}, ...extend) {
58
+ export function defineConfig(initOptions = {}, ...extend) {
59
59
  const options = merge(defaultOptions, initOptions);
60
60
 
61
61
  if (options.tailwind === true) {
@@ -110,5 +110,8 @@ export function init(initOptions = {}, ...extend) {
110
110
  if (Object.keys(eslintOptions).length > 0) rules.push(eslintOptions);
111
111
  if (extend) Array.prototype.push.apply(rules, extend);
112
112
 
113
- return defineConfig(rules);
113
+ return eslintConfig(rules);
114
114
  }
115
+
116
+ /** @deprecated Please use `defineConfig` from `@fullstacksjs/eslint-config` instead, this function will be removed in the next major release */
117
+ export const init = defineConfig;
@@ -12,20 +12,6 @@ const allExtensions = [...jsExtensions, ...tsExtensions];
12
12
  */
13
13
  function imports(options = {}) {
14
14
  const isObject = typeof options.import === 'object';
15
- const opt = options.esm ? 'always' : 'never';
16
- const extensionOptions = {
17
- ignorePackages: true,
18
- js: opt,
19
- jsx: opt,
20
- mjs: opt,
21
- cjs: opt,
22
- ...(options.typescript && {
23
- ts: opt,
24
- tsx: opt,
25
- mts: opt,
26
- cts: opt,
27
- }),
28
- };
29
15
 
30
16
  const config = {
31
17
  plugins: { import: plugin },
@@ -46,7 +32,22 @@ function imports(options = {}) {
46
32
 
47
33
  rules: {
48
34
  'import/consistent-type-specifier-style': ['warn', 'prefer-top-level'],
49
- // 'import/extensions': ['error', 'ignorePackages', extensionOptions], FIXME: This rule is broken!
35
+ /*
36
+ * FIXME: This rule is broken!
37
+ const opt = options.esm ? 'always' : 'never';
38
+ 'import/extensions': [
39
+ 'error',
40
+ opt,
41
+ {
42
+ ignorePackages: true,
43
+ js: opt,
44
+ jsx: opt,
45
+ mjs: opt,
46
+ cjs: opt,
47
+ ...(options.typescript && { ts: opt, tsx: opt, mts: opt, cts: opt }),
48
+ },
49
+ ],
50
+ */
50
51
  'import/first': 'error',
51
52
  'import/newline-after-import': 'warn',
52
53
  'import/no-absolute-path': 'error',
@@ -18,18 +18,20 @@ function react() {
18
18
  },
19
19
  rules: {
20
20
  '@eslint-react/dom/no-children-in-void-dom-elements': 'warn',
21
- '@eslint-react/dom/no-dangerously-set-innerhtml': 'off',
22
21
  '@eslint-react/dom/no-dangerously-set-innerhtml-with-children': 'error',
22
+ '@eslint-react/dom/no-dangerously-set-innerhtml': 'off',
23
23
  '@eslint-react/dom/no-find-dom-node': 'error',
24
+ '@eslint-react/dom/no-flush-sync': 'off',
25
+ '@eslint-react/dom/no-hydrate': 'warn',
24
26
  '@eslint-react/dom/no-missing-button-type': 'warn',
25
27
  '@eslint-react/dom/no-missing-iframe-sandbox': 'warn',
26
28
  '@eslint-react/dom/no-namespace': 'error',
27
29
  '@eslint-react/dom/no-render-return-value': 'error',
30
+ '@eslint-react/dom/no-render': 'warn',
28
31
  '@eslint-react/dom/no-script-url': 'warn',
29
32
  '@eslint-react/dom/no-unsafe-iframe-sandbox': 'warn',
30
33
  '@eslint-react/dom/no-unsafe-target-blank': 'warn',
31
-
32
- '@eslint-react/ensure-forward-ref-using-ref': 'warn',
34
+ '@eslint-react/ensure-forward-ref-using-ref': 'off',
33
35
  '@eslint-react/no-access-state-in-setstate': 'error',
34
36
  '@eslint-react/no-array-index-key': 'warn',
35
37
  '@eslint-react/no-children-count': 'warn',
@@ -47,8 +49,9 @@ function react() {
47
49
  '@eslint-react/no-direct-mutation-state': 'error',
48
50
  '@eslint-react/no-duplicate-key': 'error',
49
51
  '@eslint-react/no-implicit-key': 'error',
52
+ '@eslint-react/no-missing-context-display-name': 'off',
50
53
  '@eslint-react/no-missing-key': 'error',
51
- '@eslint-react/no-nested-components': 'warn',
54
+ '@eslint-react/no-nested-component-definitions': 'warn',
52
55
  '@eslint-react/no-redundant-should-component-update': 'error',
53
56
  '@eslint-react/no-set-state-in-component-did-mount': 'warn',
54
57
  '@eslint-react/no-set-state-in-component-did-update': 'warn',
@@ -61,6 +64,7 @@ function react() {
61
64
  '@eslint-react/no-unstable-default-props': 'error',
62
65
  '@eslint-react/no-unused-class-component-members': 'warn',
63
66
  '@eslint-react/no-unused-state': 'warn',
67
+ '@eslint-react/no-useless-forward-ref': 'warn',
64
68
  '@eslint-react/no-useless-fragment': 'warn',
65
69
  '@eslint-react/prefer-destructuring-assignment': 'warn',
66
70
  '@eslint-react/prefer-shorthand-boolean': 'warn',
@@ -72,8 +76,9 @@ function react() {
72
76
  '@eslint-react/hooks-extra/no-useless-custom-hooks': 'warn',
73
77
 
74
78
  '@eslint-react/naming-convention/component-name': 'warn',
75
- '@eslint-react/naming-convention/filename': 'off',
79
+ '@eslint-react/naming-convention/context-name': 'warn',
76
80
  '@eslint-react/naming-convention/filename-extension': ['error', { allow: 'always', extensions: ['.jsx', '.tsx'] }],
81
+ '@eslint-react/naming-convention/filename': 'off',
77
82
  '@eslint-react/naming-convention/use-state': 'off',
78
83
 
79
84
  'react-hooks/exhaustive-deps': 'warn',