From 5fa7c2c2caebbdbc2fb295f72bb20c3a078cd913 Mon Sep 17 00:00:00 2001 From: Rebecca Stevens Date: Wed, 20 Mar 2024 20:48:37 +1300 Subject: [PATCH] feat: add support for flat configs (#789) * feat: add support for flat configs * ci: fix release replacements * chore(release): 6.2.0-next.1 [skip ci] # [6.2.0-next.1](https://github.com/eslint-functional/eslint-plugin-functional/compare/v6.1.1...v6.2.0-next.1) (2024-03-13) ### Features * add support for flat configs ([901d52a](https://github.com/eslint-functional/eslint-plugin-functional/commit/901d52a889c4ecf5fa78474b1f7361edb6b9e60f)) * fix: build types * fix: improve types * chore(release): 6.2.0-next.2 [skip ci] # [6.2.0-next.2](https://github.com/eslint-functional/eslint-plugin-functional/compare/v6.2.0-next.1...v6.2.0-next.2) (2024-03-14) ### Bug Fixes * build types ([054a862](https://github.com/eslint-functional/eslint-plugin-functional/commit/054a86299e5b6c97a78f72a7711938273d56ef82)) * improve types ([2340489](https://github.com/eslint-functional/eslint-plugin-functional/commit/23404897399caa4f70ed0d5eb24b9ab300b65600)) * feat: move flat configs to new "flat" subpackage * ci: fix release * chore(release): 6.2.0-next.3 [skip ci] # [6.2.0-next.3](https://github.com/eslint-functional/eslint-plugin-functional/compare/v6.2.0-next.2...v6.2.0-next.3) (2024-03-14) ### Features * move flat configs to new "flat" subpackage ([1d3533f](https://github.com/eslint-functional/eslint-plugin-functional/commit/1d3533fca47673ca1dc9e50dbe5a00ad8adb5362)) --------- Co-authored-by: semantic-release-bot --- .eslintrc.json | 1 + .releaserc.yml | 28 ++- CHANGELOG.md | 22 ++ GETTING_STARTED.md | 11 + README.md | 2 +- eslint-doc-generator.config.ts | 20 +- knip.jsonc | 7 +- package.json | 21 +- rollup.config.ts | 40 +++- src/{index.ts => classic.ts} | 44 ++-- src/configs/all.ts | 56 ++--- src/configs/currying.ts | 25 ++- src/configs/deprecated.ts | 11 - src/configs/disable-type-checked.ts | 17 +- .../external-typescript-recommended.ts | 23 +- src/configs/external-vanilla-recommended.ts | 16 +- src/configs/lite.ts | 55 +++-- src/configs/no-exceptions.ts | 27 ++- src/configs/no-mutations.ts | 31 +-- src/configs/no-other-paradigms.ts | 29 +-- src/configs/no-statements.ts | 31 +-- src/configs/off.ts | 26 +-- src/configs/recommended.ts | 200 ++++++++++-------- src/configs/strict.ts | 32 +-- src/configs/stylistic.ts | 29 +-- src/flat.ts | 60 ++++++ src/rules/functional-parameters.ts | 12 +- src/rules/immutable-data.ts | 16 +- src/rules/no-classes.ts | 12 +- src/rules/no-conditional-statements.ts | 12 +- src/rules/no-expression-statements.ts | 16 +- src/rules/no-let.ts | 12 +- src/rules/no-loop-statements.ts | 12 +- src/rules/no-mixed-types.ts | 12 +- src/rules/no-promise-reject.ts | 12 +- src/rules/no-return-void.ts | 12 +- src/rules/no-this-expressions.ts | 12 +- src/rules/no-throw-statements.ts | 12 +- src/rules/no-try-statements.ts | 12 +- src/rules/prefer-immutable-types.ts | 17 +- src/rules/prefer-property-signatures.ts | 12 +- src/rules/prefer-readonly-type.ts | 12 +- src/rules/prefer-tacit.ts | 14 +- src/rules/readonly-type.ts | 14 +- src/rules/type-declaration-immutability.ts | 18 +- src/utils/constants.ts | 2 + src/utils/merge-configs.ts | 35 --- src/utils/misc.ts | 7 +- src/utils/rule.ts | 53 +++-- tests/common/ignore-options.test.ts | 6 +- tests/configs.test.ts | 34 +-- tests/helpers/testers.ts | 6 +- tests/helpers/util.ts | 27 ++- tests/index.test.ts | 44 +++- tests/mergers/configs.test.ts | 32 --- tsconfig.base.json | 3 +- tsconfig.build.json | 2 +- 57 files changed, 821 insertions(+), 545 deletions(-) rename src/{index.ts => classic.ts} (51%) delete mode 100644 src/configs/deprecated.ts create mode 100644 src/flat.ts create mode 100644 src/utils/constants.ts delete mode 100644 src/utils/merge-configs.ts delete mode 100644 tests/mergers/configs.test.ts diff --git a/.eslintrc.json b/.eslintrc.json index 606af606c..b51e8ac66 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -119,6 +119,7 @@ "@typescript-eslint/no-unused-vars": "off", "@typescript-eslint/prefer-function-type": "off", "@typescript-eslint/consistent-generic-constructors": "off", + "import/extensions": "off", "import/no-unresolved": "off", "init-declarations": "off", "jsdoc/require-jsdoc": "off", diff --git a/.releaserc.yml b/.releaserc.yml index 5a3ec16dc..0380b0add 100644 --- a/.releaserc.yml +++ b/.releaserc.yml @@ -51,11 +51,35 @@ plugins: from: "\\$\\{__VERSION__\\}" to: "${nextRelease.version}" results: - - file: "./lib/index.js" + - file: "./lib/classic.cjs" hasChanged: true numMatches: 1 numReplacements: 1 - - file: "./lib/index.mjs" + - file: "./lib/classic.d.cts" + hasChanged: false + numMatches: 0 + numReplacements: 0 + - file: "./lib/classic.d.mts" + hasChanged: false + numMatches: 0 + numReplacements: 0 + - file: "./lib/classic.mjs" + hasChanged: true + numMatches: 1 + numReplacements: 1 + - file: "./lib/flat.cjs" + hasChanged: true + numMatches: 1 + numReplacements: 1 + - file: "./lib/flat.d.cts" + hasChanged: false + numMatches: 0 + numReplacements: 0 + - file: "./lib/flat.d.mts" + hasChanged: false + numMatches: 0 + numReplacements: 0 + - file: "./lib/flat.mjs" hasChanged: true numMatches: 1 numReplacements: 1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ad28733a..baa7130e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,28 @@ # Changelog All notable changes to this project will be documented in this file. Dates are displayed in UTC. +# [6.2.0-next.3](https://github.com/eslint-functional/eslint-plugin-functional/compare/v6.2.0-next.2...v6.2.0-next.3) (2024-03-14) + + +### Features + +* move flat configs to new "flat" subpackage ([1d3533f](https://github.com/eslint-functional/eslint-plugin-functional/commit/1d3533fca47673ca1dc9e50dbe5a00ad8adb5362)) + +# [6.2.0-next.2](https://github.com/eslint-functional/eslint-plugin-functional/compare/v6.2.0-next.1...v6.2.0-next.2) (2024-03-14) + + +### Bug Fixes + +* build types ([054a862](https://github.com/eslint-functional/eslint-plugin-functional/commit/054a86299e5b6c97a78f72a7711938273d56ef82)) +* improve types ([2340489](https://github.com/eslint-functional/eslint-plugin-functional/commit/23404897399caa4f70ed0d5eb24b9ab300b65600)) + +# [6.2.0-next.1](https://github.com/eslint-functional/eslint-plugin-functional/compare/v6.1.1...v6.2.0-next.1) (2024-03-13) + + +### Features + +* add support for flat configs ([901d52a](https://github.com/eslint-functional/eslint-plugin-functional/commit/901d52a889c4ecf5fa78474b1f7361edb6b9e60f)) + ## [6.1.1](https://github.com/eslint-functional/eslint-plugin-functional/compare/v6.1.0...v6.1.1) (2024-03-11) diff --git a/GETTING_STARTED.md b/GETTING_STARTED.md index afe80fdba..44f7198a6 100644 --- a/GETTING_STARTED.md +++ b/GETTING_STARTED.md @@ -30,6 +30,17 @@ pnpm add -D eslint @typescript-eslint/parser eslint-plugin-functional ## Usage +### Flat Config + +If using the new [flat config](https://eslint.org/docs/latest/use/configure/configuration-files-new), +import from `eslint-plugin-functional/flat`. + +```ts +import functional from "eslint-plugin-functional/flat"; +``` + +### Classic Config + Add `functional` to the plugins section of your `.eslintrc` configuration file. Then configure the rules you want to use under the rules section. ```jsonc diff --git a/README.md b/README.md index 7b983576f..6cd12ff70 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Tidelift is working with the maintainers of `eslint-plugin-functional` and a gro ## Rulesets -The following rulesets are made available by this plugin: +The following rulesets are made available by this plugin. Presets: diff --git a/eslint-doc-generator.config.ts b/eslint-doc-generator.config.ts index acf0e4900..9a2487e52 100644 --- a/eslint-doc-generator.config.ts +++ b/eslint-doc-generator.config.ts @@ -3,7 +3,25 @@ import { format } from "prettier"; export default { configEmoji: [["lite", "☑️"]], - ignoreConfig: ["all", "off", "disable-type-checked"], + ignoreConfig: [ + "all", + "off", + "disable-type-checked", + "flat/all", + "flat/currying", + "flat/disable-type-checked", + "flat/external-typescript-recommended", + "flat/external-vanilla-recommended", + "flat/lite", + "flat/no-exceptions", + "flat/no-mutations", + "flat/no-other-paradigms", + "flat/no-statements", + "flat/off", + "flat/recommended", + "flat/strict", + "flat/stylistic", + ], ruleDocSectionInclude: ["Rule Details"], ruleListSplit: "meta.docs.category", postprocess: (doc) => format(doc, { parser: "markdown" }), diff --git a/knip.jsonc b/knip.jsonc index 1df103e8f..f56a9a26a 100644 --- a/knip.jsonc +++ b/knip.jsonc @@ -1,6 +1,11 @@ { "$schema": "node_modules/knip/schema-jsonc.json", - "entry": ["src/index.ts!", "tests/**/*.test.ts", "cz-adapter/index.js"], + "entry": [ + "src/flat.ts!", + "src/classic.ts!", + "tests/**/*.test.ts", + "cz-adapter/index.js", + ], "project": ["src/**/*.ts!", "tests/**/*.ts", "cz-adapter/**/*.{js,ts}"], "ignore": ["tests/fixture/file.ts"], "ignoreDependencies": ["@types/eslint", "@vitest/coverage-istanbul"], diff --git a/package.json b/package.json index 4c803cda1..4342116a0 100644 --- a/package.json +++ b/package.json @@ -34,10 +34,25 @@ "Jonas Kello" ], "exports": { - "import": "./lib/index.mjs", - "require": "./lib/index.cjs" + ".": { + "types": { + "import": "./lib/classic.d.mts", + "require": "./lib/classic.d.cts" + }, + "import": "./lib/classic.mjs", + "require": "./lib/classic.cjs" + }, + "./flat": { + "types": { + "import": "./lib/flat.d.mts", + "require": "./lib/flat.d.cts" + }, + "import": "./lib/flat.mjs", + "require": "./lib/flat.cjs" + } }, - "main": "lib/index.cjs", + "main": "lib/classic.cjs", + "types": "lib/classic.d.cts", "files": [ "lib/", "package.json", diff --git a/rollup.config.ts b/rollup.config.ts index 2bfb8f232..5c85096c7 100644 --- a/rollup.config.ts +++ b/rollup.config.ts @@ -12,17 +12,17 @@ const treeshake = { unknownGlobalSideEffects: false, } satisfies RollupOptions["treeshake"]; -const library = { - input: "src/index.ts", +const classic = { + input: "src/classic.ts", output: [ { - file: pkg.exports.import, + file: pkg.exports["."].import, format: "esm", sourcemap: false, }, { - file: pkg.exports.require, + file: pkg.exports["."].require, format: "cjs", sourcemap: false, }, @@ -42,4 +42,34 @@ const library = { treeshake, } satisfies RollupOptions; -export default [library]; +const flat = { + input: "src/flat.ts", + + output: [ + { + file: pkg.exports["./flat"].import, + format: "esm", + sourcemap: false, + }, + { + file: pkg.exports["./flat"].require, + format: "cjs", + sourcemap: false, + }, + ], + + plugins: [ + rollupPluginAutoExternal(), + rollupPluginTs({ + transpileOnly: true, + tsconfig: "tsconfig.build.json", + }), + rollupPluginDeassert({ + include: ["**/*.{js,ts}"], + }), + ], + + treeshake, +} satisfies RollupOptions; + +export default [classic, flat]; diff --git a/src/index.ts b/src/classic.ts similarity index 51% rename from src/index.ts rename to src/classic.ts index d98257d8a..21ca483ab 100644 --- a/src/index.ts +++ b/src/classic.ts @@ -15,25 +15,33 @@ import recommended from "#eslint-plugin-functional/configs/recommended"; import strict from "#eslint-plugin-functional/configs/strict"; import stylistic from "#eslint-plugin-functional/configs/stylistic"; import { rules } from "#eslint-plugin-functional/rules"; +import { ruleNameScope } from "#eslint-plugin-functional/utils/misc"; -const config: Linter.Plugin = { +export default { rules, configs: { - all, - lite, - recommended, - strict, - off, - "disable-type-checked": disableTypeChecked, - "external-vanilla-recommended": externalVanillaRecommended, - "external-typescript-recommended": externalTypeScriptRecommended, - currying, - "no-exceptions": noExceptions, - "no-mutations": noMutations, - "no-other-paradigms": noOtherParadigms, - "no-statements": noStatements, - stylistic, + all: { plugins: [ruleNameScope], rules: all }, + lite: { plugins: [ruleNameScope], rules: lite }, + recommended: { plugins: [ruleNameScope], rules: recommended }, + strict: { plugins: [ruleNameScope], rules: strict }, + off: { plugins: [ruleNameScope], rules: off }, + "disable-type-checked": { + plugins: [ruleNameScope], + rules: disableTypeChecked, + }, + "external-vanilla-recommended": { + plugins: [ruleNameScope], + rules: externalVanillaRecommended, + }, + "external-typescript-recommended": { + plugins: [ruleNameScope], + rules: externalTypeScriptRecommended, + }, + currying: { plugins: [ruleNameScope], rules: currying }, + "no-exceptions": { plugins: [ruleNameScope], rules: noExceptions }, + "no-mutations": { plugins: [ruleNameScope], rules: noMutations }, + "no-other-paradigms": { plugins: [ruleNameScope], rules: noOtherParadigms }, + "no-statements": { plugins: [ruleNameScope], rules: noStatements }, + stylistic: { plugins: [ruleNameScope], rules: stylistic }, }, -}; - -export default config; +} as Linter.Plugin; diff --git a/src/configs/all.ts b/src/configs/all.ts index 5506641dd..362f3825a 100644 --- a/src/configs/all.ts +++ b/src/configs/all.ts @@ -1,45 +1,15 @@ -import { type Linter } from "@typescript-eslint/utils/ts-eslint"; +import { type FlatConfig } from "@typescript-eslint/utils/ts-eslint"; -import * as functionalParameters from "#eslint-plugin-functional/rules/functional-parameters"; -import * as immutableData from "#eslint-plugin-functional/rules/immutable-data"; -import * as noClasses from "#eslint-plugin-functional/rules/no-classes"; -import * as noConditionalStatements from "#eslint-plugin-functional/rules/no-conditional-statements"; -import * as noExpressionStatements from "#eslint-plugin-functional/rules/no-expression-statements"; -import * as noLet from "#eslint-plugin-functional/rules/no-let"; -import * as noLoopStatements from "#eslint-plugin-functional/rules/no-loop-statements"; -import * as noMixedTypes from "#eslint-plugin-functional/rules/no-mixed-types"; -import * as noPromiseReject from "#eslint-plugin-functional/rules/no-promise-reject"; -import * as noReturnVoid from "#eslint-plugin-functional/rules/no-return-void"; -import * as noThisExpressions from "#eslint-plugin-functional/rules/no-this-expressions"; -import * as noThrowStatements from "#eslint-plugin-functional/rules/no-throw-statements"; -import * as noTryStatements from "#eslint-plugin-functional/rules/no-try-statements"; -import * as preferImmutableTypes from "#eslint-plugin-functional/rules/prefer-immutable-types"; -import * as preferPropertySignatures from "#eslint-plugin-functional/rules/prefer-property-signatures"; -import * as preferTacit from "#eslint-plugin-functional/rules/prefer-tacit"; -import * as readonlyType from "#eslint-plugin-functional/rules/readonly-type"; -import * as typeDeclarationImmutability from "#eslint-plugin-functional/rules/type-declaration-immutability"; +import { rules } from "#eslint-plugin-functional/rules"; +import { ruleNameScope } from "#eslint-plugin-functional/utils/misc"; -const config: Linter.Config = { - rules: { - [`functional/${functionalParameters.name}`]: "error", - [`functional/${immutableData.name}`]: "error", - [`functional/${noClasses.name}`]: "error", - [`functional/${noConditionalStatements.name}`]: "error", - [`functional/${noExpressionStatements.name}`]: "error", - [`functional/${noLet.name}`]: "error", - [`functional/${noLoopStatements.name}`]: "error", - [`functional/${noMixedTypes.name}`]: "error", - [`functional/${noPromiseReject.name}`]: "error", - [`functional/${noReturnVoid.name}`]: "error", - [`functional/${noThisExpressions.name}`]: "error", - [`functional/${noThrowStatements.name}`]: "error", - [`functional/${noTryStatements.name}`]: "error", - [`functional/${preferImmutableTypes.name}`]: "error", - [`functional/${preferPropertySignatures.name}`]: "error", - [`functional/${preferTacit.name}`]: "warn", - [`functional/${readonlyType.name}`]: "error", - [`functional/${typeDeclarationImmutability.name}`]: "error", - }, -}; - -export default config; +export default { + ...Object.fromEntries( + Object.entries(rules) + .filter(([, rule]) => rule.meta.deprecated !== true) + .map(([name, rule]) => [ + `${ruleNameScope}/${name}`, + rule.meta.docs.recommendedSeverity, + ]), + ), +} satisfies FlatConfig.Config["rules"]; diff --git a/src/configs/currying.ts b/src/configs/currying.ts index 8c94ea0e2..c6e571f40 100644 --- a/src/configs/currying.ts +++ b/src/configs/currying.ts @@ -1,11 +1,18 @@ -import { type Linter } from "@typescript-eslint/utils/ts-eslint"; +import { type FlatConfig } from "@typescript-eslint/utils/ts-eslint"; -import * as functionalParameters from "#eslint-plugin-functional/rules/functional-parameters"; +import { rules } from "#eslint-plugin-functional/rules"; +import { ruleNameScope } from "#eslint-plugin-functional/utils/misc"; -const config: Linter.Config = { - rules: { - [`functional/${functionalParameters.name}`]: "error", - }, -}; - -export default config; +export default Object.fromEntries( + Object.entries(rules) + .filter( + ([, rule]) => + rule.meta.deprecated !== true && + rule.meta.docs.category === "Currying" && + rule.meta.docs.recommended !== false, + ) + .map(([name, rule]) => [ + `${ruleNameScope}/${name}`, + rule.meta.docs.recommendedSeverity, + ]), +) satisfies FlatConfig.Config["rules"]; diff --git a/src/configs/deprecated.ts b/src/configs/deprecated.ts deleted file mode 100644 index f5c67e310..000000000 --- a/src/configs/deprecated.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { type Linter } from "@typescript-eslint/utils/ts-eslint"; - -import * as preferReadonlyType from "#eslint-plugin-functional/rules/prefer-readonly-type"; - -const config: Linter.Config = { - rules: { - [`functional/${preferReadonlyType.name}`]: "warn", - }, -}; - -export default config; diff --git a/src/configs/disable-type-checked.ts b/src/configs/disable-type-checked.ts index 76b7fdc5e..76827e53a 100644 --- a/src/configs/disable-type-checked.ts +++ b/src/configs/disable-type-checked.ts @@ -1,13 +1,10 @@ -import { type Linter } from "@typescript-eslint/utils/ts-eslint"; +import { type FlatConfig } from "@typescript-eslint/utils/ts-eslint"; import { rules } from "#eslint-plugin-functional/rules"; +import { ruleNameScope } from "#eslint-plugin-functional/utils/misc"; -const config: Linter.Config = { - rules: Object.fromEntries( - Object.entries(rules) - .filter(([, rule]) => rule.meta.docs?.requiresTypeChecking === true) - .map(([name]) => [`functional/${name}`, "off"]), - ), -}; - -export default config; +export default Object.fromEntries( + Object.entries(rules) + .filter(([, rule]) => rule.meta.docs?.requiresTypeChecking === true) + .map(([name]) => [`${ruleNameScope}/${name}`, "off"]), +) satisfies FlatConfig.Config["rules"]; diff --git a/src/configs/external-typescript-recommended.ts b/src/configs/external-typescript-recommended.ts index c32cbc6c2..f0c089f01 100644 --- a/src/configs/external-typescript-recommended.ts +++ b/src/configs/external-typescript-recommended.ts @@ -1,18 +1,13 @@ -import { type Linter } from "@typescript-eslint/utils/ts-eslint"; +import { type FlatConfig } from "@typescript-eslint/utils/ts-eslint"; import externalVanillaRecommended from "#eslint-plugin-functional/configs/external-vanilla-recommended"; -import { mergeConfigs } from "#eslint-plugin-functional/utils/merge-configs"; -const tsConfig: Linter.Config = { - rules: { - "@typescript-eslint/prefer-readonly": "error", - "@typescript-eslint/switch-exhaustiveness-check": "error", - }, -}; +const tsConfig = { + "@typescript-eslint/prefer-readonly": "error", + "@typescript-eslint/switch-exhaustiveness-check": "error", +} satisfies FlatConfig.Config["rules"]; -const fullConfig: Linter.Config = mergeConfigs( - externalVanillaRecommended, - tsConfig, -); - -export default fullConfig; +export default { + ...externalVanillaRecommended, + ...tsConfig, +} satisfies FlatConfig.Config["rules"]; diff --git a/src/configs/external-vanilla-recommended.ts b/src/configs/external-vanilla-recommended.ts index 1009e3517..3f97fe43d 100644 --- a/src/configs/external-vanilla-recommended.ts +++ b/src/configs/external-vanilla-recommended.ts @@ -1,11 +1,7 @@ -import { type Linter } from "@typescript-eslint/utils/ts-eslint"; +import { type FlatConfig } from "@typescript-eslint/utils/ts-eslint"; -const config: Linter.Config = { - rules: { - "prefer-const": "error", - "no-param-reassign": "error", - "no-var": "error", - }, -}; - -export default config; +export default { + "prefer-const": "error", + "no-param-reassign": "error", + "no-var": "error", +} satisfies FlatConfig.Config["rules"]; diff --git a/src/configs/lite.ts b/src/configs/lite.ts index 4f1c03b04..79bb9a28d 100644 --- a/src/configs/lite.ts +++ b/src/configs/lite.ts @@ -1,41 +1,36 @@ -import { type Linter } from "@typescript-eslint/utils/ts-eslint"; +import { type FlatConfig } from "@typescript-eslint/utils/ts-eslint"; import * as functionalParameters from "#eslint-plugin-functional/rules/functional-parameters"; import * as immutableData from "#eslint-plugin-functional/rules/immutable-data"; import * as noConditionalStatements from "#eslint-plugin-functional/rules/no-conditional-statements"; import * as noExpressionStatements from "#eslint-plugin-functional/rules/no-expression-statements"; import * as preferImmutableTypes from "#eslint-plugin-functional/rules/prefer-immutable-types"; -import { mergeConfigs } from "#eslint-plugin-functional/utils/merge-configs"; import recommended from "./recommended"; -const overrides: Linter.Config = { - rules: { - [`functional/${functionalParameters.name}`]: [ - "error", - { - enforceParameterCount: false, +const overrides = { + [functionalParameters.fullName]: [ + "error", + { + enforceParameterCount: false, + }, + ], + [immutableData.fullName]: ["error", { ignoreClasses: "fieldsOnly" }], + [noConditionalStatements.fullName]: "off", + [noExpressionStatements.fullName]: "off", + [preferImmutableTypes.fullName]: [ + "error", + { + enforcement: "None", + ignoreInferredTypes: true, + parameters: { + enforcement: "ReadonlyShallow", }, - ], - [`functional/${immutableData.name}`]: [ - "error", - { ignoreClasses: "fieldsOnly" }, - ], - [`functional/${noConditionalStatements.name}`]: "off", - [`functional/${noExpressionStatements.name}`]: "off", - [`functional/${preferImmutableTypes.name}`]: [ - "error", - { - enforcement: "None", - ignoreInferredTypes: true, - parameters: { - enforcement: "ReadonlyShallow", - }, - }, - ], - }, -}; + }, + ], +} satisfies FlatConfig.Config["rules"]; -const config: Linter.Config = mergeConfigs(recommended, overrides); - -export default config; +export default { + ...recommended, + ...overrides, +}; diff --git a/src/configs/no-exceptions.ts b/src/configs/no-exceptions.ts index f654db375..d067a1451 100644 --- a/src/configs/no-exceptions.ts +++ b/src/configs/no-exceptions.ts @@ -1,13 +1,18 @@ -import { type Linter } from "@typescript-eslint/utils/ts-eslint"; +import { type FlatConfig } from "@typescript-eslint/utils/ts-eslint"; -import * as noThrowStatements from "#eslint-plugin-functional/rules/no-throw-statements"; -import * as noTryStatements from "#eslint-plugin-functional/rules/no-try-statements"; +import { rules } from "#eslint-plugin-functional/rules"; +import { ruleNameScope } from "#eslint-plugin-functional/utils/misc"; -const config: Linter.Config = { - rules: { - [`functional/${noThrowStatements.name}`]: "error", - [`functional/${noTryStatements.name}`]: "error", - }, -}; - -export default config; +export default Object.fromEntries( + Object.entries(rules) + .filter( + ([, rule]) => + rule.meta.deprecated !== true && + rule.meta.docs.category === "No Exceptions" && + rule.meta.docs.recommended !== false, + ) + .map(([name, rule]) => [ + `${ruleNameScope}/${name}`, + rule.meta.docs.recommendedSeverity, + ]), +) satisfies FlatConfig.Config["rules"]; diff --git a/src/configs/no-mutations.ts b/src/configs/no-mutations.ts index 6c77c580b..6b7aa60b0 100644 --- a/src/configs/no-mutations.ts +++ b/src/configs/no-mutations.ts @@ -1,17 +1,18 @@ -import { type Linter } from "@typescript-eslint/utils/ts-eslint"; +import { type FlatConfig } from "@typescript-eslint/utils/ts-eslint"; -import * as immutableData from "#eslint-plugin-functional/rules/immutable-data"; -import * as noLet from "#eslint-plugin-functional/rules/no-let"; -import * as preferImmutableTypes from "#eslint-plugin-functional/rules/prefer-immutable-types"; -import * as typeDeclarationImmutability from "#eslint-plugin-functional/rules/type-declaration-immutability"; +import { rules } from "#eslint-plugin-functional/rules"; +import { ruleNameScope } from "#eslint-plugin-functional/utils/misc"; -const config: Linter.Config = { - rules: { - [`functional/${immutableData.name}`]: "error", - [`functional/${noLet.name}`]: "error", - [`functional/${preferImmutableTypes.name}`]: "error", - [`functional/${typeDeclarationImmutability.name}`]: "error", - }, -}; - -export default config; +export default Object.fromEntries( + Object.entries(rules) + .filter( + ([, rule]) => + rule.meta.deprecated !== true && + rule.meta.docs.category === "No Mutations" && + rule.meta.docs.recommended !== false, + ) + .map(([name, rule]) => [ + `${ruleNameScope}/${name}`, + rule.meta.docs.recommendedSeverity, + ]), +) satisfies FlatConfig.Config["rules"]; diff --git a/src/configs/no-other-paradigms.ts b/src/configs/no-other-paradigms.ts index 72a15c5d2..f06274300 100644 --- a/src/configs/no-other-paradigms.ts +++ b/src/configs/no-other-paradigms.ts @@ -1,15 +1,18 @@ -import { type Linter } from "@typescript-eslint/utils/ts-eslint"; +import { type FlatConfig } from "@typescript-eslint/utils/ts-eslint"; -import * as noClasses from "#eslint-plugin-functional/rules/no-classes"; -import * as noMixedTypes from "#eslint-plugin-functional/rules/no-mixed-types"; -import * as noThisExpressions from "#eslint-plugin-functional/rules/no-this-expressions"; +import { rules } from "#eslint-plugin-functional/rules"; +import { ruleNameScope } from "#eslint-plugin-functional/utils/misc"; -const config: Linter.Config = { - rules: { - [`functional/${noClasses.name}`]: "error", - [`functional/${noMixedTypes.name}`]: "error", - [`functional/${noThisExpressions.name}`]: "error", - }, -}; - -export default config; +export default Object.fromEntries( + Object.entries(rules) + .filter( + ([, rule]) => + rule.meta.deprecated !== true && + rule.meta.docs.category === "No Other Paradigms" && + rule.meta.docs.recommended !== false, + ) + .map(([name, rule]) => [ + `${ruleNameScope}/${name}`, + rule.meta.docs.recommendedSeverity, + ]), +) satisfies FlatConfig.Config["rules"]; diff --git a/src/configs/no-statements.ts b/src/configs/no-statements.ts index c8e5a02d8..49bda4097 100644 --- a/src/configs/no-statements.ts +++ b/src/configs/no-statements.ts @@ -1,17 +1,18 @@ -import { type Linter } from "@typescript-eslint/utils/ts-eslint"; +import { type FlatConfig } from "@typescript-eslint/utils/ts-eslint"; -import * as noConditionalStatements from "#eslint-plugin-functional/rules/no-conditional-statements"; -import * as noExpressionStatements from "#eslint-plugin-functional/rules/no-expression-statements"; -import * as noLoopStatements from "#eslint-plugin-functional/rules/no-loop-statements"; -import * as noReturnVoid from "#eslint-plugin-functional/rules/no-return-void"; +import { rules } from "#eslint-plugin-functional/rules"; +import { ruleNameScope } from "#eslint-plugin-functional/utils/misc"; -const config: Linter.Config = { - rules: { - [`functional/${noConditionalStatements.name}`]: "error", - [`functional/${noExpressionStatements.name}`]: "error", - [`functional/${noLoopStatements.name}`]: "error", - [`functional/${noReturnVoid.name}`]: "error", - }, -}; - -export default config; +export default Object.fromEntries( + Object.entries(rules) + .filter( + ([, rule]) => + rule.meta.deprecated !== true && + rule.meta.docs.category === "No Statements" && + rule.meta.docs.recommended !== false, + ) + .map(([name, rule]) => [ + `${ruleNameScope}/${name}`, + rule.meta.docs.recommendedSeverity, + ]), +) satisfies FlatConfig.Config["rules"]; diff --git a/src/configs/off.ts b/src/configs/off.ts index 5703b3705..c5fbfc094 100644 --- a/src/configs/off.ts +++ b/src/configs/off.ts @@ -1,22 +1,8 @@ -import { type Linter } from "@typescript-eslint/utils/ts-eslint"; +import { type FlatConfig } from "@typescript-eslint/utils/ts-eslint"; -import all from "./all"; -import deprecated from "./deprecated"; +import { rules } from "#eslint-plugin-functional/rules"; +import { ruleNameScope } from "#eslint-plugin-functional/utils/misc"; -/** - * Turn the given rules off. - */ -function turnRulesOff(rules: string[]): NonNullable { - return Object.fromEntries(rules.map((name) => [name, "off"])); -} - -const allRulesNames = new Set([ - ...Object.keys(all.rules ?? {}), - ...Object.keys(deprecated.rules ?? {}), -]); - -const config: Linter.Config = { - rules: turnRulesOff([...allRulesNames]), -}; - -export default config; +export default Object.fromEntries( + Object.entries(rules).map(([name]) => [`${ruleNameScope}/${name}`, "off"]), +) satisfies FlatConfig.Config["rules"]; diff --git a/src/configs/recommended.ts b/src/configs/recommended.ts index 7704be1ee..50b842f37 100644 --- a/src/configs/recommended.ts +++ b/src/configs/recommended.ts @@ -1,6 +1,7 @@ -import { type Linter } from "@typescript-eslint/utils/ts-eslint"; +import { type FlatConfig } from "@typescript-eslint/utils/ts-eslint"; import { Immutability } from "is-immutable-type"; +import { rules } from "#eslint-plugin-functional/rules"; import * as functionalParameters from "#eslint-plugin-functional/rules/functional-parameters"; import * as noConditionalStatements from "#eslint-plugin-functional/rules/no-conditional-statements"; import * as noLet from "#eslint-plugin-functional/rules/no-let"; @@ -10,102 +11,113 @@ import * as noTryStatements from "#eslint-plugin-functional/rules/no-try-stateme import * as preferImmutableTypes from "#eslint-plugin-functional/rules/prefer-immutable-types"; import * as typeDeclarationImmutability from "#eslint-plugin-functional/rules/type-declaration-immutability"; import { RuleEnforcementComparator } from "#eslint-plugin-functional/rules/type-declaration-immutability"; -import { mergeConfigs } from "#eslint-plugin-functional/utils/merge-configs"; +import { ruleNameScope } from "#eslint-plugin-functional/utils/misc"; -import strict from "./strict"; +const recommended = Object.fromEntries( + Object.entries(rules) + .filter( + ([, rule]) => + rule.meta.deprecated !== true && + rule.meta.docs.recommended === "recommended" && + rule.meta.docs.category !== "Stylistic", + ) + .map(([name, rule]) => [ + `${ruleNameScope}/${name}`, + rule.meta.docs.recommendedSeverity, + ]), +) satisfies FlatConfig.Config["rules"]; -const overrides: Linter.Config = { - rules: { - [`functional/${functionalParameters.name}`]: [ - "error", - { - enforceParameterCount: { - ignoreLambdaExpression: true, - ignoreIIFE: true, - ignoreGettersAndSetters: true, - }, - }, - ], - [`functional/${noConditionalStatements.name}`]: [ - "error", - { - allowReturningBranches: true, - }, - ], - [`functional/${noLet.name}`]: [ - "error", - { - allowInForLoopInit: true, +const overrides = { + [functionalParameters.fullName]: [ + "error", + { + enforceParameterCount: { + ignoreLambdaExpression: true, + ignoreIIFE: true, + ignoreGettersAndSetters: true, }, - ], - [`functional/${noThisExpressions.name}`]: "off", - [`functional/${noThrowStatements.name}`]: [ - "error", - { - allowInAsyncFunctions: true, + }, + ], + [noConditionalStatements.fullName]: [ + "error", + { + allowReturningBranches: true, + }, + ], + [noLet.fullName]: [ + "error", + { + allowInForLoopInit: true, + }, + ], + [noThisExpressions.fullName]: "off", + [noThrowStatements.fullName]: [ + "error", + { + allowInAsyncFunctions: true, + }, + ], + [noTryStatements.fullName]: "off", + [preferImmutableTypes.fullName]: [ + "error", + { + enforcement: "None", + ignoreInferredTypes: true, + parameters: { + enforcement: "ReadonlyDeep", }, - ], - [`functional/${noTryStatements.name}`]: "off", - [`functional/${preferImmutableTypes.name}`]: [ - "error", - { - enforcement: "None", - ignoreInferredTypes: true, - parameters: { - enforcement: "ReadonlyDeep", + }, + ], + [typeDeclarationImmutability.fullName]: [ + "error", + { + rules: [ + { + identifiers: ["^I?Immutable.+"], + immutability: Immutability.Immutable, + comparator: RuleEnforcementComparator.AtLeast, }, - }, - ], - [`functional/${typeDeclarationImmutability.name}`]: [ - "error", - { - rules: [ - { - identifiers: ["^I?Immutable.+"], - immutability: Immutability.Immutable, - comparator: RuleEnforcementComparator.AtLeast, - }, - { - identifiers: ["^I?ReadonlyDeep.+"], - immutability: Immutability.ReadonlyDeep, - comparator: RuleEnforcementComparator.AtLeast, - }, - { - identifiers: ["^I?Readonly.+"], - immutability: Immutability.ReadonlyShallow, - comparator: RuleEnforcementComparator.AtLeast, - fixer: [ - { - pattern: "^(Array|Map|Set)<(.+)>$", - replace: "Readonly$1<$2>", - }, - { - pattern: "^(.+)$", - replace: "Readonly<$1>", - }, - ], - }, - { - identifiers: ["^I?Mutable.+"], - immutability: Immutability.Mutable, - comparator: RuleEnforcementComparator.AtMost, - fixer: [ - { - pattern: "^Readonly(Array|Map|Set)<(.+)>$", - replace: "$1<$2>", - }, - { - pattern: "^Readonly<(.+)>$", - replace: "$1", - }, - ], - }, - ], - }, - ], - }, -}; - -const config: Linter.Config = mergeConfigs(strict, overrides); + { + identifiers: ["^I?ReadonlyDeep.+"], + immutability: Immutability.ReadonlyDeep, + comparator: RuleEnforcementComparator.AtLeast, + }, + { + identifiers: ["^I?Readonly.+"], + immutability: Immutability.ReadonlyShallow, + comparator: RuleEnforcementComparator.AtLeast, + fixer: [ + { + pattern: "^(Array|Map|Set)<(.+)>$", + replace: "Readonly$1<$2>", + }, + { + pattern: "^(.+)$", + replace: "Readonly<$1>", + }, + ], + }, + { + identifiers: ["^I?Mutable.+"], + immutability: Immutability.Mutable, + comparator: RuleEnforcementComparator.AtMost, + fixer: [ + { + pattern: "^Readonly(Array|Map|Set)<(.+)>$", + replace: "$1<$2>", + }, + { + pattern: "^Readonly<(.+)>$", + replace: "$1", + }, + ], + }, + ], + }, + ], +} satisfies FlatConfig.Config["rules"]; -export default config; +export default { + ...recommended, + ...overrides, +}; diff --git a/src/configs/strict.ts b/src/configs/strict.ts index 5521f1e66..aa0b6c030 100644 --- a/src/configs/strict.ts +++ b/src/configs/strict.ts @@ -1,18 +1,18 @@ -import { type Linter } from "@typescript-eslint/utils/ts-eslint"; +import { type FlatConfig } from "@typescript-eslint/utils/ts-eslint"; -import currying from "#eslint-plugin-functional/configs/currying"; -import noExceptions from "#eslint-plugin-functional/configs/no-exceptions"; -import noMutations from "#eslint-plugin-functional/configs/no-mutations"; -import noOtherParadigms from "#eslint-plugin-functional/configs/no-other-paradigms"; -import noStatements from "#eslint-plugin-functional/configs/no-statements"; -import { mergeConfigs } from "#eslint-plugin-functional/utils/merge-configs"; +import { rules } from "#eslint-plugin-functional/rules"; +import { ruleNameScope } from "#eslint-plugin-functional/utils/misc"; -const config: Linter.Config = mergeConfigs( - currying, - noMutations, - noExceptions, - noOtherParadigms, - noStatements, -); - -export default config; +export default Object.fromEntries( + Object.entries(rules) + .filter( + ([, rule]) => + rule.meta.deprecated !== true && + rule.meta.docs.recommended !== false && + rule.meta.docs.category !== "Stylistic", + ) + .map(([name, rule]) => [ + `${ruleNameScope}/${name}`, + rule.meta.docs.recommendedSeverity, + ]), +) satisfies FlatConfig.Config["rules"]; diff --git a/src/configs/stylistic.ts b/src/configs/stylistic.ts index 1240f118b..168593f25 100644 --- a/src/configs/stylistic.ts +++ b/src/configs/stylistic.ts @@ -1,15 +1,18 @@ -import { type Linter } from "@typescript-eslint/utils/ts-eslint"; +import { type FlatConfig } from "@typescript-eslint/utils/ts-eslint"; -import * as preferPropertySignatures from "#eslint-plugin-functional/rules/prefer-property-signatures"; -import * as preferTacit from "#eslint-plugin-functional/rules/prefer-tacit"; -import * as readonlyType from "#eslint-plugin-functional/rules/readonly-type"; +import { rules } from "#eslint-plugin-functional/rules"; +import { ruleNameScope } from "#eslint-plugin-functional/utils/misc"; -const config: Linter.Config = { - rules: { - [`functional/${preferPropertySignatures.name}`]: "error", - [`functional/${preferTacit.name}`]: "warn", - [`functional/${readonlyType.name}`]: "error", - }, -}; - -export default config; +export default Object.fromEntries( + Object.entries(rules) + .filter( + ([, rule]) => + rule.meta.deprecated !== true && + rule.meta.docs.category === "Stylistic" && + rule.meta.docs.recommended !== false, + ) + .map(([name, rule]) => [ + `${ruleNameScope}/${name}`, + rule.meta.docs.recommendedSeverity, + ]), +) satisfies FlatConfig.Config["rules"]; diff --git a/src/flat.ts b/src/flat.ts new file mode 100644 index 000000000..3ec7ba694 --- /dev/null +++ b/src/flat.ts @@ -0,0 +1,60 @@ +import { type FlatConfig } from "@typescript-eslint/utils/ts-eslint"; + +import all from "#eslint-plugin-functional/configs/all"; +import currying from "#eslint-plugin-functional/configs/currying"; +import disableTypeChecked from "#eslint-plugin-functional/configs/disable-type-checked"; +import externalTypeScriptRecommended from "#eslint-plugin-functional/configs/external-typescript-recommended"; +import externalVanillaRecommended from "#eslint-plugin-functional/configs/external-vanilla-recommended"; +import lite from "#eslint-plugin-functional/configs/lite"; +import noExceptions from "#eslint-plugin-functional/configs/no-exceptions"; +import noMutations from "#eslint-plugin-functional/configs/no-mutations"; +import noOtherParadigms from "#eslint-plugin-functional/configs/no-other-paradigms"; +import noStatements from "#eslint-plugin-functional/configs/no-statements"; +import off from "#eslint-plugin-functional/configs/off"; +import recommended from "#eslint-plugin-functional/configs/recommended"; +import strict from "#eslint-plugin-functional/configs/strict"; +import stylistic from "#eslint-plugin-functional/configs/stylistic"; +import { rules } from "#eslint-plugin-functional/rules"; +import { __VERSION__ } from "#eslint-plugin-functional/utils/constants"; + +const functional = { + meta: { + name: "eslint-plugin-functional", + version: __VERSION__, + } as const, + rules, +} satisfies Omit; + +const configs = { + all: { plugins: { functional }, rules: all }, + lite: { plugins: { functional }, rules: lite }, + recommended: { plugins: { functional }, rules: recommended }, + strict: { plugins: { functional }, rules: strict }, + off: { plugins: { functional }, rules: off }, + disableTypeChecked: { + plugins: { functional }, + rules: disableTypeChecked, + }, + externalVanillaRecommended: { + plugins: { functional }, + rules: externalVanillaRecommended, + }, + externalTypescriptRecommended: { + plugins: { functional }, + rules: externalTypeScriptRecommended, + }, + currying: { plugins: { functional }, rules: currying }, + noExceptions: { plugins: { functional }, rules: noExceptions }, + noMutations: { plugins: { functional }, rules: noMutations }, + noOtherParadigms: { + plugins: { functional }, + rules: noOtherParadigms, + }, + noStatements: { plugins: { functional }, rules: noStatements }, + stylistic: { plugins: { functional }, rules: stylistic }, +} satisfies Record; + +export default { + ...functional, + configs, +} as const; diff --git a/src/rules/functional-parameters.ts b/src/rules/functional-parameters.ts index 487ec23c5..0741308fe 100644 --- a/src/rules/functional-parameters.ts +++ b/src/rules/functional-parameters.ts @@ -13,10 +13,11 @@ import { type IgnoreIdentifierPatternOption, type IgnorePrefixSelectorOption, } from "#eslint-plugin-functional/options"; +import { ruleNameScope } from "#eslint-plugin-functional/utils/misc"; import { type ESFunction } from "#eslint-plugin-functional/utils/node-types"; import { createRuleUsingFunction, - type NamedCreateRuleMetaWithCategory, + type NamedCreateRuleCustomMeta, type RuleResult, } from "#eslint-plugin-functional/utils/rule"; import { @@ -34,6 +35,11 @@ import { isRestElement } from "#eslint-plugin-functional/utils/type-guards"; */ export const name = "functional-parameters" as const; +/** + * The full name of this rule. + */ +export const fullName = `${ruleNameScope}/${name}` as const; + /** * The parameter count options this rule can take. */ @@ -143,11 +149,13 @@ const errorMessages = { /** * The meta data for this rule. */ -const meta: NamedCreateRuleMetaWithCategory = { +const meta: NamedCreateRuleCustomMeta = { type: "suggestion", docs: { category: "Currying", description: "Enforce functional parameters.", + recommended: "recommended", + recommendedSeverity: "error", }, messages: errorMessages, schema, diff --git a/src/rules/immutable-data.ts b/src/rules/immutable-data.ts index cac723773..dd2dc8d30 100644 --- a/src/rules/immutable-data.ts +++ b/src/rules/immutable-data.ts @@ -16,11 +16,14 @@ import { type IgnoreClassesOption, type IgnoreIdentifierPatternOption, } from "#eslint-plugin-functional/options"; -import { isExpected } from "#eslint-plugin-functional/utils/misc"; +import { + isExpected, + ruleNameScope, +} from "#eslint-plugin-functional/utils/misc"; import { createRule, getTypeOfNode, - type NamedCreateRuleMetaWithCategory, + type NamedCreateRuleCustomMeta, type RuleResult, } from "#eslint-plugin-functional/utils/rule"; import { @@ -44,6 +47,11 @@ import { */ export const name = "immutable-data" as const; +/** + * The full name of this rule. + */ +export const fullName = `${ruleNameScope}/${name}` as const; + /** * The options this rule can take. */ @@ -102,11 +110,13 @@ const errorMessages = { /** * The meta data for this rule. */ -const meta: NamedCreateRuleMetaWithCategory = { +const meta: NamedCreateRuleCustomMeta = { type: "suggestion", docs: { category: "No Mutations", description: "Enforce treating data as immutable.", + recommended: "recommended", + recommendedSeverity: "error", requiresTypeChecking: true, }, messages: errorMessages, diff --git a/src/rules/no-classes.ts b/src/rules/no-classes.ts index 5b8681944..2707cf6d8 100644 --- a/src/rules/no-classes.ts +++ b/src/rules/no-classes.ts @@ -1,10 +1,11 @@ import { type JSONSchema4 } from "@typescript-eslint/utils/json-schema"; import { type RuleContext } from "@typescript-eslint/utils/ts-eslint"; +import { ruleNameScope } from "#eslint-plugin-functional/utils/misc"; import { type ESClass } from "#eslint-plugin-functional/utils/node-types"; import { createRule, - type NamedCreateRuleMetaWithCategory, + type NamedCreateRuleCustomMeta, type RuleResult, } from "#eslint-plugin-functional/utils/rule"; @@ -13,6 +14,11 @@ import { */ export const name = "no-classes" as const; +/** + * The full name of this rule. + */ +export const fullName = `${ruleNameScope}/${name}` as const; + /** * The options this rule can take. */ @@ -38,11 +44,13 @@ const errorMessages = { /** * The meta data for this rule. */ -const meta: NamedCreateRuleMetaWithCategory = { +const meta: NamedCreateRuleCustomMeta = { type: "suggestion", docs: { category: "No Other Paradigms", description: "Disallow classes.", + recommended: "recommended", + recommendedSeverity: "error", }, messages: errorMessages, schema, diff --git a/src/rules/no-conditional-statements.ts b/src/rules/no-conditional-statements.ts index 3720a4a06..a41edc3c0 100644 --- a/src/rules/no-conditional-statements.ts +++ b/src/rules/no-conditional-statements.ts @@ -4,10 +4,11 @@ import { type RuleContext } from "@typescript-eslint/utils/ts-eslint"; import { type Type } from "typescript"; import tsApiUtils from "#eslint-plugin-functional/conditional-imports/ts-api-utils"; +import { ruleNameScope } from "#eslint-plugin-functional/utils/misc"; import { createRule, getTypeOfNode, - type NamedCreateRuleMetaWithCategory, + type NamedCreateRuleCustomMeta, type RuleResult, } from "#eslint-plugin-functional/utils/rule"; import { @@ -26,6 +27,11 @@ import { */ export const name = "no-conditional-statements" as const; +/** + * The full name of this rule. + */ +export const fullName = `${ruleNameScope}/${name}` as const; + /** * The options this rule can take. */ @@ -82,11 +88,13 @@ const errorMessages = { /** * The meta data for this rule. */ -const meta: NamedCreateRuleMetaWithCategory = { +const meta: NamedCreateRuleCustomMeta = { type: "suggestion", docs: { category: "No Statements", description: "Disallow conditional statements.", + recommended: "recommended", + recommendedSeverity: "error", requiresTypeChecking: true, }, messages: errorMessages, diff --git a/src/rules/no-expression-statements.ts b/src/rules/no-expression-statements.ts index 410720f58..46a7426ee 100644 --- a/src/rules/no-expression-statements.ts +++ b/src/rules/no-expression-statements.ts @@ -14,11 +14,14 @@ import { shouldIgnorePattern, type IgnoreCodePatternOption, } from "#eslint-plugin-functional/options"; -import { isDirectivePrologue } from "#eslint-plugin-functional/utils/misc"; +import { + ruleNameScope, + isDirectivePrologue, +} from "#eslint-plugin-functional/utils/misc"; import { createRule, getTypeOfNode, - type NamedCreateRuleMetaWithCategory, + type NamedCreateRuleCustomMeta, type RuleResult, } from "#eslint-plugin-functional/utils/rule"; import { @@ -31,6 +34,11 @@ import { */ export const name = "no-expression-statements" as const; +/** + * The full name of this rule. + */ +export const fullName = `${ruleNameScope}/${name}` as const; + /** * The options this rule can take. */ @@ -79,11 +87,13 @@ const errorMessages = { /** * The meta data for this rule. */ -const meta: NamedCreateRuleMetaWithCategory = { +const meta: NamedCreateRuleCustomMeta = { type: "suggestion", docs: { category: "No Statements", description: "Disallow expression statements.", + recommended: "recommended", + recommendedSeverity: "error", requiresTypeChecking: true, }, messages: errorMessages, diff --git a/src/rules/no-let.ts b/src/rules/no-let.ts index b115299f2..84005bd36 100644 --- a/src/rules/no-let.ts +++ b/src/rules/no-let.ts @@ -12,9 +12,10 @@ import { shouldIgnorePattern, type IgnoreIdentifierPatternOption, } from "#eslint-plugin-functional/options"; +import { ruleNameScope } from "#eslint-plugin-functional/utils/misc"; import { createRule, - type NamedCreateRuleMetaWithCategory, + type NamedCreateRuleCustomMeta, type RuleResult, } from "#eslint-plugin-functional/utils/rule"; import { isInForLoopInitializer } from "#eslint-plugin-functional/utils/tree"; @@ -24,6 +25,11 @@ import { isInForLoopInitializer } from "#eslint-plugin-functional/utils/tree"; */ export const name = "no-let" as const; +/** + * The full name of this rule. + */ +export const fullName = `${ruleNameScope}/${name}` as const; + /** * The options this rule can take. */ @@ -72,11 +78,13 @@ const errorMessages = { /** * The meta data for this rule. */ -const meta: NamedCreateRuleMetaWithCategory = { +const meta: NamedCreateRuleCustomMeta = { type: "suggestion", docs: { category: "No Mutations", description: "Disallow mutable variables.", + recommended: "recommended", + recommendedSeverity: "error", }, messages: errorMessages, schema, diff --git a/src/rules/no-loop-statements.ts b/src/rules/no-loop-statements.ts index 3453117e4..3a623176d 100644 --- a/src/rules/no-loop-statements.ts +++ b/src/rules/no-loop-statements.ts @@ -1,10 +1,11 @@ import { type JSONSchema4 } from "@typescript-eslint/utils/json-schema"; import { type RuleContext } from "@typescript-eslint/utils/ts-eslint"; +import { ruleNameScope } from "#eslint-plugin-functional/utils/misc"; import { type ESLoop } from "#eslint-plugin-functional/utils/node-types"; import { createRule, - type NamedCreateRuleMetaWithCategory, + type NamedCreateRuleCustomMeta, type RuleResult, } from "#eslint-plugin-functional/utils/rule"; @@ -13,6 +14,11 @@ import { */ export const name = "no-loop-statements" as const; +/** + * The full name of this rule. + */ +export const fullName = `${ruleNameScope}/${name}` as const; + /** * The options this rule can take. */ @@ -38,11 +44,13 @@ const errorMessages = { /** * The meta data for this rule. */ -const meta: NamedCreateRuleMetaWithCategory = { +const meta: NamedCreateRuleCustomMeta = { type: "suggestion", docs: { category: "No Statements", description: "Disallow imperative loops.", + recommended: "recommended", + recommendedSeverity: "error", }, messages: errorMessages, schema, diff --git a/src/rules/no-mixed-types.ts b/src/rules/no-mixed-types.ts index 8e3596be2..8fa1be221 100644 --- a/src/rules/no-mixed-types.ts +++ b/src/rules/no-mixed-types.ts @@ -2,9 +2,10 @@ import { AST_NODE_TYPES, type TSESTree } from "@typescript-eslint/utils"; import { type JSONSchema4 } from "@typescript-eslint/utils/json-schema"; import { type RuleContext } from "@typescript-eslint/utils/ts-eslint"; +import { ruleNameScope } from "#eslint-plugin-functional/utils/misc"; import { createRuleUsingFunction, - type NamedCreateRuleMetaWithCategory, + type NamedCreateRuleCustomMeta, type RuleResult, } from "#eslint-plugin-functional/utils/rule"; import { @@ -19,6 +20,11 @@ import { */ export const name = "no-mixed-types" as const; +/** + * The full name of this rule. + */ +export const fullName = `${ruleNameScope}/${name}` as const; + /** * The options this rule can take. */ @@ -67,13 +73,15 @@ const errorMessages = { /** * The meta data for this rule. */ -const meta: NamedCreateRuleMetaWithCategory = { +const meta: NamedCreateRuleCustomMeta = { type: "suggestion", docs: { category: "No Other Paradigms", description: "Restrict types so that only members of the same kind are allowed in them.", requiresTypeChecking: true, + recommended: "recommended", + recommendedSeverity: "error", }, messages: errorMessages, schema, diff --git a/src/rules/no-promise-reject.ts b/src/rules/no-promise-reject.ts index 25c28f6d8..ed31a5dad 100644 --- a/src/rules/no-promise-reject.ts +++ b/src/rules/no-promise-reject.ts @@ -2,9 +2,10 @@ import { type TSESTree } from "@typescript-eslint/utils"; import { type JSONSchema4 } from "@typescript-eslint/utils/json-schema"; import { type RuleContext } from "@typescript-eslint/utils/ts-eslint"; +import { ruleNameScope } from "#eslint-plugin-functional/utils/misc"; import { createRule, - type NamedCreateRuleMetaWithCategory, + type NamedCreateRuleCustomMeta, type RuleResult, } from "#eslint-plugin-functional/utils/rule"; import { @@ -17,6 +18,11 @@ import { */ export const name = "no-promise-reject" as const; +/** + * The full name of this rule. + */ +export const fullName = `${ruleNameScope}/${name}` as const; + /** * The options this rule can take. */ @@ -42,11 +48,13 @@ const errorMessages = { /** * The meta data for this rule. */ -const meta: NamedCreateRuleMetaWithCategory = { +const meta: NamedCreateRuleCustomMeta = { type: "suggestion", docs: { category: "No Exceptions", description: "Disallow rejecting promises.", + recommended: false, + recommendedSeverity: "error", }, messages: errorMessages, schema, diff --git a/src/rules/no-return-void.ts b/src/rules/no-return-void.ts index 55d9754bb..a10a10a54 100644 --- a/src/rules/no-return-void.ts +++ b/src/rules/no-return-void.ts @@ -2,11 +2,12 @@ import { type JSONSchema4 } from "@typescript-eslint/utils/json-schema"; import { type RuleContext } from "@typescript-eslint/utils/ts-eslint"; import tsApiUtils from "#eslint-plugin-functional/conditional-imports/ts-api-utils"; +import { ruleNameScope } from "#eslint-plugin-functional/utils/misc"; import { type ESFunctionType } from "#eslint-plugin-functional/utils/node-types"; import { createRule, getTypeOfNode, - type NamedCreateRuleMetaWithCategory, + type NamedCreateRuleCustomMeta, type RuleResult, } from "#eslint-plugin-functional/utils/rule"; import { @@ -21,6 +22,11 @@ import { */ export const name = "no-return-void" as const; +/** + * The full name of this rule. + */ +export const fullName = `${ruleNameScope}/${name}` as const; + /** * The options this rule can take. */ @@ -74,12 +80,14 @@ const errorMessages = { /** * The meta data for this rule. */ -const meta: NamedCreateRuleMetaWithCategory = { +const meta: NamedCreateRuleCustomMeta = { type: "suggestion", docs: { category: "No Statements", description: "Disallow functions that don't return anything.", requiresTypeChecking: true, + recommended: "recommended", + recommendedSeverity: "error", }, messages: errorMessages, schema, diff --git a/src/rules/no-this-expressions.ts b/src/rules/no-this-expressions.ts index e5529f9ed..cf99915d4 100644 --- a/src/rules/no-this-expressions.ts +++ b/src/rules/no-this-expressions.ts @@ -2,9 +2,10 @@ import { type TSESTree } from "@typescript-eslint/utils"; import { type JSONSchema4 } from "@typescript-eslint/utils/json-schema"; import { type RuleContext } from "@typescript-eslint/utils/ts-eslint"; +import { ruleNameScope } from "#eslint-plugin-functional/utils/misc"; import { createRule, - type NamedCreateRuleMetaWithCategory, + type NamedCreateRuleCustomMeta, type RuleResult, } from "#eslint-plugin-functional/utils/rule"; @@ -13,6 +14,11 @@ import { */ export const name = "no-this-expressions" as const; +/** + * The full name of this rule. + */ +export const fullName = `${ruleNameScope}/${name}` as const; + /** * The options this rule can take. */ @@ -38,11 +44,13 @@ const errorMessages = { /** * The meta data for this rule. */ -const meta: NamedCreateRuleMetaWithCategory = { +const meta: NamedCreateRuleCustomMeta = { type: "suggestion", docs: { category: "No Other Paradigms", description: "Disallow this access.", + recommended: "recommended", + recommendedSeverity: "error", }, messages: errorMessages, schema, diff --git a/src/rules/no-throw-statements.ts b/src/rules/no-throw-statements.ts index 4a5308600..e577cf10d 100644 --- a/src/rules/no-throw-statements.ts +++ b/src/rules/no-throw-statements.ts @@ -2,9 +2,10 @@ import { type TSESTree } from "@typescript-eslint/utils"; import { type JSONSchema4 } from "@typescript-eslint/utils/json-schema"; import { type RuleContext } from "@typescript-eslint/utils/ts-eslint"; +import { ruleNameScope } from "#eslint-plugin-functional/utils/misc"; import { createRule, - type NamedCreateRuleMetaWithCategory, + type NamedCreateRuleCustomMeta, type RuleResult, } from "#eslint-plugin-functional/utils/rule"; import { isInFunctionBody } from "#eslint-plugin-functional/utils/tree"; @@ -14,6 +15,11 @@ import { isInFunctionBody } from "#eslint-plugin-functional/utils/tree"; */ export const name = "no-throw-statements" as const; +/** + * The full name of this rule. + */ +export const fullName = `${ruleNameScope}/${name}` as const; + /** * The options this rule can take. */ @@ -57,11 +63,13 @@ const errorMessages = { /** * The meta data for this rule. */ -const meta: NamedCreateRuleMetaWithCategory = { +const meta: NamedCreateRuleCustomMeta = { type: "suggestion", docs: { category: "No Exceptions", description: "Disallow throwing exceptions.", + recommended: "recommended", + recommendedSeverity: "error", }, messages: errorMessages, schema, diff --git a/src/rules/no-try-statements.ts b/src/rules/no-try-statements.ts index 179745875..d8b2dd40b 100644 --- a/src/rules/no-try-statements.ts +++ b/src/rules/no-try-statements.ts @@ -2,9 +2,10 @@ import { type TSESTree } from "@typescript-eslint/utils"; import { type JSONSchema4 } from "@typescript-eslint/utils/json-schema"; import { type RuleContext } from "@typescript-eslint/utils/ts-eslint"; +import { ruleNameScope } from "#eslint-plugin-functional/utils/misc"; import { createRule, - type NamedCreateRuleMetaWithCategory, + type NamedCreateRuleCustomMeta, type RuleResult, } from "#eslint-plugin-functional/utils/rule"; @@ -13,6 +14,11 @@ import { */ export const name = "no-try-statements" as const; +/** + * The full name of this rule. + */ +export const fullName = `${ruleNameScope}/${name}` as const; + /** * The options this rule can take. */ @@ -62,11 +68,13 @@ const errorMessages = { /** * The meta data for this rule. */ -const meta: NamedCreateRuleMetaWithCategory = { +const meta: NamedCreateRuleCustomMeta = { type: "suggestion", docs: { category: "No Exceptions", description: "Disallow try-catch[-finally] and try-finally patterns.", + recommended: "recommended", + recommendedSeverity: "error", }, messages: errorMessages, schema, diff --git a/src/rules/prefer-immutable-types.ts b/src/rules/prefer-immutable-types.ts index 88331df20..5bc88bc51 100644 --- a/src/rules/prefer-immutable-types.ts +++ b/src/rules/prefer-immutable-types.ts @@ -17,6 +17,7 @@ import { shouldIgnorePattern, type IgnoreClassesOption, } from "#eslint-plugin-functional/options"; +import { ruleNameScope } from "#eslint-plugin-functional/utils/misc"; import { type ESFunctionType } from "#eslint-plugin-functional/utils/node-types"; import { createRule, @@ -24,7 +25,7 @@ import { getTypeImmutabilityOfNode, getTypeImmutabilityOfType, isImplementationOfOverload, - type NamedCreateRuleMetaWithCategory, + type NamedCreateRuleCustomMeta, type RuleResult, } from "#eslint-plugin-functional/utils/rule"; import { @@ -46,6 +47,11 @@ import { */ export const name = "prefer-immutable-types" as const; +/** + * The full name of this rule. + */ +export const fullName = `${ruleNameScope}/${name}` as const; + type RawEnforcement = | Exclude | "None" @@ -302,12 +308,14 @@ const errorMessages = { /** * The meta data for this rule. */ -const meta: NamedCreateRuleMetaWithCategory = { +const meta: NamedCreateRuleCustomMeta = { type: "suggestion", docs: { category: "No Mutations", description: "Require function parameters to be typed as certain immutability", + recommended: "recommended", + recommendedSeverity: "error", requiresTypeChecking: true, }, fixable: "code", @@ -335,10 +343,7 @@ function getAllFixers( fixerConfigs: FixerConfig[] | false, suggestionsConfigs: SuggestionsConfig[] | false, ): AllFixers { - const nodeText = context - .getSourceCode() - .getText(node) - .replaceAll(/\s+/gmu, " "); + const nodeText = context.sourceCode.getText(node).replaceAll(/\s+/gmu, " "); const fix = fixerConfigs === false diff --git a/src/rules/prefer-property-signatures.ts b/src/rules/prefer-property-signatures.ts index 1bb0c8f8a..c7f138351 100644 --- a/src/rules/prefer-property-signatures.ts +++ b/src/rules/prefer-property-signatures.ts @@ -2,9 +2,10 @@ import { type TSESTree } from "@typescript-eslint/utils"; import { type JSONSchema4 } from "@typescript-eslint/utils/json-schema"; import { type RuleContext } from "@typescript-eslint/utils/ts-eslint"; +import { ruleNameScope } from "#eslint-plugin-functional/utils/misc"; import { createRule, - type NamedCreateRuleMetaWithCategory, + type NamedCreateRuleCustomMeta, type RuleResult, } from "#eslint-plugin-functional/utils/rule"; import { isInReadonly } from "#eslint-plugin-functional/utils/tree"; @@ -14,6 +15,11 @@ import { isInReadonly } from "#eslint-plugin-functional/utils/tree"; */ export const name = "prefer-property-signatures" as const; +/** + * The full name of this rule. + */ +export const fullName = `${ruleNameScope}/${name}` as const; + /** * The options this rule can take. */ @@ -58,11 +64,13 @@ const errorMessages = { /** * The meta data for this rule. */ -const meta: NamedCreateRuleMetaWithCategory = { +const meta: NamedCreateRuleCustomMeta = { type: "suggestion", docs: { category: "Stylistic", description: "Prefer property signatures over method signatures.", + recommended: "recommended", + recommendedSeverity: "error", requiresTypeChecking: true, }, messages: errorMessages, diff --git a/src/rules/prefer-readonly-type.ts b/src/rules/prefer-readonly-type.ts index ca61d179d..97ac45620 100644 --- a/src/rules/prefer-readonly-type.ts +++ b/src/rules/prefer-readonly-type.ts @@ -9,12 +9,13 @@ import { type IgnoreAccessorPatternOption, type IgnoreCodePatternOption, } from "#eslint-plugin-functional/options"; +import { ruleNameScope } from "#eslint-plugin-functional/utils/misc"; import { type ESArrayTupleType } from "#eslint-plugin-functional/utils/node-types"; import { createRule, getTypeOfNode, type BaseOptions, - type NamedCreateRuleMetaWithCategory, + type NamedCreateRuleCustomMeta, type RuleResult, } from "#eslint-plugin-functional/utils/rule"; import { @@ -42,6 +43,11 @@ import { */ export const name = "prefer-readonly-type" as const; +/** + * The full name of this rule. + */ +export const fullName = `${ruleNameScope}/${name}` as const; + /** * The options this rule can take. */ @@ -129,7 +135,7 @@ const errorMessages = { /** * The meta data for this rule. */ -const meta: NamedCreateRuleMetaWithCategory = { +const meta: NamedCreateRuleCustomMeta = { deprecated: true, replacedBy: [ "functional/prefer-immutable-types", @@ -139,6 +145,8 @@ const meta: NamedCreateRuleMetaWithCategory = { docs: { category: "No Mutations", description: "Prefer readonly types over mutable types.", + recommended: "recommended", + recommendedSeverity: "error", requiresTypeChecking: true, }, messages: errorMessages, diff --git a/src/rules/prefer-tacit.ts b/src/rules/prefer-tacit.ts index bf0b85c14..9afab141f 100644 --- a/src/rules/prefer-tacit.ts +++ b/src/rules/prefer-tacit.ts @@ -11,12 +11,13 @@ import * as semver from "semver"; import { type Type } from "typescript"; import ts from "#eslint-plugin-functional/conditional-imports/typescript"; +import { ruleNameScope } from "#eslint-plugin-functional/utils/misc"; import { type ESFunction } from "#eslint-plugin-functional/utils/node-types"; import { createRule, getESTreeNode, getTypeOfNode, - type NamedCreateRuleMetaWithCategory, + type NamedCreateRuleCustomMeta, type RuleResult, } from "#eslint-plugin-functional/utils/rule"; import { isNested } from "#eslint-plugin-functional/utils/tree"; @@ -35,6 +36,11 @@ import { */ export const name = "prefer-tacit" as const; +/** + * The full name of this rule. + */ +export const fullName = `${ruleNameScope}/${name}` as const; + /** * The options this rule can take. */ @@ -60,11 +66,13 @@ const errorMessages = { /** * The meta data for this rule. */ -const meta: NamedCreateRuleMetaWithCategory = { +const meta: NamedCreateRuleCustomMeta = { type: "suggestion", docs: { category: "Stylistic", description: "Replaces `x => f(x)` with just `f`.", + recommended: "recommended", + recommendedSeverity: "warn", requiresTypeChecking: true, }, messages: errorMessages, @@ -132,7 +140,7 @@ function fixFunctionCallToReference( return [ fixer.replaceText( node as TSESTree.Node, - context.getSourceCode().getText(caller.callee as TSESTree.Node), + context.sourceCode.getText(caller.callee as TSESTree.Node), ), ]; } diff --git a/src/rules/readonly-type.ts b/src/rules/readonly-type.ts index 7a47ab705..a7071869f 100644 --- a/src/rules/readonly-type.ts +++ b/src/rules/readonly-type.ts @@ -5,9 +5,10 @@ import { type RuleContext, } from "@typescript-eslint/utils/ts-eslint"; +import { ruleNameScope } from "#eslint-plugin-functional/utils/misc"; import { createRule, - type NamedCreateRuleMetaWithCategory, + type NamedCreateRuleCustomMeta, type RuleResult, } from "#eslint-plugin-functional/utils/rule"; import { getReadonly } from "#eslint-plugin-functional/utils/tree"; @@ -25,6 +26,11 @@ import { */ export const name = "readonly-type" as const; +/** + * The full name of this rule. + */ +export const fullName = `${ruleNameScope}/${name}` as const; + /** * The options this rule can take. */ @@ -58,12 +64,14 @@ const errorMessages = { /** * The meta data for this rule. */ -const meta: NamedCreateRuleMetaWithCategory = { +const meta: NamedCreateRuleCustomMeta = { type: "suggestion", docs: { category: "Stylistic", description: "Require consistently using either `readonly` keywords or `Readonly`", + recommended: "recommended", + recommendedSeverity: "error", requiresTypeChecking: true, }, fixable: "code", @@ -81,7 +89,7 @@ function checkTypeLiteral( ): RuleResult { const [mode] = options; const readonlyWrapper = getReadonly(node); - const sourceCode = context.getSourceCode(); + const { sourceCode } = context; if (readonlyWrapper !== null) { if (mode === "generic") { diff --git a/src/rules/type-declaration-immutability.ts b/src/rules/type-declaration-immutability.ts index 6f900de06..c8dc43177 100644 --- a/src/rules/type-declaration-immutability.ts +++ b/src/rules/type-declaration-immutability.ts @@ -12,12 +12,15 @@ import { shouldIgnorePattern, type IgnoreIdentifierPatternOption, } from "#eslint-plugin-functional/options"; -import { getNodeIdentifierTexts } from "#eslint-plugin-functional/utils/misc"; +import { + getNodeIdentifierTexts, + ruleNameScope, +} from "#eslint-plugin-functional/utils/misc"; import { type ESTypeDeclaration } from "#eslint-plugin-functional/utils/node-types"; import { createRule, getTypeImmutabilityOfNode, - type NamedCreateRuleMetaWithCategory, + type NamedCreateRuleCustomMeta, type RuleResult, } from "#eslint-plugin-functional/utils/rule"; import { isTSInterfaceDeclaration } from "#eslint-plugin-functional/utils/type-guards"; @@ -27,6 +30,11 @@ import { isTSInterfaceDeclaration } from "#eslint-plugin-functional/utils/type-g */ export const name = "type-declaration-immutability" as const; +/** + * The full name of this rule. + */ +export const fullName = `${ruleNameScope}/${name}` as const; + /** * How the actual immutability should be compared to the given immutability. */ @@ -176,11 +184,13 @@ const errorMessages = { /** * The meta data for this rule. */ -const meta: NamedCreateRuleMetaWithCategory = { +const meta: NamedCreateRuleCustomMeta = { type: "suggestion", docs: { category: "No Mutations", description: "Enforce the immutability of types based on patterns.", + recommended: "recommended", + recommendedSeverity: "error", requiresTypeChecking: true, }, messages: errorMessages, @@ -278,7 +288,7 @@ function getConfiguredFixer( context: Readonly>, configs: FixerConfig[], ): NonNullable | null { - const text = context.getSourceCode().getText(node); + const text = context.sourceCode.getText(node); const config = configs.find((c) => c.pattern.test(text)); if (config === undefined) { return null; diff --git a/src/utils/constants.ts b/src/utils/constants.ts new file mode 100644 index 000000000..6af1f8d59 --- /dev/null +++ b/src/utils/constants.ts @@ -0,0 +1,2 @@ +// eslint-disable-next-line @typescript-eslint/naming-convention -- This is a special var. +export const __VERSION__ = "0.0.0-development"; diff --git a/src/utils/merge-configs.ts b/src/utils/merge-configs.ts deleted file mode 100644 index b7d9b77c6..000000000 --- a/src/utils/merge-configs.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { deepmergeCustom } from "deepmerge-ts"; - -export const mergeConfigs = deepmergeCustom< - {}, - { - keyPath: PropertyKey[]; - } ->({ - metaDataUpdater: (previousMeta, metaMeta) => { - if (previousMeta === undefined) { - if (metaMeta.key === undefined) { - return { keyPath: [] }; - } - return { keyPath: [metaMeta.key] }; - } - if (metaMeta.key === undefined) { - return previousMeta; - } - return { - ...metaMeta, - keyPath: [...previousMeta.keyPath, metaMeta.key], - }; - }, - mergeArrays(values, utils, meta) { - if ( - meta !== undefined && - meta.keyPath.length >= 2 && - meta.keyPath[0] === "rules" - ) { - return utils.defaultMergeFunctions.mergeOthers(values); - } - - return utils.actions.defaultMerge; - }, -}); diff --git a/src/utils/misc.ts b/src/utils/misc.ts index 245e3248c..3a96b1952 100644 --- a/src/utils/misc.ts +++ b/src/utils/misc.ts @@ -17,6 +17,8 @@ import { isVariableDeclaration, } from "#eslint-plugin-functional/utils/type-guards"; +export const ruleNameScope = "functional"; + /** * Higher order function to check if the two given values are the same. */ @@ -67,8 +69,7 @@ function getNodeIdentifierText( : isUnaryExpression(node) ? getNodeIdentifierText(node.argument, context) : isTSTypeAnnotation(node) - ? context - .getSourceCode() + ? context.sourceCode .getText(node.typeAnnotation as TSESTree.Node) .replaceAll(/\s+/gmu, "") : null; @@ -92,7 +93,7 @@ export function getNodeCode( node: TSESTree.Node, context: Readonly>, ): string { - return context.getSourceCode().getText(node); + return context.sourceCode.getText(node); } /** diff --git a/src/utils/rule.ts b/src/utils/rule.ts index ce4dcdefe..0343c5901 100644 --- a/src/utils/rule.ts +++ b/src/utils/rule.ts @@ -19,26 +19,44 @@ import { type Node as TSNode, type Type, type TypeNode } from "typescript"; import ts from "#eslint-plugin-functional/conditional-imports/typescript"; import { getImmutabilityOverrides } from "#eslint-plugin-functional/settings"; +import { __VERSION__ } from "#eslint-plugin-functional/utils/constants"; import { type ESFunction } from "#eslint-plugin-functional/utils/node-types"; -// eslint-disable-next-line @typescript-eslint/naming-convention -- This is a special var. -const __VERSION__ = "0.0.0-development"; - /** * Any custom rule meta properties. */ -export type NamedCreateRuleMetaWithCategory = - NamedCreateRuleMeta & { - docs: { - /** Used for splitting the README rules list into sub-lists. */ - category: string; - }; - }; +export type NamedCreateRuleCustomMeta = Omit< + NamedCreateRuleMeta, + "docs" +> & { + docs: { + /** + * Used for creating category configs and splitting the README rules list into sub-lists. + */ + category: + | "Currying" + | "No Exceptions" + | "No Mutations" + | "No Other Paradigms" + | "No Statements" + | "Stylistic"; + + recommended: "recommended" | "strict" | false; + recommendedSeverity: "error" | "warn"; + } & Omit["docs"], "recommended">; +}; /** * All options must extends this type. */ -export type BaseOptions = unknown[]; +export type BaseOptions = ReadonlyArray; + +export type CustomRuleModule< + MessageIds extends string, + Options extends BaseOptions, +> = Omit, "meta"> & { + readonly meta: NamedCreateRuleCustomMeta; +}; /** * The result all rules return. @@ -106,10 +124,10 @@ export function createRule< Options extends BaseOptions, >( name: string, - meta: NamedCreateRuleMetaWithCategory, + meta: NamedCreateRuleCustomMeta, defaultOptions: Options, ruleFunctionsMap: RuleFunctionsMap, -): RuleModule { +) { return createRuleUsingFunction( name, meta, @@ -126,22 +144,21 @@ export function createRuleUsingFunction< Options extends BaseOptions, >( name: string, - meta: NamedCreateRuleMetaWithCategory, + meta: NamedCreateRuleCustomMeta, defaultOptions: Options, createFunction: ( context: Readonly>, options: Readonly, ) => RuleFunctionsMap, -): RuleModule { +) { const ruleCreator = RuleCreator( (ruleName) => `https://github.com/eslint-functional/eslint-plugin-functional/blob/v${__VERSION__}/docs/rules/${ruleName}.md`, ); - // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion -- false positive return ruleCreator({ name, - meta, + meta: meta as any, defaultOptions, create: (context, options) => { const ruleFunctionsMap = createFunction(context, options); @@ -157,7 +174,7 @@ export function createRuleUsingFunction< ]), ); }, - }) as RuleModule; + }) as CustomRuleModule; } /** diff --git a/tests/common/ignore-options.test.ts b/tests/common/ignore-options.test.ts index fcf977107..2c576cb1d 100644 --- a/tests/common/ignore-options.test.ts +++ b/tests/common/ignore-options.test.ts @@ -193,7 +193,7 @@ const invalidTests: Array< getRuleTester(configs.esLatest).run( "ignoreAccessorPattern", - createDummyRuleFor("AssignmentExpression"), + createDummyRuleFor("AssignmentExpression") as any, addFilename(filename, { valid: validTests, invalid: invalidTests, @@ -257,7 +257,7 @@ const assignmentExpressionInvalidTests: Array< getRuleTester(configs.esLatest).run( "ignoreIdentifierPattern", - createDummyRuleFor("AssignmentExpression"), + createDummyRuleFor("AssignmentExpression") as any, addFilename(filename, { valid: assignmentExpressionValidTests, invalid: assignmentExpressionInvalidTests, @@ -305,7 +305,7 @@ const expressionStatementInvalidTests: Array< getRuleTester(configs.esLatest).run( "ignoreCodePattern", - createDummyRuleFor("VariableDeclaration"), + createDummyRuleFor("VariableDeclaration") as any, addFilename(filename, { valid: expressionStatementValidTests, invalid: expressionStatementInvalidTests, diff --git a/tests/configs.test.ts b/tests/configs.test.ts index efcb9bae5..bb657274e 100644 --- a/tests/configs.test.ts +++ b/tests/configs.test.ts @@ -2,7 +2,6 @@ import { describe, expect, it } from "vitest"; import all from "#eslint-plugin-functional/configs/all"; import currying from "#eslint-plugin-functional/configs/currying"; -import deprecated from "#eslint-plugin-functional/configs/deprecated"; import lite from "#eslint-plugin-functional/configs/lite"; import noExceptions from "#eslint-plugin-functional/configs/no-exceptions"; import noMutations from "#eslint-plugin-functional/configs/no-mutations"; @@ -16,16 +15,11 @@ import { rules } from "#eslint-plugin-functional/rules"; describe("Configs", () => { const allRules = Object.values(rules); + const allConfigRules = Object.keys(all ?? {}); + const offConfigRules = Object.entries(off ?? {}); const allNonDeprecatedRules = allRules.filter( (rule) => rule.meta === undefined || rule.meta.deprecated !== true, ); - const allDeprecatedRules = allRules.filter( - (rule) => rule.meta.deprecated === true, - ); - - const allConfigRules = Object.keys(all.rules ?? {}); - const deprecatedConfigRules = Object.keys(deprecated.rules ?? {}); - const offConfigRules = Object.entries(off.rules ?? {}); it('"All" - should have the right number of rules', () => { expect(allConfigRules.length).to.equal(allNonDeprecatedRules.length); @@ -41,26 +35,6 @@ describe("Configs", () => { }, ); - it('"Deprecated" - should have the right number of rules', () => { - expect(deprecatedConfigRules.length).to.equal( - allDeprecatedRules.length, - "should have every deprecated rule", - ); - }); - - it.each(deprecatedConfigRules)( - '"Deprecated" - should have not have deprecated rules', - (name) => { - expect( - rules[name.slice("functional/".length) as keyof typeof rules].meta - .deprecated, - ).to.equal( - true, - `Deprecated Config contains non-deprecated rule "${name}".`, - ); - }, - ); - it('"Off" - should have the right number of rules', () => { expect(offConfigRules.length).to.equal( allRules.length, @@ -97,10 +71,10 @@ describe("Configs", () => { describe.each(configs)( '"%s" Config rules are in the "All" Config', (name, config) => { - const ruleNames = Object.keys(config.rules ?? {}); + const ruleNames = Object.keys(config ?? {}); it.each(ruleNames)(`%s`, (rule) => { - expect(all.rules?.[rule]).toBeDefined(); + expect(all?.[rule]).toBeDefined(); }); }, ); diff --git a/tests/helpers/testers.ts b/tests/helpers/testers.ts index 5e0b3ed03..bc4af9d27 100644 --- a/tests/helpers/testers.ts +++ b/tests/helpers/testers.ts @@ -1,5 +1,7 @@ import { type RuleModule } from "@typescript-eslint/utils/ts-eslint"; +import { type CustomRuleModule } from "#eslint-plugin-functional/utils/rule"; + import { getRuleTester } from "./RuleTester"; import { configs } from "./configs"; import { @@ -20,7 +22,7 @@ type TestFunction< export function testRule< TMessageIds extends string, TOptions extends Readonly, ->(ruleName: string, rule: RuleModule) { +>(ruleName: string, rule: CustomRuleModule) { return Object.fromEntries( [...Object.entries(configs)].map( ([configName, config]): [ @@ -31,7 +33,7 @@ export function testRule< ({ valid, invalid }) => { const ruleTester = getRuleTester(config); - ruleTester.run(ruleName, rule, { + ruleTester.run(ruleName, rule as RuleModule, { valid: processValidTestCase(valid), invalid: processInvalidTestCase(invalid), }); diff --git a/tests/helpers/util.ts b/tests/helpers/util.ts index 66f74cecc..9504636d9 100644 --- a/tests/helpers/util.ts +++ b/tests/helpers/util.ts @@ -7,11 +7,11 @@ import { type SharedConfigurationSettings, type TSESLint, } from "@typescript-eslint/utils"; -import { type RuleModule } from "@typescript-eslint/utils/ts-eslint"; +import { type NamedCreateRuleMeta } from "@typescript-eslint/utils/eslint-utils"; import { + type CustomRuleModule, createRuleUsingFunction, - type NamedCreateRuleMetaWithCategory, type RuleFunctionsMap, } from "#eslint-plugin-functional/utils/rule"; @@ -87,11 +87,10 @@ export function createDummyRule( create: ( context: Readonly>, ) => RuleFunctionsMap, -): RuleModule { - const meta: NamedCreateRuleMetaWithCategory<"generic"> = { +): CustomRuleModule { + const meta: NamedCreateRuleMeta<"generic"> = { type: "suggestion", docs: { - category: "testing", description: "rule used in testing", }, messages: { @@ -109,7 +108,12 @@ export function createDummyRule( }, }; - return createRuleUsingFunction("dummy-rule", meta, [true, {}], create); + return createRuleUsingFunction( + "dummy-rule", + meta as any, + [true, {}], + create, + ) as any; } /** @@ -138,10 +142,13 @@ export function addFilename< }; } -export type MessagesOf>> = - T extends RuleModule> +export type MessagesOf< + T extends CustomRuleModule>, +> = + T extends CustomRuleModule> ? Messages : never; -export type OptionsOf>> = - T extends RuleModule ? Options : never; +export type OptionsOf< + T extends CustomRuleModule>, +> = T extends CustomRuleModule ? Options : never; diff --git a/tests/index.test.ts b/tests/index.test.ts index 52aba9b1f..103552985 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -6,19 +6,20 @@ import { readdirSync } from "node:fs"; import { describe, expect, it } from "vitest"; -import plugin from "#eslint-plugin-functional"; +import classic from "#eslint-plugin-functional/classic"; +import flat from "#eslint-plugin-functional/flat"; -describe("index.ts", () => { +describe("Flat", () => { it("should have all the rules", () => { const ruleFiles: string[] = readdirSync("./src/rules").filter( (file) => file !== "index.ts" && file.endsWith(".ts"), ); expect( - Object.hasOwn(plugin, "rules"), + Object.hasOwn(flat, "rules"), 'The plugin\'s config object should have a "rules" property.', ); - expect(ruleFiles.length).to.equal(Object.keys(plugin.rules ?? {}).length); + expect(ruleFiles.length).to.equal(Object.keys(flat.rules ?? {}).length); }); it("should have all the configs", () => { @@ -27,11 +28,40 @@ describe("index.ts", () => { ); expect( - Object.hasOwn(plugin, "configs"), + Object.hasOwn(flat, "configs"), 'The plugin\'s config object should have a "configs" property.', ); - expect(configFiles.length - 1).to.equal( - Object.keys(plugin.configs ?? {}).length, + expect(configFiles.length).to.equal( + Object.keys(flat.configs ?? {}).length, + "should have all the configs except deprecated", + ); + }); +}); + +describe("Classic", () => { + it("should have all the rules", () => { + const ruleFiles: string[] = readdirSync("./src/rules").filter( + (file) => file !== "index.ts" && file.endsWith(".ts"), + ); + + expect( + Object.hasOwn(classic, "rules"), + 'The plugin\'s config object should have a "rules" property.', + ); + expect(ruleFiles.length).to.equal(Object.keys(classic.rules ?? {}).length); + }); + + it("should have all the configs", () => { + const configFiles: string[] = readdirSync("./src/configs").filter( + (file) => file !== "index.ts" && file.endsWith(".ts"), + ); + + expect( + Object.hasOwn(classic, "configs"), + 'The plugin\'s config object should have a "configs" property.', + ); + expect(configFiles.length).to.equal( + Object.keys(classic.configs ?? {}).length, "should have all the configs except deprecated", ); }); diff --git a/tests/mergers/configs.test.ts b/tests/mergers/configs.test.ts deleted file mode 100644 index 3c8b2374f..000000000 --- a/tests/mergers/configs.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { mergeConfigs } from "#eslint-plugin-functional/utils/merge-configs"; - -describe("merge eslint configs", () => { - it("should replace rule config", (t) => { - const a = { - rules: { - "plugin/rule": ["error", { foo: "bar" }], - }, - }; - const b = { - rules: { - "plugin/rule": ["error", { baz: "qux" }], - }, - }; - - const expected = { - rules: { - "plugin/rule": b.rules["plugin/rule"], - }, - }; - - const result = mergeConfigs(a, b); - - expect(result).to.deep.equal(expected, "result is not merged correctly"); - expect(result.rules["plugin/rule"]).to.equal( - expected.rules["plugin/rule"], - "rule value was not replaced", - ); - }); -}); diff --git a/tsconfig.base.json b/tsconfig.base.json index 40ccfd783..9a14633b9 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -31,7 +31,8 @@ "types": ["vitest/importMeta"], "baseUrl": ".", "paths": { - "#eslint-plugin-functional": ["src"], + "#eslint-plugin-functional/flat": ["src/flat.ts"], + "#eslint-plugin-functional/classic": ["src/classic.ts"], "#eslint-plugin-functional/configs/*": ["src/configs/*"], "#eslint-plugin-functional/options": ["src/options"], "#eslint-plugin-functional/rules": ["src/rules"], diff --git a/tsconfig.build.json b/tsconfig.build.json index 14667db5d..87c00d239 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -2,7 +2,7 @@ "extends": "./tsconfig.base.json", "compilerOptions": { "rootDir": "src", - "declaration": false + "declaration": true }, "include": ["src", "typings"] }