diff --git a/packages/eslint-config-airbnb-base/README.md b/packages/eslint-config-airbnb-base/README.md index 6ddc34dac1..a975299519 100644 --- a/packages/eslint-config-airbnb-base/README.md +++ b/packages/eslint-config-airbnb-base/README.md @@ -62,6 +62,28 @@ Our default export contains all of our ESLint rules, including ECMAScript 6+. It 2. Add `"extends": "airbnb-base"` to your .eslintrc. + If using **flat config**, add `eslint-config-airbnb-base/flat` to `eslint.config.mjs` / `eslint.config.cjs` / `eslint.config.js` : + + ```js + // eslint.config.cjs + const airbnbBase = require('eslint-config-airbnb-base/flat'); + + module.exports = [ + ...airbnbBase, + // ...... + ]; + ``` + + ```js + // eslint.config.mjs + import airbnbBase from 'eslint-config-airbnb-base/flat'; + + export default [ + ...airbnbBase, + // ...... + ]; + ``` + ### eslint-config-airbnb-base/legacy Lints ES5 and below. Requires `eslint` and `eslint-plugin-import`. @@ -94,6 +116,27 @@ See [Airbnb's overarching ESLint config](https://npmjs.com/eslint-config-airbnb) This entry point only errors on whitespace rules and sets all other rules to warnings. View the list of whitespace rules [here](https://github.com/airbnb/javascript/blob/master/packages/eslint-config-airbnb-base/whitespace.js). +If using **flat config**, add `eslint-config-airbnb-base/whitespace-flat` to `eslint.config.mjs` / `eslint.config.cjs` / `eslint.config.js` : +```js +// eslint.config.cjs +const whitespace = require('eslint-config-airbnb-base/whitespace-flat'); + +module.exports = [ + whitespace, + // ...... +]; +``` + +```js +// eslint.config.mjs +import whitespace from 'eslint-config-airbnb-base/whitespace-flat'; + +export default [ + whitespace, + // ...... +]; +``` + ## Improving this config Consider adding test cases if you're making complicated rules changes, like anything involving regexes. Perhaps in a distant future, we could use literate programming to structure our README as test cases for our .eslintrc? diff --git a/packages/eslint-config-airbnb-base/eslint.config.js b/packages/eslint-config-airbnb-base/eslint.config.js new file mode 100644 index 0000000000..737a6f1215 --- /dev/null +++ b/packages/eslint-config-airbnb-base/eslint.config.js @@ -0,0 +1,12 @@ +module.exports = [ + ...require('./flat'), + { + rules: { + // disable requiring trailing commas because it might be nice to revert to + // being JSON at some point, and I don't want to make big changes now. + "comma-dangle": 0, + + "max-len": 0 + } + } +]; diff --git a/packages/eslint-config-airbnb-base/flat.js b/packages/eslint-config-airbnb-base/flat.js new file mode 100644 index 0000000000..5d6e36151a --- /dev/null +++ b/packages/eslint-config-airbnb-base/flat.js @@ -0,0 +1,21 @@ +module.exports = [ + ...([ + './rules/flat/best-practices', + './rules/flat/errors', + './rules/flat/node', + './rules/flat/style', + './rules/flat/variables', + './rules/flat/es6', + './rules/flat/imports', + './rules/flat/strict', + ].reduce((p, c) => p.concat(require(c)), [])), // eslint-disable-line global-require, import/no-dynamic-require + { + languageOptions: { + parserOptions: { + ecmaVersion: 2018, + sourceType: 'module' + } + }, + rules: {} + } +]; diff --git a/packages/eslint-config-airbnb-base/package.json b/packages/eslint-config-airbnb-base/package.json index 8f039d4d7c..623f7783c2 100644 --- a/packages/eslint-config-airbnb-base/package.json +++ b/packages/eslint-config-airbnb-base/package.json @@ -5,8 +5,10 @@ "main": "index.js", "exports": { ".": "./index.js", + "./flat": "./flat.js", "./legacy": "./legacy.js", "./whitespace": "./whitespace.js", + "./whitespace-flat": "./whitespace-flat.js", "./rules/best-practices": "./rules/best-practices.js", "./rules/es6": "./rules/es6.js", "./rules/node": "./rules/node.js", @@ -15,6 +17,14 @@ "./rules/imports": "./rules/imports.js", "./rules/strict": "./rules/strict.js", "./rules/variables": "./rules/variables.js", + "./rules/flat/best-practices": "./rules/flat/best-practices.js", + "./rules/flat/es6": "./rules/flat/es6.js", + "./rules/flat/node": "./rules/flat/node.js", + "./rules/flat/style": "./rules/flat/style.js", + "./rules/flat/errors": "./rules/flat/errors.js", + "./rules/flat/imports": "./rules/flat/imports.js", + "./rules/flat/strict": "./rules/flat/strict.js", + "./rules/flat/variables": "./rules/flat/variables.js", "./package.json": "./package.json" }, "scripts": { @@ -72,21 +82,22 @@ "babel-preset-airbnb": "^4.5.0", "babel-tape-runner": "^3.0.0", "eclint": "^2.8.1", - "eslint": "^7.32.0 || ^8.2.0", - "eslint-find-rules": "^4.1.0", + "eslint": "^7.32.0 || ^8.2.0 || >= 9", + "eslint-find-rules": "^5.0.0", "eslint-plugin-import": "^2.30.0", "in-publish": "^2.0.1", "safe-publish-latest": "^2.0.0", "tape": "^5.9.0" }, "peerDependencies": { - "eslint": "^7.32.0 || ^8.2.0", + "eslint": "^7.32.0 || ^8.2.0 || >= 9", "eslint-plugin-import": "^2.30.0" }, "engines": { "node": "^10.12.0 || >=12.0.0" }, "dependencies": { - "confusing-browser-globals": "^1.0.11" + "confusing-browser-globals": "^1.0.11", + "globals": "^15.14.0" } } diff --git a/packages/eslint-config-airbnb-base/rules/flat/best-practices.js b/packages/eslint-config-airbnb-base/rules/flat/best-practices.js new file mode 100644 index 0000000000..b1573b6989 --- /dev/null +++ b/packages/eslint-config-airbnb-base/rules/flat/best-practices.js @@ -0,0 +1,6 @@ +const baseConfig = require('../best-practices'); + +module.exports = [{ + name: 'eslint-config-airbnb-base/best-practices', + rules: baseConfig.rules +}]; diff --git a/packages/eslint-config-airbnb-base/rules/flat/errors.js b/packages/eslint-config-airbnb-base/rules/flat/errors.js new file mode 100644 index 0000000000..11e72ea0ff --- /dev/null +++ b/packages/eslint-config-airbnb-base/rules/flat/errors.js @@ -0,0 +1,6 @@ +const baseConfig = require('../errors'); + +module.exports = [{ + name: 'eslint-config-airbnb-base/errors', + rules: baseConfig.rules +}]; diff --git a/packages/eslint-config-airbnb-base/rules/flat/es6.js b/packages/eslint-config-airbnb-base/rules/flat/es6.js new file mode 100644 index 0000000000..a7f1cb2139 --- /dev/null +++ b/packages/eslint-config-airbnb-base/rules/flat/es6.js @@ -0,0 +1,11 @@ +const globals = require('globals'); +const baseConfig = require('../es6'); + +module.exports = [{ + name: 'eslint-config-airbnb-base/es6', + languageOptions: { + globals: globals.es2015, + parserOptions: baseConfig.parserOptions + }, + rules: baseConfig.rules +}]; diff --git a/packages/eslint-config-airbnb-base/rules/flat/imports.js b/packages/eslint-config-airbnb-base/rules/flat/imports.js new file mode 100644 index 0000000000..f29eb11782 --- /dev/null +++ b/packages/eslint-config-airbnb-base/rules/flat/imports.js @@ -0,0 +1,13 @@ +const importPlugin = require('eslint-plugin-import'); +const globals = require('globals'); +const baseConfig = require('../imports'); + +module.exports = [importPlugin.flatConfigs.recommended, { + name: 'eslint-config-airbnb-base/imports', + languageOptions: { + globals: globals.es2015, + parserOptions: baseConfig.parserOptions + }, + settings: baseConfig.settings, + rules: baseConfig.rules +}]; diff --git a/packages/eslint-config-airbnb-base/rules/flat/node.js b/packages/eslint-config-airbnb-base/rules/flat/node.js new file mode 100644 index 0000000000..8fcfabfc94 --- /dev/null +++ b/packages/eslint-config-airbnb-base/rules/flat/node.js @@ -0,0 +1,10 @@ +const globals = require('globals'); +const base = require('../node'); + +module.exports = [{ + name: 'eslint-config-airbnb-base/node', + languageOptions: { + globals: globals.node + }, + rules: base.rules +}]; diff --git a/packages/eslint-config-airbnb-base/rules/flat/strict.js b/packages/eslint-config-airbnb-base/rules/flat/strict.js new file mode 100644 index 0000000000..095cc03f85 --- /dev/null +++ b/packages/eslint-config-airbnb-base/rules/flat/strict.js @@ -0,0 +1,6 @@ +const base = require('../strict'); + +module.exports = [{ + name: 'eslint-config-airbnb-base/strict', + rules: base.rules +}]; diff --git a/packages/eslint-config-airbnb-base/rules/flat/style.js b/packages/eslint-config-airbnb-base/rules/flat/style.js new file mode 100644 index 0000000000..7f9dc9263e --- /dev/null +++ b/packages/eslint-config-airbnb-base/rules/flat/style.js @@ -0,0 +1,6 @@ +const base = require('../style'); + +module.exports = [{ + name: 'eslint-config-airbnb-base/style', + rules: base.rules +}]; diff --git a/packages/eslint-config-airbnb-base/rules/flat/variables.js b/packages/eslint-config-airbnb-base/rules/flat/variables.js new file mode 100644 index 0000000000..45164a35a8 --- /dev/null +++ b/packages/eslint-config-airbnb-base/rules/flat/variables.js @@ -0,0 +1,6 @@ +const base = require('../variables'); + +module.exports = [{ + name: 'eslint-config-airbnb-base/variables', + rules: base.rules +}]; diff --git a/packages/eslint-config-airbnb-base/whitespace-flat-async.js b/packages/eslint-config-airbnb-base/whitespace-flat-async.js new file mode 100755 index 0000000000..8e2479fbbf --- /dev/null +++ b/packages/eslint-config-airbnb-base/whitespace-flat-async.js @@ -0,0 +1,49 @@ +#!/usr/bin/env node + +const { isArray } = Array; +const { entries } = Object; +const { ESLint } = require('eslint'); + +const baseConfig = require('./flat'); +const whitespaceRules = require('./whitespaceRules'); + +const severities = ['off', 'warn', 'error']; + +function getSeverity(ruleConfig) { + if (isArray(ruleConfig)) { + return getSeverity(ruleConfig[0]); + } + if (typeof ruleConfig === 'number') { + return severities[ruleConfig]; + } + return ruleConfig; +} + +async function onlyErrorOnRules(rulesToError, config) { + const errorsOnly = { + languageOptions: config[config.length - 1].languageOptions || {}, + rules: config[config.length - 1].rules || {}, + }; + const cli = new ESLint({ baseConfig: config }); + const baseRules = (await cli.calculateConfigForFile(require.resolve('./flat'))).rules; + + entries(baseRules).forEach((rule) => { + const ruleName = rule[0]; + const ruleConfig = rule[1]; + const severity = getSeverity(ruleConfig); + + if (rulesToError.indexOf(ruleName) === -1 && severity === 'error') { + if (isArray(ruleConfig)) { + errorsOnly.rules[ruleName] = ['warn'].concat(ruleConfig.slice(1)); + } else if (typeof ruleConfig === 'number') { + errorsOnly.rules[ruleName] = 1; + } else { + errorsOnly.rules[ruleName] = 'warn'; + } + } + }); + + return errorsOnly; +} + +onlyErrorOnRules(whitespaceRules, baseConfig).then((config) => console.log(JSON.stringify(config))); diff --git a/packages/eslint-config-airbnb-base/whitespace-flat.js b/packages/eslint-config-airbnb-base/whitespace-flat.js new file mode 100644 index 0000000000..6eca016503 --- /dev/null +++ b/packages/eslint-config-airbnb-base/whitespace-flat.js @@ -0,0 +1,13 @@ +/* eslint global-require: 0 */ + +const path = require('path'); +const { execSync } = require('child_process'); + +// NOTE: ESLint adds runtime statistics to the output (so it's no longer JSON) if TIMING is set +module.exports = JSON.parse(String(execSync(path.join(__dirname, 'whitespace-flat-async.js'), { + cwd: __dirname, // NOTE: cwd is set to prevent ESLint.calculateConfigForFile return {} + env: { + ...process.env, + TIMING: undefined, + } +}))); diff --git a/packages/eslint-config-airbnb/README.md b/packages/eslint-config-airbnb/README.md index 3b3f43196d..0571a2d45d 100644 --- a/packages/eslint-config-airbnb/README.md +++ b/packages/eslint-config-airbnb/README.md @@ -58,14 +58,79 @@ If you don't need React, see [eslint-config-airbnb-base](https://npmjs.com/eslin 2. Add `"extends": "airbnb"` to your `.eslintrc` + If using **flat config**, add `eslint-config-airbnb/flat` to `eslint.config.mjs` / `eslint.config.cjs` / `eslint.config.js` : + + ```js + // eslint.config.cjs + const airbnb = require('eslint-config-airbnb/flat'); + + module.exports = [ + ...airbnb, + // ...... + ]; + ``` + + ```js + // eslint.config.mjs + import airbnb from 'eslint-config-airbnb/flat'; + + export default [ + ...airbnb, + // ...... + ]; + ``` + ### eslint-config-airbnb/hooks This entry point enables the linting rules for React hooks (requires v16.8+). To use, add `"extends": ["airbnb", "airbnb/hooks"]` to your `.eslintrc`. +If using **flat config**, add `eslint-config-airbnb/hooks-flat` to `eslint.config.mjs` / `eslint.config.cjs` / `eslint.config.js` : + +```js +// eslint.config.cjs +const hooksFlat = require('eslint-config-airbnb/hooks-flat'); + +module.exports = [ + ...hooksFlat, + // ...... +]; +``` + +```js +// eslint.config.mjs +import hooksFlat from 'eslint-config-airbnb/hooks-flat'; + +export default [ + ...hooksFlat, + // ...... +]; +``` + ### eslint-config-airbnb/whitespace This entry point only errors on whitespace rules and sets all other rules to warnings. View the list of whitespace rules [here](https://github.com/airbnb/javascript/blob/master/packages/eslint-config-airbnb/whitespace.js). +If using **flat config**, add `eslint-config-airbnb/whitespace-flat` to `eslint.config.mjs` / `eslint.config.cjs` / `eslint.config.js` : +```js +// eslint.config.cjs +const whitespace = require('eslint-config-airbnb/whitespace-flat'); + +module.exports = [ + whitespace, + // ...... +]; +``` + +```js +// eslint.config.mjs +import whitespace from 'eslint-config-airbnb/whitespace-flat'; + +export default [ + whitespace, + // ...... +]; +``` + ### eslint-config-airbnb/base This entry point is deprecated. See [eslint-config-airbnb-base](https://npmjs.com/eslint-config-airbnb-base). diff --git a/packages/eslint-config-airbnb/eslint.config.js b/packages/eslint-config-airbnb/eslint.config.js new file mode 100644 index 0000000000..eb5fe07cab --- /dev/null +++ b/packages/eslint-config-airbnb/eslint.config.js @@ -0,0 +1,10 @@ +module.exports = [ + ...require('./flat'), // eslint-disable-line global-require + { + rules: { + // disable requiring trailing commas because it might be nice to revert to + // being JSON at some point, and I don't want to make big changes now. + 'comma-dangle': 0, + } + } +]; diff --git a/packages/eslint-config-airbnb/flat.js b/packages/eslint-config-airbnb/flat.js new file mode 100644 index 0000000000..74d4fc3d0d --- /dev/null +++ b/packages/eslint-config-airbnb/flat.js @@ -0,0 +1,8 @@ +module.exports = [ + ...([ + 'eslint-config-airbnb-base/flat', + './rules/flat/react', + './rules/flat/react-a11y', + // eslint-disable-next-line global-require, import/no-dynamic-require + ].reduce((p, c) => p.concat(require(c)), [])) +]; diff --git a/packages/eslint-config-airbnb/hooks-flat.js b/packages/eslint-config-airbnb/hooks-flat.js new file mode 100644 index 0000000000..1d976af559 --- /dev/null +++ b/packages/eslint-config-airbnb/hooks-flat.js @@ -0,0 +1 @@ +module.exports = require('./rules/flat/react-hooks'); diff --git a/packages/eslint-config-airbnb/package.json b/packages/eslint-config-airbnb/package.json index c36359ea7c..6b94df4eed 100644 --- a/packages/eslint-config-airbnb/package.json +++ b/packages/eslint-config-airbnb/package.json @@ -5,13 +5,19 @@ "main": "index.js", "exports": { ".": "./index.js", + "./flat": "./flat.js", "./base": "./base.js", "./hooks": "./hooks.js", + "./hooks-flat": "./hooks-flat.js", "./legacy": "./legacy.js", "./whitespace": "./whitespace.js", + "./whitespace-flat": "./whitespace-flat.js", "./rules/react": "./rules/react.js", "./rules/react-a11y": "./rules/react-a11y.js", "./rules/react-hooks": "./rules/react-hooks.js", + "./rules/flat/react": "./rules/flat/react.js", + "./rules/flat/react-a11y": "./rules/flat/react-a11y.js", + "./rules/flat/react-hooks": "./rules/flat/react-hooks.js", "./package.json": "./package.json" }, "scripts": { @@ -66,15 +72,15 @@ }, "homepage": "https://github.com/airbnb/javascript", "dependencies": { - "eslint-config-airbnb-base": "^15.0.0" + "eslint-config-airbnb-base": "file:../eslint-config-airbnb-base" }, "devDependencies": { "@babel/runtime": "^7.25.6", "babel-preset-airbnb": "^4.5.0", "babel-tape-runner": "^3.0.0", "eclint": "^2.8.1", - "eslint": "^7.32.0 || ^8.2.0", - "eslint-find-rules": "^4.1.0", + "eslint": "^7.32.0 || ^8.2.0 || >= 9", + "eslint-find-rules": "^5.0.0", "eslint-plugin-import": "^2.30.0", "eslint-plugin-jsx-a11y": "^6.10.0", "eslint-plugin-react": "^7.36.1", @@ -85,7 +91,7 @@ "tape": "^5.9.0" }, "peerDependencies": { - "eslint": "^7.32.0 || ^8.2.0", + "eslint": "^7.32.0 || ^8.2.0 || >= 9", "eslint-plugin-import": "^2.30.0", "eslint-plugin-jsx-a11y": "^6.10.0", "eslint-plugin-react": "^7.36.1", diff --git a/packages/eslint-config-airbnb/rules/flat/react-a11y.js b/packages/eslint-config-airbnb/rules/flat/react-a11y.js new file mode 100644 index 0000000000..57cbafa051 --- /dev/null +++ b/packages/eslint-config-airbnb/rules/flat/react-a11y.js @@ -0,0 +1,14 @@ +const jsxA11yPlugin = require('eslint-plugin-jsx-a11y'); +const reactPlugin = require('eslint-plugin-react'); +const base = require('../react-a11y'); + +module.exports = { + plugins: { + 'jsx-a11y': jsxA11yPlugin, + react: reactPlugin + }, + languageOptions: { + parserOptions: base.parserOptions + }, + rules: base.rules +}; diff --git a/packages/eslint-config-airbnb/rules/flat/react-hooks.js b/packages/eslint-config-airbnb/rules/flat/react-hooks.js new file mode 100644 index 0000000000..ba45291e4b --- /dev/null +++ b/packages/eslint-config-airbnb/rules/flat/react-hooks.js @@ -0,0 +1,12 @@ +const reactHooksPlugin = require('eslint-plugin-react-hooks'); +const base = require('../react-hooks'); + +module.exports = [{ + plugins: { + 'react-hooks': reactHooksPlugin, + }, + languageOptions: { + parserOptions: base.parserOptions + }, + rules: base.rules +}]; diff --git a/packages/eslint-config-airbnb/rules/flat/react.js b/packages/eslint-config-airbnb/rules/flat/react.js new file mode 100644 index 0000000000..b279495f69 --- /dev/null +++ b/packages/eslint-config-airbnb/rules/flat/react.js @@ -0,0 +1,13 @@ +const reactPlugin = require('eslint-plugin-react'); +const base = require('../react'); + +module.exports = [{ + plugins: { + react: reactPlugin + }, + languageOptions: { + parserOptions: base.parserOptions + }, + rules: base.rules, + settings: base.settings +}]; diff --git a/packages/eslint-config-airbnb/whitespace-flat-async.js b/packages/eslint-config-airbnb/whitespace-flat-async.js new file mode 100755 index 0000000000..cece59e417 --- /dev/null +++ b/packages/eslint-config-airbnb/whitespace-flat-async.js @@ -0,0 +1,49 @@ +#!/usr/bin/env node + +const { isArray } = Array; +const { entries } = Object; +const { ESLint } = require('eslint'); + +const baseConfig = require('./flat'); +const whitespaceRules = require('./whitespaceRules'); + +const severities = ['off', 'warn', 'error']; + +function getSeverity(ruleConfig) { + if (isArray(ruleConfig)) { + return getSeverity(ruleConfig[0]); + } + if (typeof ruleConfig === 'number') { + return severities[ruleConfig]; + } + return ruleConfig; +} + +async function onlyErrorOnRules(rulesToError, config) { + const errorsOnly = { + languageOptions: config.languageOptions || {}, + rules: config.rules || {}, + }; + const cli = new ESLint({ baseConfig: config }); + const baseRules = (await cli.calculateConfigForFile(require.resolve('./flat'))).rules; + + entries(baseRules).forEach((rule) => { + const ruleName = rule[0]; + const ruleConfig = rule[1]; + const severity = getSeverity(ruleConfig); + + if (rulesToError.indexOf(ruleName) === -1 && severity === 'error') { + if (isArray(ruleConfig)) { + errorsOnly.rules[ruleName] = ['warn'].concat(ruleConfig.slice(1)); + } else if (typeof ruleConfig === 'number') { + errorsOnly.rules[ruleName] = 1; + } else { + errorsOnly.rules[ruleName] = 'warn'; + } + } + }); + + return errorsOnly; +} + +onlyErrorOnRules(whitespaceRules, baseConfig).then((config) => console.log(JSON.stringify(config))); diff --git a/packages/eslint-config-airbnb/whitespace-flat.js b/packages/eslint-config-airbnb/whitespace-flat.js new file mode 100644 index 0000000000..6eca016503 --- /dev/null +++ b/packages/eslint-config-airbnb/whitespace-flat.js @@ -0,0 +1,13 @@ +/* eslint global-require: 0 */ + +const path = require('path'); +const { execSync } = require('child_process'); + +// NOTE: ESLint adds runtime statistics to the output (so it's no longer JSON) if TIMING is set +module.exports = JSON.parse(String(execSync(path.join(__dirname, 'whitespace-flat-async.js'), { + cwd: __dirname, // NOTE: cwd is set to prevent ESLint.calculateConfigForFile return {} + env: { + ...process.env, + TIMING: undefined, + } +})));