diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 3b8aa86..6e98150 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -10,12 +10,12 @@ jobs:
fail-fast: false
matrix:
node-version:
- - 16
- - 14
- - 12
+ - 22
+ - 20
+ - 18
steps:
- - uses: actions/checkout@v2
- - uses: actions/setup-node@v2
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm install
diff --git a/index.js b/index.js
index cd07b89..38a36a5 100644
--- a/index.js
+++ b/index.js
@@ -1,218 +1,209 @@
-'use strict';
-module.exports = {
- parserOptions: {
- ecmaFeatures: {
- jsx: true
- }
- },
- plugins: [
- 'react',
- 'react-hooks'
- ],
- settings: {
- react: {
- version: 'detect'
- }
- },
- rules: {
- 'react/boolean-prop-naming': [
- 'error',
- {
- validateNested: true
- }
- ],
- 'react/button-has-type': 'error',
- 'react/jsx-child-element-spacing': 'error',
- 'react/default-props-match-prop-types': 'error',
- 'react/function-component-definition': [
- 'error',
- {
- namedComponents: 'function-declaration',
- unnamedComponents: 'arrow-function'
- }
- ],
- 'react/hook-use-state': 'error',
- 'react/iframe-missing-sandbox': 'error',
- 'react/no-access-state-in-setstate': 'error',
- 'react/no-array-index-key': 'error',
- 'react/no-arrow-function-lifecycle': 'error',
- 'react/no-children-prop': 'error',
- 'react/no-danger': 'error',
- 'react/no-danger-with-children': 'error',
- 'react/no-deprecated': 'error',
- 'react/no-did-update-set-state': 'error',
- 'react/no-direct-mutation-state': 'error',
- 'react/no-find-dom-node': 'error',
- 'react/no-invalid-html-attribute': 'error',
- 'react/no-is-mounted': 'error',
- 'react/no-namespace': 'error',
- 'react/no-redundant-should-component-update': 'error',
- 'react/no-render-return-value': 'error',
- 'react/no-typos': 'error',
- 'react/no-string-refs': [
- 'error',
- {
- noTemplateLiterals: true
- }
- ],
- 'react/no-this-in-sfc': 'error',
- 'react/no-unescaped-entities': 'error',
- 'react/no-unknown-property': 'error',
- 'react/no-unsafe': 'error',
- 'react/no-unused-prop-types': 'error',
- 'react/no-unused-state': 'error',
- 'react/prefer-read-only-props': 'error',
- 'react/prop-types': 'error',
- 'react/react-in-jsx-scope': 'error',
- 'react/require-default-props': [
- 'error',
- {
- forbidDefaultForRequired: true,
- ignoreFunctionalComponents: true
- }
- ],
- 'react/self-closing-comp': 'error',
- 'react/state-in-constructor': [
- 'error',
- 'never'
- ],
- 'react/static-property-placement': 'error',
- 'react/style-prop-object': [
- 'error',
- {
- allow: [
- // This allows react-intl’s ``.
- 'FormattedNumber'
- ]
- }
- ],
- 'react/void-dom-elements-no-children': 'error',
- 'react/jsx-boolean-value': 'error',
- 'react/jsx-closing-bracket-location': [
- 'error',
- {
- nonEmpty: 'tag-aligned',
- selfClosing: false
- }
- ],
- 'react/jsx-closing-tag-location': 'error',
- 'react/jsx-curly-newline': [
- 'error',
- {
- multiline: 'consistent',
- singleline: 'forbid'
- }
- ],
- 'react/jsx-curly-spacing': [
- 'error',
- 'never'
- ],
- 'react/jsx-equals-spacing': [
- 'error',
- 'never'
- ],
- 'react/jsx-first-prop-new-line': 'error',
- 'react/jsx-indent': [
- 'error',
- 'tab',
- {
- checkAttributes: true,
- indentLogicalExpressions: true
- }
- ],
- 'react/jsx-indent-props': [
- 'error',
- 'tab'
- ],
- 'react/jsx-key': [
- 'error',
- {
- checkFragmentShorthand: true,
- checkKeyMustBeforeSpread: true,
- warnOnDuplicates: true
- }
- ],
- 'react/jsx-max-props-per-line': [
- 'error',
- {
- maximum: 3,
- when: 'multiline'
- }
- ],
- 'react/jsx-no-bind': [
- 'error',
- {
- allowArrowFunctions: true
- }
- ],
- 'react/jsx-no-comment-textnodes': 'error',
- 'react/jsx-no-constructed-context-values': 'error',
- 'react/jsx-no-duplicate-props': [
- 'error',
- {
- ignoreCase: true
- }
- ],
- 'react/jsx-no-script-url': 'error',
- 'react/jsx-no-target-blank': [
- 'error',
- {
- warnOnSpreadAttributes: true,
- forms: true
- }
- ],
- 'react/jsx-no-undef': 'error',
- 'react/jsx-no-useless-fragment': 'error',
- // Disabled for now as it produces too many errors
- // 'react/jsx-one-expression-per-line': ['error', {allow: 'single-child'}],
- 'react/jsx-curly-brace-presence': [
- 'error',
- {
- props: 'never',
- children: 'never',
- propElementValues: 'always'
- }
- ],
- 'react/jsx-fragments': [
- 'error',
- 'syntax'
- ],
- 'react/jsx-pascal-case': 'error',
- 'react/jsx-props-no-multi-spaces': 'error',
- 'react/jsx-sort-props': [
- 'error',
- {
- callbacksLast: true,
- shorthandFirst: true,
- noSortAlphabetically: true,
- reservedFirst: true
- }
- ],
- 'react/jsx-tag-spacing': [
- 'error',
- {
- closingSlash: 'never',
- beforeSelfClosing: 'never',
- afterOpening: 'never',
- beforeClosing: 'never'
- }
- ],
- 'react/jsx-uses-react': 'error',
- 'react/jsx-uses-vars': 'error',
- 'react/jsx-wrap-multilines': [
- 'error',
- {
- declaration: 'parens-new-line',
- assignment: 'parens-new-line',
- return: 'parens-new-line',
- arrow: 'parens-new-line',
- condition: 'ignore',
- logical: 'ignore',
- prop: 'ignore'
- }
- ],
+import react from 'eslint-plugin-react';
+import reactHooks from 'eslint-plugin-react-hooks';
- 'react-hooks/rules-of-hooks': 'error',
- 'react-hooks/exhaustive-deps': 'warn'
- }
-};
+export default [
+ {
+ plugins: {
+ react,
+ 'react-hooks': reactHooks,
+ },
+ languageOptions: {
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ },
+ },
+ },
+ settings: {
+ react: {
+ version: 'detect',
+ },
+ },
+ rules: {
+ 'react/boolean-prop-naming': [
+ 'error',
+ {
+ validateNested: true,
+ },
+ ],
+ 'react/button-has-type': 'error',
+ 'react/jsx-child-element-spacing': 'error',
+ 'react/default-props-match-prop-types': 'error',
+ 'react/function-component-definition': [
+ 'error',
+ {
+ namedComponents: 'function-declaration',
+ unnamedComponents: 'arrow-function',
+ },
+ ],
+ 'react/hook-use-state': 'error',
+ 'react/iframe-missing-sandbox': 'error',
+ 'react/no-access-state-in-setstate': 'error',
+ 'react/no-array-index-key': 'error',
+ 'react/no-arrow-function-lifecycle': 'error',
+ 'react/no-children-prop': 'error',
+ 'react/no-danger': 'error',
+ 'react/no-danger-with-children': 'error',
+ 'react/no-deprecated': 'error',
+ 'react/no-did-update-set-state': 'error',
+ 'react/no-direct-mutation-state': 'error',
+ 'react/no-find-dom-node': 'error',
+ 'react/no-invalid-html-attribute': 'error',
+ 'react/no-is-mounted': 'error',
+ 'react/no-namespace': 'error',
+ 'react/no-redundant-should-component-update': 'error',
+ 'react/no-render-return-value': 'error',
+ 'react/no-typos': 'error',
+ 'react/no-string-refs': [
+ 'error',
+ {
+ noTemplateLiterals: true,
+ },
+ ],
+ 'react/no-this-in-sfc': 'error',
+ 'react/no-unescaped-entities': 'error',
+ 'react/no-unknown-property': 'error',
+ 'react/no-unsafe': 'error',
+ 'react/no-unused-prop-types': 'error',
+ 'react/no-unused-state': 'error',
+ 'react/prefer-read-only-props': 'error',
+ 'react/prop-types': 'error',
+ 'react/react-in-jsx-scope': 'error',
+ 'react/require-default-props': [
+ 'error',
+ {
+ forbidDefaultForRequired: true,
+ ignoreFunctionalComponents: true,
+ },
+ ],
+ 'react/self-closing-comp': 'error',
+ 'react/state-in-constructor': ['error', 'never'],
+ 'react/static-property-placement': 'error',
+ 'react/style-prop-object': [
+ 'error',
+ {
+ allow: [
+ // This allows react-intl’s ``.
+ 'FormattedNumber',
+ ],
+ },
+ ],
+ 'react/void-dom-elements-no-children': 'error',
+ 'react/jsx-boolean-value': 'error',
+ 'react/jsx-closing-bracket-location': [
+ 'error',
+ {
+ nonEmpty: 'tag-aligned',
+ selfClosing: false,
+ },
+ ],
+ 'react/jsx-closing-tag-location': 'error',
+ 'react/jsx-curly-newline': [
+ 'error',
+ {
+ multiline: 'consistent',
+ singleline: 'forbid',
+ },
+ ],
+ 'react/jsx-curly-spacing': ['error', 'never'],
+ 'react/jsx-equals-spacing': ['error', 'never'],
+ 'react/jsx-first-prop-new-line': 'error',
+ 'react/jsx-indent': [
+ 'error',
+ 'tab',
+ {
+ checkAttributes: true,
+ indentLogicalExpressions: true,
+ },
+ ],
+ 'react/jsx-indent-props': ['error', 'tab'],
+ 'react/jsx-key': [
+ 'error',
+ {
+ checkFragmentShorthand: true,
+ checkKeyMustBeforeSpread: true,
+ warnOnDuplicates: true,
+ },
+ ],
+ 'react/jsx-max-props-per-line': [
+ 'error',
+ {
+ maximum: 3,
+ when: 'multiline',
+ },
+ ],
+ 'react/jsx-no-bind': [
+ 'error',
+ {
+ allowArrowFunctions: true,
+ },
+ ],
+ 'react/jsx-no-comment-textnodes': 'error',
+ 'react/jsx-no-constructed-context-values': 'error',
+ 'react/jsx-no-duplicate-props': [
+ 'error',
+ {
+ ignoreCase: true,
+ },
+ ],
+ 'react/jsx-no-script-url': 'error',
+ 'react/jsx-no-target-blank': [
+ 'error',
+ {
+ warnOnSpreadAttributes: true,
+ forms: true,
+ },
+ ],
+ 'react/jsx-no-undef': 'error',
+ 'react/jsx-no-useless-fragment': 'error',
+ // Disabled for now as it produces too many errors
+ // 'react/jsx-one-expression-per-line': ['error', {allow: 'single-child'}],
+ 'react/jsx-curly-brace-presence': [
+ 'error',
+ {
+ props: 'never',
+ children: 'never',
+ propElementValues: 'always',
+ },
+ ],
+ 'react/jsx-fragments': ['error', 'syntax'],
+ 'react/jsx-pascal-case': 'error',
+ 'react/jsx-props-no-multi-spaces': 'error',
+ 'react/jsx-sort-props': [
+ 'error',
+ {
+ callbacksLast: true,
+ shorthandFirst: true,
+ noSortAlphabetically: true,
+ reservedFirst: true,
+ },
+ ],
+ 'react/jsx-tag-spacing': [
+ 'error',
+ {
+ closingSlash: 'never',
+ beforeSelfClosing: 'never',
+ afterOpening: 'never',
+ beforeClosing: 'never',
+ },
+ ],
+ 'react/jsx-uses-react': 'error',
+ 'react/jsx-uses-vars': 'error',
+ 'react/jsx-wrap-multilines': [
+ 'error',
+ {
+ declaration: 'parens-new-line',
+ assignment: 'parens-new-line',
+ return: 'parens-new-line',
+ arrow: 'parens-new-line',
+ condition: 'ignore',
+ logical: 'ignore',
+ prop: 'ignore',
+ },
+ ],
+
+ 'react-hooks/rules-of-hooks': 'error',
+ 'react-hooks/exhaustive-deps': 'warn',
+ },
+ },
+];
diff --git a/package.json b/package.json
index 19d32c3..079d3dc 100644
--- a/package.json
+++ b/package.json
@@ -10,12 +10,17 @@
"email": "sindresorhus@gmail.com",
"url": "https://sindresorhus.com"
},
+ "type": "module",
"engines": {
"node": ">=12"
},
"scripts": {
"test": "ava"
},
+ "exports": {
+ ".": "./index.js",
+ "./space": "./space.js"
+ },
"files": [
"index.js",
"space.js"
@@ -51,16 +56,15 @@
"simple"
],
"devDependencies": {
- "ava": "^2.4.0",
- "eslint": "^8.6.0",
+ "ava": "^6.2.0",
+ "eslint": "^9.18.0",
"eslint-plugin-react": "^7.29.0",
- "eslint-plugin-react-hooks": "^4.3.0",
- "is-plain-obj": "^3.0.0",
+ "eslint-plugin-react-hooks": "^5.1.0",
"react": "^17.0.2"
},
"peerDependencies": {
- "eslint": ">=8.6.0",
+ "eslint": ">=9.18.0",
"eslint-plugin-react": ">=7.29.0",
- "eslint-plugin-react-hooks": ">=4.3.0"
+ "eslint-plugin-react-hooks": ">=5.1.0"
}
}
diff --git a/readme.md b/readme.md
index a86ba71..22df646 100644
--- a/readme.md
+++ b/readme.md
@@ -10,51 +10,37 @@ npm install --save-dev eslint-config-xo eslint-config-xo-react eslint-plugin-rea
## Usage
-Add some ESLint config to your package.json:
+Add some ESLint config to your `eslint.config.js`:
-```json
-{
- "name": "my-awesome-project",
- "eslintConfig": {
- "extends": [
- "xo",
- "xo-react"
- ]
- }
-}
-```
-
-Or to .eslintrc:
+```js
+// eslint.config.js
+import xo from 'eslint-config-xo';
+import xoReact from 'eslint-config-xo-react';
-```json
-{
- "extends": [
- "xo",
- "xo-react"
- ]
-}
+export default [...xo, ...xoReact];
```
Use the `space` sub-config if you want 2 space indentation instead of tabs:
-```json
-{
- "extends": [
- "xo",
- "xo-react/space"
- ]
-}
+```js
+// eslint.config.js
+import xo from 'eslint-config-xo';
+import xoReactSpace from 'eslint-config-xo-react/space';
+
+export default [...xo, ...xoReactSpace];
```
-You can also mix it with a [XO](https://github.com/xojs/xo) sub-config:
+You can also mix it with a [eslint-config-xo](https://github.com/xojs/eslint-config-xo) sub-config:
-```json
-{
- "extends": [
- "xo/esnext",
- "xo-react"
- ]
-}
+```js
+// eslint.config.js
+import xoSpace from 'eslint-config-xo/space';
+import xoReactSpace from 'eslint-config-xo-react/space';
+
+export default [
+ ...xoSpace,
+ ...xoReactSpace
+];
```
## Tip
diff --git a/space.js b/space.js
index 8f9d4b6..31aa43e 100644
--- a/space.js
+++ b/space.js
@@ -1,16 +1,14 @@
-'use strict';
-const path = require('path');
+import configs from './index.js';
-module.exports = {
- extends: path.join(__dirname, 'index.js'),
- rules: {
- 'react/jsx-indent-props': [
- 'error',
- 2
- ],
- 'react/jsx-indent': [
- 'error',
- 2
- ]
- }
-};
+const [config] = configs;
+
+export default [
+ {
+ ...config,
+ rules: {
+ ...config.rules,
+ 'react/jsx-indent-props': ['error', 2],
+ 'react/jsx-indent': ['error', 2],
+ },
+ },
+];
diff --git a/test/test.js b/test/test.js
index e7bbf05..85d8916 100644
--- a/test/test.js
+++ b/test/test.js
@@ -1,12 +1,13 @@
import test from 'ava';
-import isPlainObj from 'is-plain-obj';
+import eslintConfigXoReact from '../index.js';
+import eslintConfigXoReactSpace from '../space.js';
import {ESLint} from 'eslint';
const hasRule = (errors, ruleId) => errors.some(error => error.ruleId === ruleId);
async function runEslint(string, config) {
const eslint = new ESLint({
- useEslintrc: false,
+ overrideConfigFile: true,
overrideConfig: config,
});
@@ -16,28 +17,20 @@ async function runEslint(string, config) {
}
test('main', async t => {
- const config = require('../space.js');
+ t.true(Array.isArray(eslintConfigXoReact));
- t.true(isPlainObj(config));
- t.true(isPlainObj(config.rules));
-
- const errors = await runEslint('var app =
Unicorn
', config);
+ const errors = await runEslint('var app = Unicorn
', eslintConfigXoReact);
t.true(hasRule(errors, 'react/react-in-jsx-scope'));
});
test('space', async t => {
- const config = require('../space.js');
-
- t.true(isPlainObj(config));
- t.true(isPlainObj(config.rules));
+ t.true(Array.isArray(eslintConfigXoReactSpace));
- const errors = await runEslint('\n\t\n', config);
+ const errors = await runEslint('\n\t\n', eslintConfigXoReactSpace);
t.true(hasRule(errors, 'react/jsx-indent'));
});
test('no errors', async t => {
- const config = require('../index.js');
-
- const errors = await runEslint('var React = require(\'react\');\nvar el = ;', config);
+ const errors = await runEslint('var React = require(\'react\');\nvar el = ;', eslintConfigXoReact);
t.deepEqual(errors, []);
});