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..6d0a9a1f81 --- /dev/null +++ b/packages/eslint-config-airbnb-base/eslint.config.js @@ -0,0 +1,13 @@ +const base = require('./flat'); + +module.exports = [ + ...base, + { rules: { 'comma-dangle': 'off', 'max-len': 'off' } }, + { + files: ['test/**'], + rules: { + 'no-shadow': 'off', + 'id-length': ['error', { min: 2, properties: 'never', exceptions: ['t'] }], + }, + }, +]; diff --git a/packages/eslint-config-airbnb-base/flat.js b/packages/eslint-config-airbnb-base/flat.js new file mode 100644 index 0000000000..9690f3753b --- /dev/null +++ b/packages/eslint-config-airbnb-base/flat.js @@ -0,0 +1,44 @@ +const globals = require('globals'); +const importPlugin = require('eslint-plugin-import'); + +const bestPractices = require('./rules/best-practices'); +const errors = require('./rules/errors'); +const es6 = require('./rules/es6'); +const imports = require('./rules/imports'); +const node = require('./rules/node'); +const strict = require('./rules/strict'); +const style = require('./rules/style'); +const variables = require('./rules/variables'); + +module.exports = [ + { + languageOptions: { + ecmaVersion: 2018, + sourceType: 'module', + globals: { + ...globals.es2015, + ...globals.node, + }, + parserOptions: { + ecmaFeatures: { + generators: false, + objectLiteralDuplicateProperties: false, + }, + }, + }, + plugins: { + import: importPlugin, + }, + settings: imports.settings, + rules: { + ...bestPractices.rules, + ...errors.rules, + ...node.rules, + ...style.rules, + ...variables.rules, + ...es6.rules, + ...imports.rules, + ...strict.rules, + }, + }, +]; diff --git a/packages/eslint-config-airbnb-base/package.json b/packages/eslint-config-airbnb-base/package.json index 8f039d4d7c..7a7acc42f9 100644 --- a/packages/eslint-config-airbnb-base/package.json +++ b/packages/eslint-config-airbnb-base/package.json @@ -15,6 +15,7 @@ "./rules/imports": "./rules/imports.js", "./rules/strict": "./rules/strict.js", "./rules/variables": "./rules/variables.js", + "./flat": "./flat.js", "./package.json": "./package.json" }, "scripts": { @@ -72,21 +73,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.0.0", + "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.0.0", "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/whitespace-async.js b/packages/eslint-config-airbnb-base/whitespace-async.js index 06f4f89075..a1cb77e8cd 100755 --- a/packages/eslint-config-airbnb-base/whitespace-async.js +++ b/packages/eslint-config-airbnb-base/whitespace-async.js @@ -4,7 +4,8 @@ const { isArray } = Array; const { entries } = Object; const { ESLint } = require('eslint'); -const baseConfig = require('.'); +const isFlat = ESLint.configType === 'flat'; +const baseConfig = isFlat ? require('./flat') : require('.'); const whitespaceRules = require('./whitespaceRules'); const severities = ['off', 'warn', 'error']; @@ -20,11 +21,10 @@ function getSeverity(ruleConfig) { } async function onlyErrorOnRules(rulesToError, config) { - const errorsOnly = { ...config }; - const cli = new ESLint({ - useEslintrc: false, - baseConfig: config - }); + const errorsOnly = isFlat ? { rules: {} } : { ...config }; + const cli = isFlat + ? new ESLint({ overrideConfigFile: true, overrideConfig: config }) + : new ESLint({ useEslintrc: false, baseConfig: config }); const baseRules = (await cli.calculateConfigForFile(require.resolve('./'))).rules; entries(baseRules).forEach((rule) => { diff --git a/packages/eslint-config-airbnb-base/whitespace.js b/packages/eslint-config-airbnb-base/whitespace.js index 01e5198671..2718974ac0 100644 --- a/packages/eslint-config-airbnb-base/whitespace.js +++ b/packages/eslint-config-airbnb-base/whitespace.js @@ -50,12 +50,27 @@ if (CLIEngine) { } else { const path = require('path'); const { execSync } = require('child_process'); + const { ESLint } = require('eslint'); // 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-async.js'), { + const ruleOverrides = JSON.parse(String(execSync(path.join(__dirname, 'whitespace-async.js'), { env: { ...process.env, TIMING: undefined, } }))); + + if (ESLint.configType === 'flat') { + // In flat mode, whitespace-async.js outputs only { rules: {} } since plugins + // are not JSON-serializable. Merge the rule overrides with the full flat config + // so consumers get a complete config with plugins, settings, and languageOptions. + const flatConfig = require('./flat'); + const lastConfig = flatConfig[flatConfig.length - 1]; + module.exports = [ + ...flatConfig.slice(0, -1), + { ...lastConfig, rules: { ...lastConfig.rules, ...ruleOverrides.rules } }, + ]; + } else { + module.exports = ruleOverrides; + } } diff --git a/packages/eslint-config-airbnb/eslint.config.js b/packages/eslint-config-airbnb/eslint.config.js new file mode 100644 index 0000000000..81f8ac4d2a --- /dev/null +++ b/packages/eslint-config-airbnb/eslint.config.js @@ -0,0 +1,13 @@ +const airbnb = require('./flat'); + +module.exports = [ + ...airbnb, + { rules: { 'comma-dangle': 'off' } }, + { + files: ['test/**'], + rules: { + 'no-shadow': 'off', + 'id-length': ['error', { min: 2, properties: 'never', exceptions: ['t'] }], + }, + }, +]; diff --git a/packages/eslint-config-airbnb/flat-base.js b/packages/eslint-config-airbnb/flat-base.js new file mode 100644 index 0000000000..2707c16704 --- /dev/null +++ b/packages/eslint-config-airbnb/flat-base.js @@ -0,0 +1 @@ +module.exports = require('eslint-config-airbnb-base/flat'); diff --git a/packages/eslint-config-airbnb/flat-hooks.js b/packages/eslint-config-airbnb/flat-hooks.js new file mode 100644 index 0000000000..05b735210f --- /dev/null +++ b/packages/eslint-config-airbnb/flat-hooks.js @@ -0,0 +1,20 @@ +const reactHooksPlugin = require('eslint-plugin-react-hooks'); + +const reactHooks = require('./rules/react-hooks'); + +module.exports = [ + { + files: ['**/*.{js,jsx,mjs,cjs}'], + plugins: { + 'react-hooks': reactHooksPlugin, + }, + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + }, + rules: reactHooks.rules, + }, +]; diff --git a/packages/eslint-config-airbnb/flat.js b/packages/eslint-config-airbnb/flat.js new file mode 100644 index 0000000000..9cf9469c1e --- /dev/null +++ b/packages/eslint-config-airbnb/flat.js @@ -0,0 +1,30 @@ +const reactPlugin = require('eslint-plugin-react'); +const jsxA11yPlugin = require('eslint-plugin-jsx-a11y'); + +const baseFlatConfig = require('eslint-config-airbnb-base/flat'); + +const react = require('./rules/react'); +const reactA11y = require('./rules/react-a11y'); + +module.exports = [ + ...baseFlatConfig, + { + files: ['**/*.{js,jsx,mjs,cjs}'], + plugins: { + react: reactPlugin, + 'jsx-a11y': jsxA11yPlugin, + }, + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + }, + settings: react.settings, + rules: { + ...react.rules, + ...reactA11y.rules, + }, + }, +]; diff --git a/packages/eslint-config-airbnb/package.json b/packages/eslint-config-airbnb/package.json index c36359ea7c..2ce1ebac10 100644 --- a/packages/eslint-config-airbnb/package.json +++ b/packages/eslint-config-airbnb/package.json @@ -12,6 +12,9 @@ "./rules/react": "./rules/react.js", "./rules/react-a11y": "./rules/react-a11y.js", "./rules/react-hooks": "./rules/react-hooks.js", + "./flat": "./flat.js", + "./flat-base": "./flat-base.js", + "./flat-hooks": "./flat-hooks.js", "./package.json": "./package.json" }, "scripts": { @@ -73,8 +76,8 @@ "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.0.0", + "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 +88,7 @@ "tape": "^5.9.0" }, "peerDependencies": { - "eslint": "^7.32.0 || ^8.2.0", + "eslint": "^7.32.0 || ^8.2.0 || ^9.0.0", "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/test/test-react-order.js b/packages/eslint-config-airbnb/test/test-react-order.js index 0ab4f937f7..efd1a1ebd1 100644 --- a/packages/eslint-config-airbnb/test/test-react-order.js +++ b/packages/eslint-config-airbnb/test/test-react-order.js @@ -4,6 +4,8 @@ import eslintrc from '..'; import reactRules from '../rules/react'; import reactA11yRules from '../rules/react-a11y'; +const isFlat = ESLint.configType === 'flat'; + const rules = { // It is okay to import devDependencies in tests. 'import/no-extraneous-dependencies': [2, { devDependencies: true }], @@ -12,17 +14,32 @@ const rules = { // otherwise we need some junk in our fixture code 'react/no-unused-class-component-methods': 0, }; -const cli = new (CLIEngine || ESLint)({ - useEslintrc: false, - baseConfig: eslintrc, - ...(CLIEngine ? { rules } : { overrideConfig: { rules } }), -}); + +function createCli() { + if (isFlat) { + // eslint-disable-next-line global-require + const flatConfig = require('../flat'); + return new ESLint({ + overrideConfigFile: true, + overrideConfig: [...flatConfig, { rules }], + }); + } + return new (CLIEngine || ESLint)({ + useEslintrc: false, + baseConfig: eslintrc, + ...(CLIEngine ? { rules } : { overrideConfig: { rules } }), + }); +} +const cli = createCli(); async function lint(text) { // @see https://eslint.org/docs/developer-guide/nodejs-api.html#executeonfiles // @see https://eslint.org/docs/developer-guide/nodejs-api.html#executeontext - const linter = CLIEngine ? cli.executeOnText(text) : await cli.lintText(text); - return (CLIEngine ? linter.results : linter)[0]; + if (CLIEngine) { + return cli.executeOnText(text).results[0]; + } + const results = await cli.lintText(text, isFlat ? { filePath: 'test.jsx' } : undefined); + return results[0]; } function wrapComponent(body) { diff --git a/packages/eslint-config-airbnb/whitespace-async.js b/packages/eslint-config-airbnb/whitespace-async.js index 06f4f89075..a1cb77e8cd 100755 --- a/packages/eslint-config-airbnb/whitespace-async.js +++ b/packages/eslint-config-airbnb/whitespace-async.js @@ -4,7 +4,8 @@ const { isArray } = Array; const { entries } = Object; const { ESLint } = require('eslint'); -const baseConfig = require('.'); +const isFlat = ESLint.configType === 'flat'; +const baseConfig = isFlat ? require('./flat') : require('.'); const whitespaceRules = require('./whitespaceRules'); const severities = ['off', 'warn', 'error']; @@ -20,11 +21,10 @@ function getSeverity(ruleConfig) { } async function onlyErrorOnRules(rulesToError, config) { - const errorsOnly = { ...config }; - const cli = new ESLint({ - useEslintrc: false, - baseConfig: config - }); + const errorsOnly = isFlat ? { rules: {} } : { ...config }; + const cli = isFlat + ? new ESLint({ overrideConfigFile: true, overrideConfig: config }) + : new ESLint({ useEslintrc: false, baseConfig: config }); const baseRules = (await cli.calculateConfigForFile(require.resolve('./'))).rules; entries(baseRules).forEach((rule) => { diff --git a/packages/eslint-config-airbnb/whitespace.js b/packages/eslint-config-airbnb/whitespace.js index 01e5198671..2718974ac0 100644 --- a/packages/eslint-config-airbnb/whitespace.js +++ b/packages/eslint-config-airbnb/whitespace.js @@ -50,12 +50,27 @@ if (CLIEngine) { } else { const path = require('path'); const { execSync } = require('child_process'); + const { ESLint } = require('eslint'); // 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-async.js'), { + const ruleOverrides = JSON.parse(String(execSync(path.join(__dirname, 'whitespace-async.js'), { env: { ...process.env, TIMING: undefined, } }))); + + if (ESLint.configType === 'flat') { + // In flat mode, whitespace-async.js outputs only { rules: {} } since plugins + // are not JSON-serializable. Merge the rule overrides with the full flat config + // so consumers get a complete config with plugins, settings, and languageOptions. + const flatConfig = require('./flat'); + const lastConfig = flatConfig[flatConfig.length - 1]; + module.exports = [ + ...flatConfig.slice(0, -1), + { ...lastConfig, rules: { ...lastConfig.rules, ...ruleOverrides.rules } }, + ]; + } else { + module.exports = ruleOverrides; + } }