From ac7c70e81c53a50556c0d4ed678c3d08e7ad4079 Mon Sep 17 00:00:00 2001 From: Guz013 <43732358+Guz013@users.noreply.github.com> Date: Sat, 2 Sep 2023 08:19:33 -0300 Subject: [PATCH] =?UTF-8?q?feat:=20=E2=9C=A8=20rules=20variations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- configs/js/src/configs/core.js | 1 + configs/js/src/configs/documentation.js | 19 ++--- .../js/src/configs/environments/browser.js | 19 ++--- configs/js/src/configs/environments/node.js | 25 ++++--- configs/js/src/configs/formatting.js | 22 +++--- configs/js/src/configs/index.d.ts | 49 ++++++++---- configs/js/src/configs/naming.js | 19 ++--- configs/js/src/configs/overrides.js | 18 +++-- configs/js/src/configs/problems.js | 19 ++--- .../js/src/configs/suggestions-typescript.js | 22 +++--- configs/js/src/configs/suggestions.js | 23 +++--- configs/js/src/lib/rule-variations.js | 75 +++++++++++++++++++ configs/js/src/presets/recommended.js | 10 +-- configs/js/src/presets/strict.js | 10 +-- 14 files changed, 218 insertions(+), 113 deletions(-) create mode 100644 configs/js/src/lib/rule-variations.js diff --git a/configs/js/src/configs/core.js b/configs/js/src/configs/core.js index ffa2004..786aa6d 100644 --- a/configs/js/src/configs/core.js +++ b/configs/js/src/configs/core.js @@ -44,6 +44,7 @@ const config = { node: true, typescript: true, }, + 'jsdoc/mode': 'typescript', }, }; export default config; diff --git a/configs/js/src/configs/documentation.js b/configs/js/src/configs/documentation.js index a7fd411..336f8c2 100644 --- a/configs/js/src/configs/documentation.js +++ b/configs/js/src/configs/documentation.js @@ -1,9 +1,10 @@ +/* eslint-disable import/no-relative-parent-imports */ /* eslint-disable unicorn/no-useless-spread */ -// eslint-disable-next-line import/no-relative-parent-imports +import { createVariations } from '../lib/rule-variations.js'; import { jsFiles, tsFiles } from '../constants.js'; -/** @type {import('eslint').Linter.FlatConfig} */ -const recommended = { +/** @type {import('./index.d.ts').ConfigVariations} */ +const recommended = createVariations({ files: [...tsFiles, ...jsFiles], rules: { ...{}, // Plugin: eslint-plugin-jsdoc @@ -14,19 +15,19 @@ const recommended = { 'jsdoc/require-property-description': 'error', 'jsdoc/require-returns-check': 'error', }, -}; +}); -/** @type {import('eslint').Linter.FlatConfig} */ -const strict = { - ...recommended, +/** @type {import('./index.d.ts').ConfigVariations} */ +const strict = createVariations({ + ...recommended.error, rules: { - ...recommended.rules, + ...recommended.error.rules, ...{}, // Plugin: eslint-plugin-jsdoc 'jsdoc/require-description': 'error', 'jsdoc/require-file-overview': 'error', }, -}; +}); const documentation = { recommended, strict }; export default documentation; diff --git a/configs/js/src/configs/environments/browser.js b/configs/js/src/configs/environments/browser.js index 27c8ee0..7bc9673 100644 --- a/configs/js/src/configs/environments/browser.js +++ b/configs/js/src/configs/environments/browser.js @@ -1,9 +1,10 @@ +/* eslint-disable import/no-relative-parent-imports */ /* eslint-disable unicorn/no-useless-spread */ -// eslint-disable-next-line import/no-relative-parent-imports +import { createVariations } from '../../lib/rule-variations.js'; import { jsFiles, tsFiles } from '../../constants.js'; -/** @type {import('eslint').Linter.FlatConfig} */ -const recommended = { +/** @type {import('../index.d.ts').ConfigVariations} */ +const recommended = createVariations({ files: [...tsFiles, ...jsFiles], rules: { ...{}, // Plugin: eslint-plugin-unicorn @@ -16,15 +17,15 @@ const recommended = { 'unicorn/prefer-modern-dom-apis': 'error', 'unicorn/prefer-query-selector': 'error', }, -}; +}); -/** @type {import('eslint').Linter.FlatConfig} */ -const strict = { - ...recommended, +/** @type {import('../index.d.ts').ConfigVariations} */ +const strict = createVariations({ + ...recommended.error, rules: { - ...recommended.rules, + ...recommended.error.rules, }, -}; +}); const node = { recommended, strict }; export default node; diff --git a/configs/js/src/configs/environments/node.js b/configs/js/src/configs/environments/node.js index e94d632..6f5a70d 100644 --- a/configs/js/src/configs/environments/node.js +++ b/configs/js/src/configs/environments/node.js @@ -1,9 +1,10 @@ +/* eslint-disable import/no-relative-parent-imports */ /* eslint-disable unicorn/no-useless-spread */ -// eslint-disable-next-line import/no-relative-parent-imports +import { createVariations } from '../../lib/rule-variations.js'; import { jsFiles, tsFiles } from '../../constants.js'; -/** @type {import('eslint').Linter.FlatConfig} */ -const commonjs = { +/** @type {import('../index.d.ts').ConfigVariations} */ +const commonjs = createVariations({ files: ['**/*.cts', '**/*.cjs'], rules: { ...{}, // Plugin: @typescript-eslint/eslint-plugin @@ -16,10 +17,10 @@ const commonjs = { ...{}, // Plugin: eslint-plugin-import 'import/no-commonjs': 'off', }, -}; +}); -/** @type {import('eslint').Linter.FlatConfig} */ -const recommended = { +/** @type {import('../index.d.ts').ConfigVariations} */ +const recommended = createVariations({ files: [...tsFiles, ...jsFiles], rules: { ...{}, // Plugin: eslint-plugin-unicorn @@ -28,15 +29,15 @@ const recommended = { ...{}, // Plugin: eslint-plugin-import 'import/no-dynamic-require': 'error', }, -}; +}); -/** @type {import('eslint').Linter.FlatConfig} */ -const strict = { - ...recommended, +/** @type {import('../index.d.ts').ConfigVariations} */ +const strict = createVariations({ + ...recommended.error, rules: { - ...recommended.rules, + ...recommended.error.rules, }, -}; +}); const node = { commonjs, recommended, strict }; export default node; diff --git a/configs/js/src/configs/formatting.js b/configs/js/src/configs/formatting.js index 76c8bc2..7f23d25 100644 --- a/configs/js/src/configs/formatting.js +++ b/configs/js/src/configs/formatting.js @@ -1,14 +1,15 @@ +/* eslint-disable import/no-relative-parent-imports */ /* eslint-disable unicorn/no-useless-spread */ import perfectionistPlugin from 'eslint-plugin-perfectionist'; -// eslint-disable-next-line import/no-relative-parent-imports +import { createVariations } from '../lib/rule-variations.js'; import { jsFiles, tsFiles } from '../constants.js'; /** * This config relates to code formatting and style in JavaScript and TypeScript * Recommended alternative, better for projects in prototyping phases. - * @type {import('eslint').Linter.FlatConfig} + * @type {import('./index.d.ts').ConfigVariations} */ -const recommended = { +const recommended = createVariations({ files: [...tsFiles, ...jsFiles], plugins: { // @ts-expect-error because plugin doesn't export correct type @@ -107,6 +108,9 @@ const recommended = { '@typescript-eslint/type-annotation-spacing': ['error', { after: true, before: false, + overrides: { + arrow: { after: true, before: true }, + }, }], 'block-spacing': 'off', 'brace-style': 'off', @@ -149,19 +153,19 @@ const recommended = { 'perfectionist/sort-union-types': ['error', { type: 'natural' }], }, -}; +}); /** * This config relates to code formatting and style in JavaScript and TypeScript * Strict alternative, better for projects in refactoring and/or production phases. - * @type {import('eslint').Linter.FlatConfig} + * @type {import('./index.d.ts').ConfigVariations} */ -const strict = { - ...recommended, +const strict = createVariations({ + ...recommended.error, rules: { - ...recommended.rules, + ...recommended.error.rules, }, -}; +}); const formatting = { recommended, strict }; export default formatting; diff --git a/configs/js/src/configs/index.d.ts b/configs/js/src/configs/index.d.ts index d758a4a..81b2760 100644 --- a/configs/js/src/configs/index.d.ts +++ b/configs/js/src/configs/index.d.ts @@ -1,41 +1,58 @@ import type { Linter } from 'eslint'; +interface ConfigVariations { + /** + * Enable all rules with `error` level. + */ + error: Linter.FlatConfig, + /** + * Disable all rules in this config. + */ + off: Linter.FlatConfig, + /** + * Enable all rules with `warn` level. + */ + warn: Linter.FlatConfig, +} + const configs: Readonly<{ core: Linter.FlatConfig, documentation: { - recommended: Linter.FlatConfig, - strict: Linter.FlatConfig, + recommended: ConfigVariations, + strict: ConfigVariations, }, environments: { browser: { - recommended: Linter.FlatConfig, - strict: Linter.FlatConfig, + recommended: ConfigVariations, + strict: ConfigVariations, }, node: { - commonjs: Linter.FlatConfig, - recommended: Linter.FlatConfig, - strict: Linter.FlatConfig, + commonjs: ConfigVariations, + recommended: ConfigVariations, + strict: ConfigVariations, }, }, formatting: { - recommended: Linter.FlatConfig, - strict: Linter.FlatConfig, + recommended: ConfigVariations, + strict: ConfigVariations, }, naming: { - recommended: Linter.FlatConfig, - strict: Linter.FlatConfig, + recommended: ConfigVariations, + strict: ConfigVariations, }, overrides: { - performance: Linter.FlatConfig, + 'inferrable-types': ConfigVariations, + performance: ConfigVariations, }, suggestions: { - recommended: Linter.FlatConfig, - strict: Linter.FlatConfig, + recommended: ConfigVariations, + strict: ConfigVariations, }, 'suggestions-typescript': { - recommended: Linter.FlatConfig, - strict: Linter.FlatConfig, + recommended: ConfigVariations, + strict: ConfigVariations, }, }>; export default configs; +export type { ConfigVariations }; diff --git a/configs/js/src/configs/naming.js b/configs/js/src/configs/naming.js index ffc9b59..aa44c90 100644 --- a/configs/js/src/configs/naming.js +++ b/configs/js/src/configs/naming.js @@ -1,30 +1,31 @@ +/* eslint-disable import/no-relative-parent-imports */ /* eslint-disable unicorn/no-useless-spread */ -// eslint-disable-next-line import/no-relative-parent-imports +import { createVariations } from '../lib/rule-variations.js'; import { jsFiles, tsFiles } from '../constants.js'; /** * This config suggest alternate ways of doing things in JavaScript and TypeScript * Recommended alternative, better for projects in prototyping phases. - * @type {import('eslint').Linter.FlatConfig} + * @type {import('./index.d.ts').ConfigVariations} */ -const recommended = { +const recommended = createVariations({ files: [...tsFiles, ...jsFiles], rules: { ...{}, // Plugin: eslint-plugin-unicorn 'unicorn/filename-case': ['error', { case: 'kebabCase' }], 'unicorn/prevent-abbreviations': 'error', }, -}; +}); /** * This config suggest alternate ways of doing things in JavaScript and TypeScript * Strict alternative, better for projects in refactoring and/or production phases. - * @type {import('eslint').Linter.FlatConfig} + * @type {import('./index.d.ts').ConfigVariations} */ -const strict = { - ...recommended, +const strict = createVariations({ + ...recommended.error, rules: { - ...recommended.rules, + ...recommended.error.rules, ...{}, // Plugin: @typescript-eslint/eslint-plugin // '@typescript-eslint/naming-convention': 'error', @@ -32,7 +33,7 @@ const strict = { 'unicorn/no-keyword-prefix': 'error', }, -}; +}); const suggestions = { recommended, strict }; export default suggestions; diff --git a/configs/js/src/configs/overrides.js b/configs/js/src/configs/overrides.js index bef8e93..22136d7 100644 --- a/configs/js/src/configs/overrides.js +++ b/configs/js/src/configs/overrides.js @@ -1,25 +1,27 @@ -// eslint-disable-next-line import/no-relative-parent-imports +/* eslint-disable import/no-relative-parent-imports */ +/* eslint-disable unicorn/no-useless-spread */ +import { createVariations } from '../lib/rule-variations.js'; import { jsFiles, tsFiles } from '../constants.js'; // TODO [>=1.0.0]: Create a separate config for performance related practices -/** @type {import('eslint').Linter.FlatConfig} */ -const performance = { +/** @type {import('./index.d.ts').ConfigVariations} */ +const performance = createVariations({ files: [...tsFiles, ...jsFiles], rules: { 'prefer-object-spread': 'off', 'prefer-spread': 'off', }, -}; +}); -/** @type {import('eslint').Linter.FlatConfig} */ -const inferrableTypes = { +/** @type {import('./index.d.ts').ConfigVariations} */ +const inferrableTypes = createVariations({ files: [...tsFiles], rules: { '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/no-inferrable-types': 'error', '@typescript-eslint/typedef': 'off', }, -}; +}); -const overrides = { inferrableTypes, performance }; +const overrides = { 'inferrable-types': inferrableTypes, performance }; export default overrides; diff --git a/configs/js/src/configs/problems.js b/configs/js/src/configs/problems.js index e1568b9..aa76791 100644 --- a/configs/js/src/configs/problems.js +++ b/configs/js/src/configs/problems.js @@ -1,13 +1,14 @@ +/* eslint-disable import/no-relative-parent-imports */ /* eslint-disable unicorn/no-useless-spread */ -// eslint-disable-next-line import/no-relative-parent-imports +import { createVariations } from '../lib/rule-variations.js'; import { jsFiles, tsFiles } from '../constants.js'; /** * This config relates to possible logic and syntax errors JavaScript and TypeScript * Recommended alternative, better for projects in prototyping phases. - * @type {import('eslint').Linter.FlatConfig} + * @type {import('./index.d.ts').ConfigVariations} */ -const recommended = { +const recommended = createVariations({ files: [...tsFiles, ...jsFiles], rules: { ...{}, // ESLint rules @@ -75,17 +76,17 @@ const recommended = { 'import/namespace': 'error', 'import/no-unresolved': 'error', }, -}; +}); /** * This config relates to possible logic and syntax errors JavaScript and TypeScript * Strict alternative, better for projects in refactoring and/or production phases. - * @type {import('eslint').Linter.FlatConfig} + * @type {import('./index.d.ts').ConfigVariations} */ -const strict = { - ...recommended, +const strict = createVariations({ + ...recommended.error, rules: { - ...recommended.rules, + ...recommended.error.rules, ...{}, // ESLint rules 'no-constant-binary-expression': 'error', @@ -102,7 +103,7 @@ const strict = { ...{}, // Plugin: eslint-plugin-import 'import/no-extraneous-dependencies': 'error', }, -}; +}); const problems = { recommended, strict }; export default problems; diff --git a/configs/js/src/configs/suggestions-typescript.js b/configs/js/src/configs/suggestions-typescript.js index 76cc646..c0766f5 100644 --- a/configs/js/src/configs/suggestions-typescript.js +++ b/configs/js/src/configs/suggestions-typescript.js @@ -1,10 +1,10 @@ +/* eslint-disable import/no-relative-parent-imports */ /* eslint-disable unicorn/no-useless-spread */ +import { createVariations } from '../lib/rule-variations.js'; +import { jsFiles, tsFiles } from '../constants.js'; -// eslint-disable-next-line import/no-relative-parent-imports -import { tsFiles } from '../constants.js'; - -/** @type {import('eslint').Linter.FlatConfig} */ -const recommended = { +/** @type {import('./index.d.ts').ConfigVariations} */ +const recommended = createVariations({ files: [...tsFiles], rules: { ...{}, // Plugin: @typescript-eslint/eslint-plugin @@ -17,18 +17,18 @@ const recommended = { 'jsdoc/require-property-type': 'off', 'jsdoc/require-returns-type': 'off', }, -}; +}); -/** @type {import('eslint').Linter.FlatConfig} */ -const strict = { - ...recommended, +/** @type {import('./index.d.ts').ConfigVariations} */ +const strict = createVariations({ + ...recommended.error, rules: { - ...recommended.rules, + ...recommended.error.rules, ...{}, // Plugin: @typescript-eslint/eslint-plugin '@typescript-eslint/explicit-member-accessibility': 'error', '@typescript-eslint/explicit-module-boundary-types': 'error', }, -}; +}); const typescript = { recommended, strict }; export default typescript; diff --git a/configs/js/src/configs/suggestions.js b/configs/js/src/configs/suggestions.js index 97eba0e..c3e89b4 100644 --- a/configs/js/src/configs/suggestions.js +++ b/configs/js/src/configs/suggestions.js @@ -1,13 +1,15 @@ +/* eslint-disable import/no-relative-parent-imports */ /* eslint-disable unicorn/no-useless-spread */ -// eslint-disable-next-line import/no-relative-parent-imports +import { createVariations } from '../lib/rule-variations.js'; import { jsFiles, tsFiles } from '../constants.js'; + /** * This config suggest alternate ways of doing things in JavaScript and TypeScript * Recommended alternative, better for projects in prototyping phases. - * @type {import('eslint').Linter.FlatConfig} + * @type {import('./index.d.ts').ConfigVariations} */ -const recommended = { +const recommended = createVariations({ files: [...tsFiles, ...jsFiles], rules: { 'camelcase': 'error', @@ -242,20 +244,20 @@ const recommended = { 'jsdoc/require-yields': 'error', 'jsdoc/require-yields-check': 'error', 'jsdoc/sort-tags': 'error', - 'jsdoc/valid-types': 'error', + // 'jsdoc/valid-types': 'error', This is already handled by Typescript type checking mostly }, -}; +}); /** * This config suggest alternate ways of doing things in JavaScript and TypeScript * Strict alternative, better for projects in refactoring and/or production phases. - * @type {import('eslint').Linter.FlatConfig} + * @type {import('./index.d.ts').ConfigVariations} */ -const strict = { - ...recommended, +const strict = createVariations({ + ...recommended.error, rules: { - ...recommended.rules, + ...recommended.error.rules, ...{}, // ESLint rules 'accessor-pairs': 'error', @@ -363,7 +365,6 @@ const strict = { '@typescript-eslint/parameter-properties': 'error', '@typescript-eslint/prefer-enum-initializers': 'error', '@typescript-eslint/prefer-readonly': 'error', - '@typescript-eslint/prefer-readonly-parameter-types': 'error', '@typescript-eslint/promise-function-async': 'error', '@typescript-eslint/require-array-sort-compare': 'error', '@typescript-eslint/require-await': 'error', @@ -428,7 +429,7 @@ const strict = { 'jsdoc/no-blank-blocks': 'error', }, -}; +}); const suggestions = { recommended, strict }; export default suggestions; diff --git a/configs/js/src/lib/rule-variations.js b/configs/js/src/lib/rule-variations.js new file mode 100644 index 0000000..59cac1f --- /dev/null +++ b/configs/js/src/lib/rule-variations.js @@ -0,0 +1,75 @@ + +/** + * @typedef {import('eslint').Linter.RuleEntry} RuleEntry + * @typedef {import('eslint').Linter.RuleLevel} RuleLevel + * @typedef {import('eslint').Linter.FlatConfig} FlatConfig + */ + +/** + * @param {Readonly} ruleEntry + * - The rule entry to be modified. + * @param {RuleLevel} level + * - The new level to be passed to the rule. + * @returns {RuleEntry} + */ +function changeLevel(ruleEntry, level) { + if (typeof level === 'number') { + /** @type {RuleLevel[]} */ + const levels = ['error', 'off', 'warn']; + level = levels[level]; + } + + if (Array.isArray(ruleEntry)) + return [level, ruleEntry[1]]; + + return level; + +} + +/** + * @param {Readonly<{[name: string]: RuleEntry}>} rules + * - The object to be iterated through. + * @param {([name, entry]: [string, RuleEntry]) => [string, RuleEntry]} handler + * - Function to run on every rule entry. + * @returns {{[name: string]: RuleEntry}} - The resulting object. + */ +function iterateRules(rules, handler) { + const entries = Object.entries(rules); + entries.map(entry => handler(entry)); + return Object.fromEntries(entries); +} + +/** + * @param {Readonly} config + * - The configuration object to create `error`, `warn`, and `off` variations. + * @returns {{error: FlatConfig, warn: FlatConfig, off: FlatConfig}} + */ +function createVariations(config) { + const configError = { + ...config, + rules: iterateRules( + config.rules ?? {}, + ([key, entry]) => [key, changeLevel(entry, 'error')], + ), + }; + + const configWarning = { + ...config, + rules: iterateRules( + config.rules ?? {}, + ([key, entry]) => [key, changeLevel(entry, 'warn')], + ), + }; + + const configDisabled = { + ...config, + rules: iterateRules( + config.rules ?? {}, + ([key, entry]) => [key, changeLevel(entry, 'off')], + ), + }; + + return { error: configError, off: configDisabled, warn: configWarning }; +} + +export { createVariations, iterateRules }; diff --git a/configs/js/src/presets/recommended.js b/configs/js/src/presets/recommended.js index 1d7c211..ce45193 100644 --- a/configs/js/src/presets/recommended.js +++ b/configs/js/src/presets/recommended.js @@ -4,10 +4,10 @@ import configs from '../configs/index.js'; /** @type {import('eslint').Linter.FlatConfig[]} */ const recommended = [ configs.core, - configs['suggestions-typescript'].recommended, - configs.suggestions.recommended, - configs.formatting.recommended, - configs.naming.recommended, - configs.documentation.recommended, + configs['suggestions-typescript'].recommended.error, + configs.suggestions.recommended.error, + configs.formatting.recommended.error, + configs.naming.recommended.error, + configs.documentation.recommended.error, ]; export default recommended; diff --git a/configs/js/src/presets/strict.js b/configs/js/src/presets/strict.js index 7398715..891a1e2 100644 --- a/configs/js/src/presets/strict.js +++ b/configs/js/src/presets/strict.js @@ -4,10 +4,10 @@ import configs from '../configs/index.js'; /** @type {import('eslint').Linter.FlatConfig[]} */ const strict = [ configs.core, - configs['suggestions-typescript'].strict, - configs.suggestions.strict, - configs.formatting.strict, - configs.naming.strict, - configs.documentation.recommended, + configs['suggestions-typescript'].strict.error, + configs.suggestions.strict.error, + configs.formatting.strict.error, + configs.naming.strict.error, + configs.documentation.recommended.error, ]; export default strict;