diff --git a/configs/all.js b/configs/all.js new file mode 100644 index 0000000000..ace4dc3d75 --- /dev/null +++ b/configs/all.js @@ -0,0 +1,17 @@ +'use strict'; +const conflictingRules = require('./conflicting-rules.js'); + +module.exports = { + ...require('./base.js'), + rules: { + ...Object.fromEntries( + Object.entries(require('./recommended.js').rules) + .filter( + ruleEntry => + !Object.keys(conflictingRules.rules).includes(ruleEntry[0]), + ) + .map(ruleEntry => [ruleEntry[0], 'error']), + ), + ...conflictingRules.rules, + }, +}; diff --git a/configs/base.js b/configs/base.js new file mode 100644 index 0000000000..8071bb108c --- /dev/null +++ b/configs/base.js @@ -0,0 +1,13 @@ +'use strict'; +module.exports = { + env: { + es6: true, + }, + parserOptions: { + ecmaVersion: 2021, + sourceType: 'module', + }, + plugins: [ + 'unicorn', + ], +}; diff --git a/configs/conflicting-rules.js b/configs/conflicting-rules.js new file mode 100644 index 0000000000..aa98a5bffb --- /dev/null +++ b/configs/conflicting-rules.js @@ -0,0 +1,6 @@ +'use strict'; +module.exports = { + rules: { + 'no-nested-ternary': 'off', + }, +}; diff --git a/configs/recommended.js b/configs/recommended.js new file mode 100644 index 0000000000..750f8df822 --- /dev/null +++ b/configs/recommended.js @@ -0,0 +1,110 @@ +'use strict'; +module.exports = { + ...require('./base.js'), + rules: { + 'unicorn/better-regex': 'error', + 'unicorn/catch-error-name': 'error', + 'unicorn/consistent-destructuring': 'error', + 'unicorn/consistent-function-scoping': 'error', + 'unicorn/custom-error-definition': 'off', + 'unicorn/empty-brace-spaces': 'error', + 'unicorn/error-message': 'error', + 'unicorn/escape-case': 'error', + 'unicorn/expiring-todo-comments': 'error', + 'unicorn/explicit-length-check': 'error', + 'unicorn/filename-case': 'error', + 'unicorn/import-index': 'off', + 'unicorn/import-style': 'error', + 'unicorn/new-for-builtins': 'error', + 'unicorn/no-abusive-eslint-disable': 'error', + 'unicorn/no-array-callback-reference': 'error', + 'unicorn/no-array-for-each': 'error', + 'unicorn/no-array-method-this-argument': 'error', + 'unicorn/no-array-push-push': 'error', + 'unicorn/no-array-reduce': 'error', + 'unicorn/no-console-spaces': 'error', + 'unicorn/no-document-cookie': 'error', + 'unicorn/no-for-loop': 'error', + 'unicorn/no-hex-escape': 'error', + 'unicorn/no-instanceof-array': 'error', + 'unicorn/no-invalid-remove-event-listener': 'error', + 'unicorn/no-keyword-prefix': 'off', + 'unicorn/no-lonely-if': 'error', + 'no-nested-ternary': 'off', + 'unicorn/no-nested-ternary': 'error', + 'unicorn/no-new-array': 'error', + 'unicorn/no-new-buffer': 'error', + 'unicorn/no-null': 'error', + 'unicorn/no-object-as-default-parameter': 'error', + 'unicorn/no-process-exit': 'error', + 'unicorn/no-static-only-class': 'error', + 'unicorn/no-this-assignment': 'error', + 'unicorn/no-unreadable-array-destructuring': 'error', + 'unicorn/no-unsafe-regex': 'off', + 'unicorn/no-unused-properties': 'off', + 'unicorn/no-useless-fallback-in-spread': 'error', + 'unicorn/no-useless-length-check': 'error', + 'unicorn/no-useless-spread': 'error', + 'unicorn/no-useless-undefined': 'error', + 'unicorn/no-zero-fractions': 'error', + 'unicorn/number-literal-case': 'error', + 'unicorn/numeric-separators-style': 'error', + 'unicorn/prefer-add-event-listener': 'error', + 'unicorn/prefer-array-find': 'error', + 'unicorn/prefer-array-flat': 'error', + 'unicorn/prefer-array-flat-map': 'error', + 'unicorn/prefer-array-index-of': 'error', + 'unicorn/prefer-array-some': 'error', + // TODO: Enable this by default when targeting a Node.js version that supports `Array#at`. + 'unicorn/prefer-at': 'off', + 'unicorn/prefer-date-now': 'error', + 'unicorn/prefer-default-parameters': 'error', + 'unicorn/prefer-dom-node-append': 'error', + 'unicorn/prefer-dom-node-dataset': 'error', + 'unicorn/prefer-dom-node-remove': 'error', + 'unicorn/prefer-dom-node-text-content': 'error', + 'unicorn/prefer-includes': 'error', + 'unicorn/prefer-keyboard-event-key': 'error', + 'unicorn/prefer-math-trunc': 'error', + 'unicorn/prefer-modern-dom-apis': 'error', + 'unicorn/prefer-module': 'error', + 'unicorn/prefer-negative-index': 'error', + 'unicorn/prefer-node-protocol': 'error', + 'unicorn/prefer-number-properties': 'error', + 'unicorn/prefer-object-from-entries': 'error', + // TODO: Enable this by default when targeting a Node.js version that supports `Object.hasOwn`. + 'unicorn/prefer-object-has-own': 'off', + 'unicorn/prefer-optional-catch-binding': 'error', + 'unicorn/prefer-prototype-methods': 'error', + 'unicorn/prefer-query-selector': 'error', + 'unicorn/prefer-reflect-apply': 'error', + 'unicorn/prefer-regexp-test': 'error', + 'unicorn/prefer-set-has': 'error', + 'unicorn/prefer-spread': 'error', + // TODO: Enable this by default when targeting Node.js 16. + 'unicorn/prefer-string-replace-all': 'off', + 'unicorn/prefer-string-slice': 'error', + 'unicorn/prefer-string-starts-ends-with': 'error', + 'unicorn/prefer-string-trim-start-end': 'error', + 'unicorn/prefer-switch': 'error', + 'unicorn/prefer-ternary': 'error', + // TODO: Enable this by default when targeting Node.js 14. + 'unicorn/prefer-top-level-await': 'off', + 'unicorn/prefer-type-error': 'error', + 'unicorn/prevent-abbreviations': 'error', + 'unicorn/require-array-join-separator': 'error', + 'unicorn/require-number-to-fixed-digits-argument': 'error', + 'unicorn/require-post-message-target-origin': 'error', + 'unicorn/string-content': 'off', + 'unicorn/throw-new-error': 'error', + ...require('./conflicting-rules.js').rules, + }, + overrides: [ + { + files: ['*.ts', '*.tsx'], + rules: { + 'unicorn/require-post-message-target-origin': 'off', + }, + }, + ], +}; diff --git a/docs/new-rule.md b/docs/new-rule.md index 498c48ac94..abd2be0c78 100644 --- a/docs/new-rule.md +++ b/docs/new-rule.md @@ -17,7 +17,7 @@ Use the [`astexplorer` site](https://astexplorer.net) with the `espree` parser a - Open “rules/{RULE_ID}.js” and implement the rule logic. - Add the correct [`meta.type`](https://eslint.org/docs/developer-guide/working-with-rules#rule-basics) to the rule. - Open “docs/rules/{RULE_ID}.js” and write some documentation. -- Double check `index.js` and `readme.md`, make sure the new rule is correctly added. +- Double check `configs/recommended.js` and `readme.md`, make sure the new rule is correctly added. - Run `$ npm test` to ensure the tests pass. - Run `$ npm run integration` to run the rules against real projects to ensure your rule does not fail on real-world code. - Open a pull request with a title in exactly the format `` Add `rule-name` rule ``, for example, `` Add `no-unused-properties` rule ``. diff --git a/index.js b/index.js index a1219aee0d..6cf8b95487 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,8 @@ 'use strict'; const createDeprecatedRules = require('./rules/utils/create-deprecated-rules.js'); const {loadRules} = require('./rules/utils/rule.js'); +const recommendedConfig = require('./configs/recommended.js'); +const all = require('./configs/all.js'); const deprecatedRules = createDeprecatedRules({ // {ruleId: ReplacementRuleId | ReplacementRuleId[]}, if no replacement, use `{ruleId: []}` @@ -26,122 +28,7 @@ module.exports = { ...deprecatedRules, }, configs: { - recommended: { - env: { - es6: true, - }, - parserOptions: { - ecmaVersion: 2021, - sourceType: 'module', - }, - plugins: [ - 'unicorn', - ], - rules: { - 'unicorn/better-regex': 'error', - 'unicorn/catch-error-name': 'error', - 'unicorn/consistent-destructuring': 'error', - 'unicorn/consistent-function-scoping': 'error', - 'unicorn/custom-error-definition': 'off', - 'unicorn/empty-brace-spaces': 'error', - 'unicorn/error-message': 'error', - 'unicorn/escape-case': 'error', - 'unicorn/expiring-todo-comments': 'error', - 'unicorn/explicit-length-check': 'error', - 'unicorn/filename-case': 'error', - 'unicorn/import-index': 'off', - 'unicorn/import-style': 'error', - 'unicorn/new-for-builtins': 'error', - 'unicorn/no-abusive-eslint-disable': 'error', - 'unicorn/no-array-callback-reference': 'error', - 'unicorn/no-array-for-each': 'error', - 'unicorn/no-array-method-this-argument': 'error', - 'unicorn/no-array-push-push': 'error', - 'unicorn/no-array-reduce': 'error', - 'unicorn/no-console-spaces': 'error', - 'unicorn/no-document-cookie': 'error', - 'unicorn/no-for-loop': 'error', - 'unicorn/no-hex-escape': 'error', - 'unicorn/no-instanceof-array': 'error', - 'unicorn/no-invalid-remove-event-listener': 'error', - 'unicorn/no-keyword-prefix': 'off', - 'unicorn/no-lonely-if': 'error', - 'no-nested-ternary': 'off', - 'unicorn/no-nested-ternary': 'error', - 'unicorn/no-new-array': 'error', - 'unicorn/no-new-buffer': 'error', - 'unicorn/no-null': 'error', - 'unicorn/no-object-as-default-parameter': 'error', - 'unicorn/no-process-exit': 'error', - 'unicorn/no-static-only-class': 'error', - 'unicorn/no-this-assignment': 'error', - 'unicorn/no-unreadable-array-destructuring': 'error', - 'unicorn/no-unsafe-regex': 'off', - 'unicorn/no-unused-properties': 'off', - 'unicorn/no-useless-fallback-in-spread': 'error', - 'unicorn/no-useless-length-check': 'error', - 'unicorn/no-useless-spread': 'error', - 'unicorn/no-useless-undefined': 'error', - 'unicorn/no-zero-fractions': 'error', - 'unicorn/number-literal-case': 'error', - 'unicorn/numeric-separators-style': 'error', - 'unicorn/prefer-add-event-listener': 'error', - 'unicorn/prefer-array-find': 'error', - 'unicorn/prefer-array-flat': 'error', - 'unicorn/prefer-array-flat-map': 'error', - 'unicorn/prefer-array-index-of': 'error', - 'unicorn/prefer-array-some': 'error', - // TODO: Enable this by default when targeting a Node.js version that supports `Array#at`. - 'unicorn/prefer-at': 'off', - 'unicorn/prefer-date-now': 'error', - 'unicorn/prefer-default-parameters': 'error', - 'unicorn/prefer-dom-node-append': 'error', - 'unicorn/prefer-dom-node-dataset': 'error', - 'unicorn/prefer-dom-node-remove': 'error', - 'unicorn/prefer-dom-node-text-content': 'error', - 'unicorn/prefer-includes': 'error', - 'unicorn/prefer-keyboard-event-key': 'error', - 'unicorn/prefer-math-trunc': 'error', - 'unicorn/prefer-modern-dom-apis': 'error', - 'unicorn/prefer-module': 'error', - 'unicorn/prefer-negative-index': 'error', - 'unicorn/prefer-node-protocol': 'error', - 'unicorn/prefer-number-properties': 'error', - 'unicorn/prefer-object-from-entries': 'error', - // TODO: Enable this by default when targeting a Node.js version that supports `Object.hasOwn`. - 'unicorn/prefer-object-has-own': 'off', - 'unicorn/prefer-optional-catch-binding': 'error', - 'unicorn/prefer-prototype-methods': 'error', - 'unicorn/prefer-query-selector': 'error', - 'unicorn/prefer-reflect-apply': 'error', - 'unicorn/prefer-regexp-test': 'error', - 'unicorn/prefer-set-has': 'error', - 'unicorn/prefer-spread': 'error', - // TODO: Enable this by default when targeting Node.js 16. - 'unicorn/prefer-string-replace-all': 'off', - 'unicorn/prefer-string-slice': 'error', - 'unicorn/prefer-string-starts-ends-with': 'error', - 'unicorn/prefer-string-trim-start-end': 'error', - 'unicorn/prefer-switch': 'error', - 'unicorn/prefer-ternary': 'error', - // TODO: Enable this by default when targeting Node.js 14. - 'unicorn/prefer-top-level-await': 'off', - 'unicorn/prefer-type-error': 'error', - 'unicorn/prevent-abbreviations': 'error', - 'unicorn/require-array-join-separator': 'error', - 'unicorn/require-number-to-fixed-digits-argument': 'error', - 'unicorn/require-post-message-target-origin': 'error', - 'unicorn/string-content': 'off', - 'unicorn/throw-new-error': 'error', - }, - overrides: [ - { - files: ['*.ts', '*.tsx'], - rules: { - 'unicorn/require-post-message-target-origin': 'off', - }, - }, - ], - }, + recommended: recommendedConfig, + all, }, }; diff --git a/package.json b/package.json index b2de72b962..c80e85c95d 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,8 @@ }, "files": [ "index.js", - "rules" + "rules", + "configs" ], "keywords": [ "eslint", diff --git a/readme.md b/readme.md index 033064e781..500c7bbb52 100644 --- a/readme.md +++ b/readme.md @@ -255,7 +255,7 @@ See [docs/deprecated-rules.md](docs/deprecated-rules.md) ## Recommended config -This plugin exports a [`recommended` config](index.js) that enforces good practices. +This plugin exports a [`recommended` config](configs/recommended.js) that enforces good practices. Enable it in your `package.json` with the `extends` option: @@ -272,6 +272,23 @@ See the [ESLint docs](https://eslint.org/docs/user-guide/configuring/configurati **Note**: This config will also enable the correct [parser options](https://eslint.org/docs/user-guide/configuring/language-options#specifying-parser-options) and [environment](https://eslint.org/docs/user-guide/configuring/language-options#specifying-environments). +## All config + +This plugin exports an [`all` config](configs/all.js) that makes use of all rules (except for deprecated ones). + +Enable it in your `package.json` with the `extends` option: + +```json +{ + "name": "my-awesome-project", + "eslintConfig": { + "extends": "plugin:unicorn/all" + } +} +``` + +See the [ESLint docs](https://eslint.org/docs/user-guide/configuring/configuration-files#extending-configuration-files) for more information about extending config files. + ## Maintainers - [Sindre Sorhus](https://github.com/sindresorhus) diff --git a/scripts/create-rule.mjs b/scripts/create-rule.mjs index dd7e5272ab..b744a7019e 100644 --- a/scripts/create-rule.mjs +++ b/scripts/create-rule.mjs @@ -36,20 +36,20 @@ function renderTemplate({source, target, data}) { return fs.writeFileSync(targetFile, content); } -function updateIndex(id) { - const RULE_START = '\t\t\trules: {\n'; - const RULE_END = '\n\t\t\t}'; - const RULE_INDENT = '\t'.repeat(4); +function updateRecommended(id) { + const RULE_START = '\n\trules: {\n'; + const RULE_END = '\n\t}'; + const RULE_INDENT = '\t'.repeat(2); let ruleContent = `${RULE_INDENT}'unicorn/${id}': 'error',`; - const file = path.join(ROOT, 'index.js'); + const file = path.join(ROOT, 'configs/recommended.js'); const content = fs.readFileSync(file, 'utf8'); const [before, rest] = content.split(RULE_START); const [rules, after] = rest.split(RULE_END); const lines = rules.split('\n'); if (!lines.every(line => line.startsWith(RULE_INDENT))) { - throw new Error('Unexpected content in “index.js”.'); + throw new Error('Unexpected content in “configs/recommended.js”.'); } const unicornRuleLines = lines.filter(line => line.startsWith(`${RULE_INDENT}'unicorn/`)); @@ -156,7 +156,7 @@ function updateIndex(id) { target: `test/${id}.mjs`, data, }); - updateIndex(id); + updateRecommended(id); try { await execa('code', [ diff --git a/test/package.mjs b/test/package.mjs index 95c63b5857..e531ec4f31 100644 --- a/test/package.mjs +++ b/test/package.mjs @@ -2,6 +2,7 @@ import fs from 'node:fs'; import path from 'node:path'; import test from 'ava'; import pify from 'pify'; +import {ESLint} from 'eslint'; import index from '../index.js'; let ruleFiles; @@ -52,10 +53,27 @@ test('Every rule is defined in index file in alphabetical order', t => { ruleFiles.length - deprecatedRules.length, 'There are more exported rules in the recommended config than rule files.', ); + t.is( + Object.keys(index.configs.all.rules).length - deprecatedRules.length - ignoredRules.length, + ruleFiles.length - deprecatedRules.length, + 'There are more rules than those exported in the all config.', + ); testSorted(t, Object.keys(index.configs.recommended.rules), 'configs.recommended.rules'); }); +test('validate configuration', async t => { + const results = []; + for (const config of Object.keys(index.configs)) { + results.push(t.notThrowsAsync( + new ESLint({overrideConfigFile: './configs/' + config + '.js'}).lintText(''), + `Configuration file for "${config}" is invalid at "./configs/${config}.js"`, + )); + } + + await Promise.all(results); +}); + test('Every rule is defined in readme.md usage and list of rules in alphabetical order', async t => { const readme = await pify(fs.readFile)('readme.md', 'utf8'); let usageRules; diff --git a/test/run-rules-on-codebase/lint.mjs b/test/run-rules-on-codebase/lint.mjs index 2365927511..e7a4070fa6 100644 --- a/test/run-rules-on-codebase/lint.mjs +++ b/test/run-rules-on-codebase/lint.mjs @@ -2,22 +2,13 @@ import process from 'node:process'; import {ESLint} from 'eslint'; import unicorn from '../../index.js'; +import allConfig from '../../configs/all.js'; -const {recommended} = unicorn.configs; const files = [process.argv[2] || '.']; const fix = process.argv.includes('--fix'); -const enableAllRules = Object.fromEntries( - Object.entries(recommended.rules) - .filter(([id]) => id.startsWith('unicorn/')) - .map(([id]) => [id, 'error']), -); - const eslint = new ESLint({ - baseConfig: { - ...recommended, - rules: enableAllRules, - }, + baseConfig: allConfig, useEslintrc: false, extensions: ['.js', '.mjs'], plugins: { diff --git a/test/smoke/eslint-remote-tester.config.js b/test/smoke/eslint-remote-tester.config.js index 50bdbde9da..e7b010718f 100644 --- a/test/smoke/eslint-remote-tester.config.js +++ b/test/smoke/eslint-remote-tester.config.js @@ -49,6 +49,6 @@ module.exports = { jsx: true, }, }, - extends: ['plugin:unicorn/recommended'], + extends: ['plugin:unicorn/all'], }, };