diff --git a/.cspell.json b/.cspell.json
index 5df00838e..6258c928c 100644
--- a/.cspell.json
+++ b/.cspell.json
@@ -1,5 +1,5 @@
{
- "version": "0.1",
+ "version": "0.2",
"language": "en",
"ignorePaths": [
"**/coverage/**",
@@ -21,6 +21,7 @@
"misc",
"filetypes"
],
+ "import": ["./node_modules/@cspell/dict-cryptocurrencies/cspell-ext.json"],
"ignoreRegExpList": [
"/```[\\w\\W]*?```/",
"/~~~[\\w\\W]*?~~~/",
@@ -28,17 +29,11 @@
"/`[^`]*`/",
"/\\.\\/docs\\/rules\\/[^.]*.md/",
"/TS[^\\s]+/",
- "\\(#.+?\\)"
- ],
- "words": [
- "globstar",
- "IIFE",
- "IIFEs",
- "ruleset",
- "rulesets",
- "typeguard",
- "typeguards"
+ "\\(#.+?\\)",
+ "// @ts-.*",
+ "/[A-Za-z0-9]{32,}/"
],
+ "words": ["litecoin", "globstar", "IIFE", "IIFEs", "ruleset", "rulesets"],
"overrides": [
{
"filename": "**/*.{ts,js}",
diff --git a/.eslint-doc-generatorrc.js b/.eslint-doc-generatorrc.js
new file mode 100644
index 000000000..06c92f4cb
--- /dev/null
+++ b/.eslint-doc-generatorrc.js
@@ -0,0 +1,12 @@
+const { format } = require("prettier");
+
+/** @type {import('eslint-doc-generator').GenerateOptions} */
+const config = {
+ configEmoji: [["lite", "βοΈ"]],
+ ignoreConfig: ["all", "off"],
+ ruleDocSectionInclude: ["Rule Details"],
+ ruleListSplit: "meta.docs.category",
+ postprocess: (doc) => format(doc, { parser: "markdown" }),
+};
+
+module.exports = config;
diff --git a/.eslintrc.json b/.eslintrc.json
index bd6c7fc07..0099e9c18 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -29,29 +29,17 @@
],
"sourceType": "module"
},
- "ignorePatterns": ["/build/", "/coverage/", "/lib/", "/**/*.cjs", "/**/*.js"],
+ "ignorePatterns": [
+ "/build/",
+ "/coverage/",
+ "/lib/",
+ "/**/*.cjs",
+ "/**/*.js",
+ "/src/utils/tsutils.ts"
+ ],
"rules": {
- "@typescript-eslint/explicit-module-boundary-types": "off",
- "@typescript-eslint/no-unnecessary-condition": "off",
- "@typescript-eslint/no-unsafe-argument": "off",
- "@typescript-eslint/no-unsafe-assignment": "off",
- "@typescript-eslint/no-unsafe-call": "off",
- "@typescript-eslint/no-unsafe-member-access": "off",
- "@typescript-eslint/no-unsafe-return": "off",
- "@typescript-eslint/prefer-readonly-parameter-types": "warn",
- "import/no-relative-parent-imports": "error",
- "functional/prefer-readonly-type": "off",
- "node/no-unsupported-features/es-builtins": "off",
- "node/no-unsupported-features/es-syntax": "off",
- "promise/prefer-await-to-callbacks": "off",
- // only available for node >= 16.8
- "unicorn/prefer-at": "off",
- // enable once supported in all our supported node versions.
- "unicorn/prefer-node-protocol": "off",
- // only available for node >= 15.4
- "unicorn/prefer-string-replace-all": "off",
- // only available for node >= 14.8
- "unicorn/prefer-top-level-await": "off"
+ "functional/prefer-immutable-types": "off",
+ "import/no-relative-parent-imports": "error"
},
"overrides": [
// Top level files.
@@ -64,7 +52,7 @@
"files": ["./src/**/*"],
"extends": ["plugin:eslint-plugin/recommended"],
"rules": {
- "functional/no-expression-statement": "error"
+ "functional/no-expression-statements": "error"
}
},
{
@@ -75,17 +63,37 @@
}
},
{
- "files": ["./src/util/typeguard.ts", "./tests/**/*", "./cz-adapter/**/*"],
+ "files": ["./src/utils/type-guards.ts", "./src/utils/node-types.ts"],
"rules": {
"jsdoc/require-jsdoc": "off"
}
},
+ {
+ "files": ["./tests/**/*", "./cz-adapter/**/*"],
+ "rules": {
+ "@typescript-eslint/no-unsafe-argument": "off",
+ "@typescript-eslint/no-unsafe-assignment": "off",
+ "@typescript-eslint/no-unsafe-call": "off",
+ "@typescript-eslint/no-unsafe-member-access": "off",
+ "@typescript-eslint/no-unsafe-return": "off",
+ "jsdoc/require-jsdoc": "off"
+ }
+ },
+ {
+ "files": ["./typings/**/*"],
+ "extends": ["plugin:functional/off"],
+ "rules": {
+ "@typescript-eslint/no-unused-vars": "off",
+ "@typescript-eslint/no-explicit-any": "off"
+ }
+ },
// FIXME: This override is defined in the upsteam; it shouldn't need to be redefined here. Why?
{
"files": ["./**/*.md/**"],
"parserOptions": {
"project": null
},
+ "extends": ["plugin:markdown/recommended", "plugin:functional/off"],
"rules": {
"@typescript-eslint/await-thenable": "off",
"@typescript-eslint/consistent-type-definitions": "off",
@@ -94,12 +102,15 @@
"@typescript-eslint/naming-convention": "off",
"@typescript-eslint/no-confusing-void-expression": "off",
"@typescript-eslint/no-empty-function": "off",
+ "@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-floating-promises": "off",
"@typescript-eslint/no-for-in-array": "off",
"@typescript-eslint/no-implied-eval": "off",
"@typescript-eslint/no-misused-promises": "off",
"@typescript-eslint/no-throw-literal": "off",
+ "@typescript-eslint/no-unnecessary-condition": "off",
"@typescript-eslint/no-unnecessary-type-assertion": "off",
+ "@typescript-eslint/no-unsafe-argument": "off",
"@typescript-eslint/no-unsafe-assignment": "off",
"@typescript-eslint/no-unsafe-call": "off",
"@typescript-eslint/no-unsafe-member-access": "off",
@@ -120,17 +131,6 @@
"@typescript-eslint/strict-boolean-expressions": "off",
"@typescript-eslint/switch-exhaustiveness-check": "off",
"@typescript-eslint/unbound-method": "off",
- "functional/functional-parameters": "off",
- "functional/immutable-data": "off",
- "functional/no-class": "off",
- "functional/no-expression-statement": "off",
- "functional/no-let": "off",
- "functional/no-loop-statement": "off",
- "functional/no-return-void": "off",
- "functional/no-this-expression": "off",
- "functional/no-throw-statement": "off",
- "functional/no-try-statement": "off",
- "functional/prefer-readonly-type": "off",
"import/no-unresolved": "off",
"init-declarations": "off",
"jsdoc/require-jsdoc": "off",
@@ -146,6 +146,7 @@
"sonarjs/no-unused-collection": "off",
"unicorn/prefer-optional-catch-binding": "off",
"unicorn/prefer-top-level-await": "off",
+ "unicorn/switch-case-braces": "off",
"dot-notation": "error",
"no-implied-eval": "error",
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 000000000..cc73d2088
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,3 @@
+issuehunt: eslint-functional/eslint-plugin-functional
+ko_fi: rebeccastevens
+custom: https://github.com/eslint-functional/eslint-plugin-functional/blob/main/DONATIONS.md
diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md
index 9b5ac9936..991b139de 100644
--- a/.github/ISSUE_TEMPLATE/bug-report.md
+++ b/.github/ISSUE_TEMPLATE/bug-report.md
@@ -6,22 +6,22 @@ labels: 'Type: Bug, Status: Triage'
assignees: ''
---
-# Bug Report
+## Bug Report
-## Expected behavior
+### Expected behavior
-## Actual behavior
+### Actual behavior
-## Steps to reproduce
+### Steps to reproduce
-## Proposed changes
+### Proposed changes
diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md
index 69b6c7a4b..737b02aa2 100644
--- a/.github/ISSUE_TEMPLATE/feature-request.md
+++ b/.github/ISSUE_TEMPLATE/feature-request.md
@@ -6,4 +6,4 @@ labels: 'Type: Idea, Status: Triage'
assignees: ''
---
-# Suggestion
+## Suggestion
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 57535107b..a4e2cba82 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,5 +1,5 @@
Fixes: #(Issue Number)
-# Proposed Changes
+## Proposed Changes
diff --git a/.github/renovate.json b/.github/renovate.json
index 94441f149..6e75937f4 100644
--- a/.github/renovate.json
+++ b/.github/renovate.json
@@ -1,6 +1,10 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
- "extends": ["config:base", "helpers:disableTypesNodeMajor"],
+ "extends": [
+ "config:base",
+ "helpers:disableTypesNodeMajor",
+ "schedule:monthly"
+ ],
"postUpdateOptions": ["yarnDedupeHighest"],
"labels": ["Type: Maintenance", ":blue_heart:"],
"automerge": true,
@@ -24,7 +28,7 @@
{
"matchManagers": ["npm"],
"matchDepTypes": ["devDependencies"],
- "rangeStrategy": "update-lockfile",
+ "rangeStrategy": "pin",
"semanticCommitType": "chore",
"semanticCommitScope": "dev-deps"
},
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 9e17a487c..d89d02f92 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -41,9 +41,6 @@ jobs:
- name: Run Linting Checks
run: yarn lint
- - name: Check spelling
- run: yarn check-spelling
-
test:
name: "Test - Node: ${{ matrix.node_version }} - TS: ${{ matrix.ts_version }} - OS: ${{ matrix.os }}"
needs: pre_job
@@ -54,7 +51,6 @@ jobs:
os:
- "ubuntu-latest"
node_version:
- - "14"
- "16"
- "18"
ts_version:
@@ -107,6 +103,8 @@ jobs:
const filename = "./tsconfig.base.json";
const tsConfig = require(filename);
delete tsConfig.compilerOptions.exactOptionalPropertyTypes;
+ delete tsConfig.compilerOptions.noPropertyAccessFromIndexSignature;
+ delete tsConfig.compilerOptions.noUncheckedIndexedAccess;
const tsConfigString = JSON.stringify(tsConfig, undefined, 2);
fs.writeFileSync(filename, tsConfigString, { encoding: "utf8" });
console.log("TS Config updated successfully.");
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 9093f5640..6ffe70b47 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -24,9 +24,24 @@ jobs:
- name: Install dependencies
run: yarn install --immutable --ignore-scripts
- - name: Build & Verify
+ - name: Build
+ run: yarn build
+
+ - name: Verify
run: yarn verify
+ - name: Install semantic release
+ - run: >
+ npm install --no-save
+ @google/semantic-release-replace-plugin
+ @semantic-release/changelog
+ @semantic-release/commit-analyzer
+ @semantic-release/git
+ @semantic-release/github
+ @semantic-release/npm
+ @semantic-release/release-notes-generator
+ semantic-release
+
- name: Release
run: npx semantic-release
env:
diff --git a/.markdownlint.json b/.markdownlint.json
index f558653eb..76b1102a6 100644
--- a/.markdownlint.json
+++ b/.markdownlint.json
@@ -58,6 +58,7 @@
"allowed_elements": [
"br",
"div",
+ "img",
"sub",
"sup",
"span",
@@ -87,7 +88,7 @@
"MD043": false,
// MD044/proper-names - Proper names should have the correct capitalization
// "MD044": {
- // "names": ["JavaScript", "TypeScript", "TSLint", "ESLint"],
+ // "names": ["JavaScript", "TypeScript"],
// "code_blocks": false
// },
// MD045/no-alt-text - Images should have alternate text (alt text)
diff --git a/.nvmrc b/.nvmrc
new file mode 100644
index 000000000..56bfee434
--- /dev/null
+++ b/.nvmrc
@@ -0,0 +1 @@
+v16.10.0
diff --git a/.nycrc b/.nycrc
index bfb13fde5..77fff9689 100644
--- a/.nycrc
+++ b/.nycrc
@@ -1,16 +1,8 @@
{
"extends": "@istanbuljs/nyc-config-typescript",
- "include": [
- "src/**/*",
- "build/src/**/*"
- ],
- "exclude": [
- "src/util/conditional-imports/**/*"
- ],
- "reporter": [
- "lcov",
- "text"
- ],
+ "include": ["src/**/*", "build/src/**/*"],
+ "exclude": ["src/util/conditional-imports/**/*"],
+ "reporter": ["lcov", "text"],
"check-coverage": false,
"watermarks": {
"lines": [80, 95],
diff --git a/.prettierrc b/.prettierrc
index d59927fd9..1bac4e322 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -1,3 +1,5 @@
{
+ "$schema": "http://json.schemastore.org/prettierrc",
+ "plugins": ["prettier-plugin-packagejson"],
"embeddedLanguageFormatting": "off"
}
diff --git a/.vscode/settings.json b/.vscode/settings.json
index e4e7d2169..e21b79e10 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -22,12 +22,16 @@
".markdownlint.json": "jsonc",
".markdownlintignore": "ignore"
},
+ "[markdown]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "editor.formatOnSave": true
+ },
"[json]": {
- "editor.defaultFormatter": "vscode.json-language-features",
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"[jsonc]": {
- "editor.defaultFormatter": "vscode.json-language-features",
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"[typescript]": {
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 60f6b384e..9540372aa 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -50,7 +50,7 @@ All notable changes to this project will be documented in this file. Dates are d
### Features
-* **no-throw-statement:** add an option to allow throw statements within async functions ([#330](https://github.com/eslint-functional/eslint-plugin-functional/issues/330)) ([7cee76b](https://github.com/eslint-functional/eslint-plugin-functional/commit/7cee76b0baeeea20dc32546c133b35f2dc12e01d))
+* **no-throw-statements:** add an option to allow throw statements within async functions ([#330](https://github.com/eslint-functional/eslint-plugin-functional/issues/330)) ([7cee76b](https://github.com/eslint-functional/eslint-plugin-functional/commit/7cee76b0baeeea20dc32546c133b35f2dc12e01d))
## [4.1.1](https://github.com/eslint-functional/eslint-plugin-functional/compare/v4.1.0...v4.1.1) (2022-01-08)
@@ -126,13 +126,13 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
### Fixed
-- fix(no-conditional-statement): break/continue are no longer treated as returning inside of a switch [`#272`](https://github.com/eslint-functional/eslint-plugin-functional/issues/272)
+- fix(no-conditional-statements): break/continue are no longer treated as returning inside of a switch [`#272`](https://github.com/eslint-functional/eslint-plugin-functional/issues/272)
## [v3.7.1](https://github.com/eslint-functional/eslint-plugin-functional/compare/v3.7.0...v3.7.1) - 2021-09-20
### Fixed
-- fix(no-conditional-statement): branch with break/continue statements now treated as returning branch [`#269`](https://github.com/eslint-functional/eslint-plugin-functional/issues/269)
+- fix(no-conditional-statements): branch with break/continue statements now treated as returning branch [`#269`](https://github.com/eslint-functional/eslint-plugin-functional/issues/269)
## [v3.7.0](https://github.com/eslint-functional/eslint-plugin-functional/compare/v3.6.0...v3.7.0) - 2021-08-28
@@ -150,7 +150,7 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
### Fixed
-- feat(no-expression-statement): add option ignoreVoid [`#71`](https://github.com/eslint-functional/eslint-plugin-functional/issues/71)
+- feat(no-expression-statements): add option ignoreVoid [`#71`](https://github.com/eslint-functional/eslint-plugin-functional/issues/71)
- docs: update tslint migration guide [`#214`](https://github.com/eslint-functional/eslint-plugin-functional/issues/214)
## [v3.5.0](https://github.com/eslint-functional/eslint-plugin-functional/compare/v3.4.1...v3.5.0) - 2021-08-01
@@ -165,7 +165,7 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
### Commits
-- feat(no-conditional-statement): allow switches that exhaust all types [`35a72f1`](https://github.com/eslint-functional/eslint-plugin-functional/commit/35a72f1f9243aa5207851df1b5e5c25f0918e3bc)
+- feat(no-conditional-statements): allow switches that exhaust all types [`35a72f1`](https://github.com/eslint-functional/eslint-plugin-functional/commit/35a72f1f9243aa5207851df1b5e5c25f0918e3bc)
## [v3.4.0](https://github.com/eslint-functional/eslint-plugin-functional/compare/v3.3.0...v3.4.0) - 2021-07-31
@@ -183,7 +183,7 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
### Fixed
-- feat(no-conditional-statement): support never-returning functions for option allowReturningBranches [`#99`](https://github.com/eslint-functional/eslint-plugin-functional/issues/99)
+- feat(no-conditional-statements): support never-returning functions for option allowReturningBranches [`#99`](https://github.com/eslint-functional/eslint-plugin-functional/issues/99)
## [v3.2.2](https://github.com/eslint-functional/eslint-plugin-functional/compare/v3.2.1...v3.2.2) - 2021-07-23
@@ -248,7 +248,7 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
- fix(prefer-readonly-type): add support for mapped types [`#107`](https://github.com/eslint-functional/eslint-plugin-functional/pull/107)
- fix(immutable-data): add support for ignoring exceptions for update expressions [`#108`](https://github.com/eslint-functional/eslint-plugin-functional/pull/108)
- fix(type guard): cast with `as` as the type guard doesn't seem to be working correctly anymore [`#111`](https://github.com/eslint-functional/eslint-plugin-functional/pull/111)
-- improvement(no-mixed-type): rule violations now mark the type as wrong, not members of the type [`#93`](https://github.com/eslint-functional/eslint-plugin-functional/pull/93)
+- improvement(no-mixed-types): rule violations now mark the type as wrong, not members of the type [`#93`](https://github.com/eslint-functional/eslint-plugin-functional/pull/93)
- Reduce use of .reduce() [`#92`](https://github.com/eslint-functional/eslint-plugin-functional/pull/92)
### Fixed
@@ -312,11 +312,11 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
### Merged
-- feat(no-expression-statement): allow specifying directive prologues [`#74`](https://github.com/eslint-functional/eslint-plugin-functional/pull/74)
+- feat(no-expression-statements): allow specifying directive prologues [`#74`](https://github.com/eslint-functional/eslint-plugin-functional/pull/74)
### Fixed
-- fix(no-expression-statement): allow specifying directive prologues [`#68`](https://github.com/eslint-functional/eslint-plugin-functional/issues/68)
+- fix(no-expression-statements): allow specifying directive prologues [`#68`](https://github.com/eslint-functional/eslint-plugin-functional/issues/68)
## [v1.0.1](https://github.com/eslint-functional/eslint-plugin-functional/compare/v1.0.0...v1.0.1) - 2019-12-11
@@ -386,7 +386,7 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
- Refactor out the checkNode function for createRule. [`#48`](https://github.com/eslint-functional/eslint-plugin-functional/pull/48)
- Text matching of MemberExpression nodes now includes the property name [`#47`](https://github.com/eslint-functional/eslint-plugin-functional/pull/47)
- Test configs [`#43`](https://github.com/eslint-functional/eslint-plugin-functional/pull/43)
-- feat(no-mixed-type): no-mixed-interface -> no-mixed-type [`#42`](https://github.com/eslint-functional/eslint-plugin-functional/pull/42)
+- feat(no-mixed-types): no-mixed-interface -> no-mixed-types [`#42`](https://github.com/eslint-functional/eslint-plugin-functional/pull/42)
- feat(configs): Create additional configs for each category of rules. [`#40`](https://github.com/eslint-functional/eslint-plugin-functional/pull/40)
- feat(functional-parameters): Add option to allow iifes [`#39`](https://github.com/eslint-functional/eslint-plugin-functional/pull/39)
- new rule: prefer-type [`#38`](https://github.com/eslint-functional/eslint-plugin-functional/pull/38)
@@ -414,7 +414,7 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
- no-return-void [`#28`](https://github.com/eslint-functional/eslint-plugin-functional/pull/28)
- functional-parameters [`#27`](https://github.com/eslint-functional/eslint-plugin-functional/pull/27)
- feat(readonly-keyword) Add support for parameter properties [`#26`](https://github.com/eslint-functional/eslint-plugin-functional/pull/26)
-- no-conditional-statement [`#23`](https://github.com/eslint-functional/eslint-plugin-functional/pull/23)
+- no-conditional-statements [`#23`](https://github.com/eslint-functional/eslint-plugin-functional/pull/23)
- chore: Remove no-delete rule. [`#21`](https://github.com/eslint-functional/eslint-plugin-functional/pull/21)
- new rule: immutable-data [`#22`](https://github.com/eslint-functional/eslint-plugin-functional/pull/22)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 000000000..da15d9ede
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,23 @@
+# Contributing
+
+## How to
+
+For new features file an issue. For bugs, file an issue and optionally file a PR with a failing test.
+
+## How to develop
+
+To execute the tests run `yarn test`.
+
+To learn about ESLint plugin development see the [relevant section](https://eslint.org/docs/developer-guide/working-with-plugins) of the ESLint docs. You can also checkout the [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint) repo which has some more information specific to TypeScript.
+
+In order to know which AST nodes are created for a snippet of TypeScript code you can use [AST explorer](https://astexplorer.net/) with options JavaScript and @typescript-eslint/parser.
+
+### Commit Messages
+
+> tl;dr: use `npx cz` instead of `git commit`.
+
+Commit messages must follow [Conventional Commit messages guidelines](https://www.conventionalcommits.org/en/v1.0.0/). You can use `npx cz` instead of `git commit` to run an interactive prompt to generate the commit message. We've customize the prompt specifically for this project. For more information see [commitizen](https://github.com/commitizen/cz-cli#readme).
+
+### How to publish
+
+Publishing is handled by [semantic release](https://github.com/semantic-release/semantic-release#readme) - there shouldn't be any need to publish manually.
diff --git a/DONATIONS.md b/DONATIONS.md
new file mode 100644
index 000000000..c7c054584
--- /dev/null
+++ b/DONATIONS.md
@@ -0,0 +1,33 @@
+# Donations
+
+Any donations would be much appreciated. π
+
+## Real money
+
+[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/rebeccastevens)
+
+## Cryptocurrencies
+
+
+ Bitcoin
+
+![bitcoin address QR code](https://raw.githubusercontent.com/RebeccaStevens/RebeccaStevens/main/assets/cryptocurrencies/wallets/bitcoin.png)\
+bc1qgr2xwvkpztsaq9kplud84r3dfz4g3e7d5c5lxm
+
+
+
+
+ Ethereum
+
+![ethereum address QR code](https://raw.githubusercontent.com/RebeccaStevens/RebeccaStevens/main/assets/cryptocurrencies/wallets/ethereum.png)\
+0x643769d1DD2Cb912656dAA27C1b97e5A81EF9fd2
+
+
+
+
+ Litecoin
+
+![litecoin address QR code](https://raw.githubusercontent.com/RebeccaStevens/RebeccaStevens/main/assets/cryptocurrencies/wallets/litecoin.png)\
+ltc1qxr7p6z4hrh87g9mjjk67chyduwrh2nfrpxksjv
+
+
diff --git a/GETTING_STARTED.md b/GETTING_STARTED.md
new file mode 100644
index 000000000..41d75201e
--- /dev/null
+++ b/GETTING_STARTED.md
@@ -0,0 +1,100 @@
+# Getting Started
+
+## Installation
+
+### JavaScript
+
+```sh
+# Install with npm
+npm install -D eslint eslint-plugin-functional
+
+# Install with yarn
+yarn add -D eslint eslint-plugin-functional
+
+# Install with pnpm
+pnpm add -D eslint eslint-plugin-functional
+```
+
+### TypeScript
+
+```sh
+# Install with npm
+npm install -D eslint @typescript-eslint/parser eslint-plugin-functional
+
+# Install with yarn
+yarn add -D eslint @typescript-eslint/parser eslint-plugin-functional
+
+# Install with pnpm
+pnpm add -D eslint @typescript-eslint/parser eslint-plugin-functional
+```
+
+## Usage
+
+Add `functional` to the plugins section of your `.eslintrc` configuration file. Then configure the rules you want to use under the rules section.
+
+```jsonc
+{
+ "plugins": ["functional"],
+ "rules": {
+ "functional/rule-name": "error"
+ }
+}
+```
+
+There are several rulesets provided by this plugin.
+[See below](#rulesets) for what they are and what rules are included in each.
+Enable rulesets via the "extends" property of your `.eslintrc` configuration file.
+
+```jsonc
+{
+ // ...
+ "extends": [
+ "plugin:functional/external-vanilla-recommended",
+ "plugin:functional/recommended",
+ "plugin:functional/stylistic"
+ ]
+}
+```
+
+### With TypeScript
+
+Add `@typescript-eslint/parser` to the "parser" filed in your `.eslintrc` configuration file.
+To use type information, you will need to specify a path to your `tsconfig.json` file in the "project" property of "parserOptions".
+
+```jsonc
+{
+ "parser": "@typescript-eslint/parser",
+ "parserOptions": {
+ "project": "./tsconfig.json"
+ }
+}
+```
+
+See [@typescript-eslint/parser's README.md](https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/parser#readme) for more information on the available parser options.
+
+### Example Config
+
+```jsonc
+{
+ "parser": "@typescript-eslint/parser",
+ "parserOptions": {
+ "project": "tsconfig.json"
+ },
+ "env": {
+ "es6": true
+ },
+ "plugins": [
+ "@typescript-eslint",
+ "functional"
+ ],
+ "extends": [
+ "eslint:recommended",
+ "plugin:@typescript-eslint/eslint-recommended",
+ "plugin:@typescript-eslint/recommended",
+ "plugin:@typescript-eslint/recommended-requiring-type-checking",
+ "plugin:functional/external-typescript-recommended",
+ "plugin:functional/recommended",
+ "plugin:functional/stylistic"
+ ]
+}
+```
diff --git a/README.md b/README.md
index 798121699..8b0c07afa 100644
--- a/README.md
+++ b/README.md
@@ -16,133 +16,13 @@ An [ESLint](http://eslint.org) plugin to disable mutation and promote functional
-> :wave: If you previously used the rules in [tslint-immutable](https://www.npmjs.com/package/tslint-immutable), this package is the ESLint version of those rules. Please see the [migration guide](docs/user-guide/migrating-from-tslint.md) for how to migrate.
+## Donate
-## Features
+[Any donations would be much appreciated](./DONATIONS.md). π
-- [No mutations](#no-mutations)
-- [No object-orientation](#no-object-orientation)
-- [No statements](#no-statements)
-- [No exceptions](#no-exceptions)
-- [Currying](#currying)
-- [Stylistic](#stylistic)
+## Getting Started
-### No mutations
-
-One aim of this project is to leverage the type system in TypeScript to enforce immutability at compile-time while still using regular objects and arrays. Additionally, this project will also aim to support disabling mutability for vanilla JavaScript where possible.
-
-### No object-orientation
-
-JavaScript is multi-paradigm, allowing both object-oriented and functional programming styles. In order to promote a functional style, the object oriented features of JavaScript need to be disabled.
-
-### No statements
-
-In functional programming everything is an expression that produces a value. JavaScript has a lot of syntax that is just statements that does not produce a value. That syntax has to be disabled to promote a functional style.
-
-### No exceptions
-
-Functional programming style does not use run-time exceptions. Instead expressions produces values to indicate errors.
-
-### Currying
-
-JavaScript functions support syntax that is not compatible with curried functions. To enable currying this syntax has to be disabled.
-
-### Stylistic
-
-Enforce code to be written in a more functional style.
-
-## Installation
-
-### JavaScript
-
-```sh
-# Install with npm
-npm install -D eslint eslint-plugin-functional
-
-# Install with yarn
-yarn add -D eslint eslint-plugin-functional
-```
-
-### TypeScript
-
-```sh
-# Install with npm
-npm install -D eslint @typescript-eslint/parser tsutils eslint-plugin-functional
-
-# Install with yarn
-yarn add -D eslint @typescript-eslint/parser tsutils eslint-plugin-functional
-```
-
-## Usage
-
-Add `functional` to the plugins section of your `.eslintrc` configuration file. Then configure the rules you want to use under the rules section.
-
-```jsonc
-{
- "plugins": ["functional"],
- "rules": {
- "functional/rule-name": "error"
- }
-}
-```
-
-There are several rulesets provided by this plugin.
-[See below](#rulesets) for what they are and what rules are including in each.
-Enable rulesets via the "extends" property of your `.eslintrc` configuration file.
-
-```jsonc
-{
- // ...
- "extends": [
- "plugin:functional/external-recommended",
- "plugin:functional/recommended",
- "plugin:functional/stylistic"
- ]
-}
-```
-
-### With TypeScript
-
-Add `@typescript-eslint/parser` to the "parser" filed in your `.eslintrc` configuration file.
-To use type information, you will need to specify a path to your `tsconfig.json` file in the "project" property of "parserOptions".
-
-```jsonc
-{
- "parser": "@typescript-eslint/parser",
- "parserOptions": {
- "project": "./tsconfig.json"
- }
-}
-```
-
-See [@typescript-eslint/parser's README.md](https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/parser#readme) for more information on the available parser options.
-
-### Example Config
-
-```jsonc
-{
- "parser": "@typescript-eslint/parser",
- "parserOptions": {
- "project": "tsconfig.json"
- },
- "env": {
- "es6": true
- },
- "plugins": [
- "@typescript-eslint",
- "functional"
- ],
- "extends": [
- "eslint:recommended",
- "plugin:@typescript-eslint/eslint-recommended",
- "plugin:@typescript-eslint/recommended",
- "plugin:@typescript-eslint/recommended-requiring-type-checking",
- "plugin:functional/external-recommended",
- "plugin:functional/recommended",
- "plugin:functional/stylistic"
- ]
-}
-```
+[See our getting started guide](./GETTING_STARTED.md).
## Rulesets
@@ -150,150 +30,152 @@ The following rulesets are made available by this plugin:
Presets:
-- **Recommended** (plugin:functional/recommended)
-- **Lite** (plugin:functional/lite)
-- **Off** (plugin:functional/off)
-
-Categorized:
-
-- **No Mutations** (plugin:functional/no-mutations)
-- **No Object Orientation** (plugin:functional/no-object-orientation)
-- **No Statements** (plugin:functional/no-statements)
-- **No Exceptions** (plugin:functional/no-exceptions)
-- **Currying** (plugin:functional/currying)
-- **Stylistic** (plugin:functional/stylistic)
-
-Other:
-
-- **All** (plugin:functional/all) - Enables all rules defined in this plugin.
-- **External Recommended** (plugin:functional/external-recommended) - Configures recommended rules not defined by this plugin.
-
-The [below section](#supported-rules) gives details on which rules are enabled by each ruleset.
-
-## Supported Rules
+- **Strict** (`plugin:functional/strict`)\
+ Enforce recommended rules designed to strictly enforce functional programming.
-**Key**:
+- **Recommended** (`plugin:functional/recommended`)\
+ Has the same goal as the `strict` preset but a little more lenient, allowing for functional-like coding styles and nicer integration with non-functional 3rd-party libraries.
-| Symbol | Meaning |
-| :---------------: | -------------------------------------------------------------------------------------------------------------------------------------- |
-| :hear_no_evil: | Ruleset: Lite
This ruleset is designed to enforce a somewhat functional programming code style. |
-| :speak_no_evil: | Ruleset: Recommended
This ruleset is designed to enforce a functional programming code style. |
-| :wrench: | Fixable
Problems found by this rule are potentially fixable with the `--fix` option. |
-| :thought_balloon: | Only Available for TypeScript
The rule either requires Type Information or only works with TypeScript syntax. |
-| :blue_heart: | Works better with TypeScript
Type Information will be used if available making the rule work in more cases. |
+- **Lite** (`plugin:functional/lite`)\
+ Good if you're new to functional programming or are converting a large codebase.
-### No Mutations Rules
-
-:see_no_evil: = `no-mutations` Ruleset.
-
-| Name | Description | :see_no_evil: | :hear_no_evil: | :speak_no_evil: | :wrench: | :blue_heart: |
-| -------------------------------------------------------------- | -------------------------------------------------------------------------- | :---------------------------------------------: | :--------------------------------------: | :----------------------------------------------: | :------: | :---------------: |
-| [`immutable-data`](./docs/rules/immutable-data.md) | Disallow mutating objects and arrays | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | :blue_heart: |
-| [`no-let`](./docs/rules/no-let.md) | Disallow mutable variables | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | |
-| [`no-method-signature`](./docs/rules/no-method-signature.md) | Enforce property signatures with readonly modifiers over method signatures | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | :thought_balloon: |
-| [`prefer-readonly-type`](./docs/rules/prefer-readonly-type.md) | Use readonly types and readonly modifiers where possible | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :wrench: | :thought_balloon: |
-
-### No Object-Orientation Rules
+Categorized:
-:see_no_evil: = `no-object-orientation` Ruleset.
+- **Currying** (`plugin:functional/currying`)\
+ JavaScript functions support syntax that is not compatible with curried functions. To enforce currying, this syntax should be prevented.
-| Name | Description | :see_no_evil: | :hear_no_evil: | :speak_no_evil: | :wrench: | :blue_heart: |
-| ---------------------------------------------------------- | ------------------------------------------------------------------------ | :------------------------------------------------------: | :--------------------------------------: | :----------------------------------------------: | :------: | :---------------: |
-| [`no-class`](./docs/rules/no-class.md) | Disallow classes | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | |
-| [`no-mixed-type`](./docs/rules/no-mixed-type.md) | Restrict types so that only members of the same kind are allowed in them | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | :thought_balloon: |
-| [`no-this-expression`](./docs/rules/no-this-expression.md) | Disallow `this` access | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | |
+- **No Exceptions** (`plugin:functional/no-exceptions`)\
+ Functional programming style does not use run-time exceptions. Instead expressions produces values to indicate errors.
-### No Statements Rules
+- **No Mutations** (`plugin:functional/no-mutations`)\
+ Prevent mutating any data as that's not functional
-:see_no_evil: = `no-statements` Ruleset.
+- **No Other Paradigms** (`plugin:functional/no-other-paradigms`)\
+ JavaScript is multi-paradigm, allowing not only functional, but object-oriented as well as other programming styles. To promote a functional style, prevent the use of other paradigm styles.
-| Name | Description | :see_no_evil: | :hear_no_evil: | :speak_no_evil: | :wrench: | :blue_heart: |
-| ---------------------------------------------------------------------- | ---------------------------------------------------------- | :----------------------------------------------: | :--------------------------------------: | :----------------------------------------------: | :------: | :---------------: |
-| [`no-conditional-statement`](./docs/rules/no-conditional-statement.md) | Disallow conditional statements (if and switch statements) | :heavy_check_mark: | | :heavy_check_mark: | | :thought_balloon: |
-| [`no-expression-statement`](./docs/rules/no-expression-statement.md) | Disallow expressions to cause side-effects | :heavy_check_mark: | | :heavy_check_mark: | | :thought_balloon: |
-| [`no-loop-statement`](./docs/rules/no-loop-statement.md) | Disallow imperative loops | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | |
-| [`no-return-void`](./docs/rules/no-return-void.md) | Disallow functions that return nothing | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | :thought_balloon: |
+- **No Statements** (`plugin:functional/no-statements`)\
+ In functional programming everything is an expression that produces a value. JavaScript has a lot of syntax that is just statements that does not produce a value. That syntax has to be prevented to promote a functional style.
-### No Exceptions Rules
+- **Stylistic** (`plugin:functional/stylistic`)\
+ Enforce code styles that can be considered to be more functional.
-:see_no_evil: = `no-exceptions` Ruleset.
+Other:
-| Name | Description | :see_no_evil: | :hear_no_evil: | :speak_no_evil: | :wrench: | :blue_heart: |
-| ---------------------------------------------------------- | ----------------------------------------------------- | :----------------------------------------------: | :--------------------------------------: | :----------------------------------------------: | :------: | :----------: |
-| [`no-promise-reject`](./docs/rules/no-promise-reject.md) | Disallow rejecting Promises | | | | | |
-| [`no-throw-statement`](./docs/rules/no-throw-statement.md) | Disallow throwing exceptions | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | |
-| [`no-try-statement`](./docs/rules/no-try-statement.md) | Disallow try-catch[-finally] and try-finally patterns | :heavy_check_mark: | | :heavy_check_mark: | | |
+- **All** (`plugin:functional/all`)\
+ Enables all rules defined in this plugin.
-### Currying Rules
+- **Off** (`plugin:functional/off`)\
+ Disable all rules defined in this plugin.
-:see_no_evil: = `currying` Ruleset.
+- **External Vanilla Recommended** (`plugin:functional/external-vanilla-recommended`)\
+ Configures recommended [vanilla ESLint](https://www.npmjs.com/package/eslint) rules.
-| Name | Description | :see_no_evil: | :hear_no_evil: | :speak_no_evil: | :wrench: | :blue_heart: |
-| ---------------------------------------------------------------- | ----------------------------------------- | :-----------------------------------------: | :--------------------------------------: | :----------------------------------------------: | :------: | :----------: |
-| [`functional-parameters`](./docs/rules/functional-parameters.md) | Functions must have functional parameters | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | |
+- **External Typescript Recommended** (`plugin:functional/external-typescript-recommended`)\
+ Configures recommended [TypeScript ESLint](https://www.npmjs.com/package/@typescript-eslint/eslint-plugin) rules. Enabling this ruleset will also enable the vanilla one.
-### Stylistic Rules
+The [below section](#rules) gives details on which rules are enabled by each ruleset.
-:see_no_evil: = `stylistic` Ruleset.
+## Rules
-| Name | Description | :see_no_evil: | :hear_no_evil: | :speak_no_evil: | :wrench: | :blue_heart: |
-| ---------------------------------------------- | ----------------------- | :------------------------------------------: | :--------------------------------------: | :----------------------------------------------: | :------: | :----------: |
-| [`prefer-tacit`](./docs/rules/prefer-tacit.md) | Tacit/Point-Free style. | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :wrench: | :blue_heart: |
+
-## Recommended standard rules
+πΌ Configurations enabled in.\
+β οΈ Configurations set to warn in.\
+π« Configurations disabled in.\
+βοΈ Set in the `lite` configuration.\
+β
Set in the `recommended` configuration.\
+π Set in the `strict` configuration.\
+π¨ Set in the `stylistic` configuration.\
+π§ Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\
+π‘ Manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).\
+β Deprecated.
-In addition to the immutability rules above, there are a few standard rules that need to be enabled to achieve immutability.
+### Currying
-These rules are all included in the _external-recommended_ rulesets.
+| Name | Description | πΌ | β οΈ | π« | π§ | π‘ | β |
+| :----------------------------------------------------------- | :----------------------------- | :--------------------------- | :-- | :-- | :-- | :-- | :-- |
+| [functional-parameters](docs/rules/functional-parameters.md) | Enforce functional parameters. | ![badge-currying][] βοΈ β
π | | | | | |
-### [no-var](https://eslint.org/docs/rules/no-var)
+### No Exceptions
-Without this rule, it is still possible to create `var` variables that are mutable.
+| Name | Description | πΌ | β οΈ | π« | π§ | π‘ | β |
+| :------------------------------------------------------- | :----------------------------------------------------- | :-------------------------------- | :-- | :---- | :-- | :-- | :-- |
+| [no-promise-reject](docs/rules/no-promise-reject.md) | Disallow try-catch[-finally] and try-finally patterns. | | | | | | |
+| [no-throw-statements](docs/rules/no-throw-statements.md) | Disallow throwing exceptions. | βοΈ ![badge-no-exceptions][] β
π | | | | | |
+| [no-try-statements](docs/rules/no-try-statements.md) | Disallow try-catch[-finally] and try-finally patterns. | ![badge-no-exceptions][] π | | βοΈ β
| | | |
-### [no-param-reassign](https://eslint.org/docs/rules/no-param-reassign)
+### No Mutations
-Without this rule, function parameters are mutable.
+| NameΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β | Description | πΌ | β οΈ | π« | π§ | π‘ | β |
+| :--------------------------------------------------------------------------- | :-------------------------------------------------------------- | :------------------------------- | :-- | :-- | :-- | :-- | :-- |
+| [immutable-data](docs/rules/immutable-data.md) | Enforce treating data as immutable. | βοΈ ![badge-no-mutations][] β
π | | | | | |
+| [no-let](docs/rules/no-let.md) | Disallow mutable variables. | βοΈ ![badge-no-mutations][] β
π | | | | | |
+| [prefer-immutable-types](docs/rules/prefer-immutable-types.md) | Require function parameters to be typed as certain immutability | βοΈ ![badge-no-mutations][] β
π | | | π§ | | |
+| [prefer-readonly-type](docs/rules/prefer-readonly-type.md) | Prefer readonly types over mutable types. | | | | π§ | | β |
+| [type-declaration-immutability](docs/rules/type-declaration-immutability.md) | Enforce the immutability of types based on patterns. | βοΈ ![badge-no-mutations][] β
π | | | π§ | | |
-### [prefer-const](https://eslint.org/docs/rules/prefer-const)
+### No Other Paradigms
-This rule is helpful when converting from an imperative code style to a functional one.
+| NameΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β | Description | πΌ | β οΈ | π« | π§ | π‘ | β |
+| :------------------------------------------------------- | :------------------------------------------------------------------------ | :------------------------------------- | :-- | :---- | :-- | :-- | :-- |
+| [no-classes](docs/rules/no-classes.md) | Disallow classes. | βοΈ ![badge-no-other-paradigms][] β
π | | | | | |
+| [no-mixed-types](docs/rules/no-mixed-types.md) | Restrict types so that only members of the same kind are allowed in them. | βοΈ ![badge-no-other-paradigms][] β
π | | | | | |
+| [no-this-expressions](docs/rules/no-this-expressions.md) | Disallow this access. | ![badge-no-other-paradigms][] π | | βοΈ β
| | | |
-### [@typescript-eslint/prefer-readonly](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/prefer-readonly.md)
+### No Statements
-This rule is helpful when working with classes.
+| Name | Description | πΌ | β οΈ | π« | π§ | π‘ | β |
+| :------------------------------------------------------------------- | :--------------------------------------------- | :-------------------------------- | :-- | :-- | :-- | :-- | :-- |
+| [no-conditional-statements](docs/rules/no-conditional-statements.md) | Disallow conditional statements. | ![badge-no-statements][] β
π | | βοΈ | | | |
+| [no-expression-statements](docs/rules/no-expression-statements.md) | Disallow expression statements. | ![badge-no-statements][] β
π | | βοΈ | | | |
+| [no-loop-statements](docs/rules/no-loop-statements.md) | Disallow imperative loops. | βοΈ ![badge-no-statements][] β
π | | | | | |
+| [no-return-void](docs/rules/no-return-void.md) | Disallow functions that don't return anything. | βοΈ ![badge-no-statements][] β
π | | | | | |
-### [@typescript-eslint/prefer-readonly-parameter-types](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/prefer-readonly-parameter-types.md)
+### Stylistic
-Functional functions must not modify any data passed into them.
-This rule marks mutable parameters as a violation as they prevent readonly versions of that data from being passed in.
+| NameΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β | Description | πΌ | β οΈ | π« | π§ | π‘ | β |
+| :--------------------------------------------------------------------- | :--------------------------------------------------------------------- | :-- | :-- | :-- | :-- | :-- | :-- |
+| [prefer-property-signatures](docs/rules/prefer-property-signatures.md) | Prefer property signatures over method signatures. | π¨ | | | | | |
+| [prefer-tacit](docs/rules/prefer-tacit.md) | Replaces `x => f(x)` with just `f`. | | π¨ | | | π‘ | |
+| [readonly-type](docs/rules/readonly-type.md) | Require consistently using either `readonly` keywords or `Readonly` | π¨ | | | π§ | | |
-However, due to many 3rd-party libraries only providing mutable versions of their types, often it can not be easy to satisfy this rule. Thus by default we only enable this rule with the "warn" severity rather than "error".
+
-### [@typescript-eslint/switch-exhaustiveness-check](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.md)
+[badge-currying]: https://img.shields.io/badge/-currying-red.svg
+[badge-lite]: https://img.shields.io/badge/-lite-green.svg
+[badge-no-exceptions]: https://img.shields.io/badge/-no--exceptions-blue.svg
+[badge-no-mutations]: https://img.shields.io/badge/-no--mutations-orange.svg
+[badge-no-other-paradigms]: https://img.shields.io/badge/-no--other--paradigms-yellow.svg
+[badge-no-statements]: https://img.shields.io/badge/-no--statements-purple.svg
-Although our [no-conditional-statement](./docs/rules/no-conditional-statement.md) rule also performs this check, this rule has a fixer that will implement the unimplemented cases which can be useful.
+## External Recommended Rules
-## How to contribute
+In addition to the above rules, there are a few other rules we recommended.
-For new features file an issue. For bugs, file an issue and optionally file a PR with a failing test.
+These rules are what are included in the _external recommended_ rulesets.
-## How to develop
+### Vanilla Rules
-To execute the tests run `yarn test`.
+- [no-var](https://eslint.org/docs/rules/no-var)\
+ Without this rule, it is still possible to create `var` variables that are mutable.
-To learn about ESLint plugin development see the [relevant section](https://eslint.org/docs/developer-guide/working-with-plugins) of the ESLint docs. You can also checkout the [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint) repo which has some more information specific to TypeScript.
+- [no-param-reassign](https://eslint.org/docs/rules/no-param-reassign)\
+ Don't allow function parameters to be reassigned, they should be treated as constants.
-In order to know which AST nodes are created for a snippet of TypeScript code you can use [AST explorer](https://astexplorer.net/) with options JavaScript and @typescript-eslint/parser.
+- [prefer-const](https://eslint.org/docs/rules/prefer-const)\
+ This rule provides a helpful fixer when converting from an imperative code style to a functional one.
-### Commit Messages
+### Typescript Rules
-> tl;dr: use `npx cz` instead of `git commit`.
+- [@typescript-eslint/prefer-readonly](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/prefer-readonly.md)\
+ This rule is helpful when working with classes.
-Commit messages must follow [Conventional Commit messages guidelines](https://www.conventionalcommits.org/en/v1.0.0/). You can use `npx cz` instead of `git commit` to run a interactive prompt to generate the commit message. We've customize the prompt specifically for this project. For more information see [commitizen](https://github.com/commitizen/cz-cli#readme).
+- [@typescript-eslint/switch-exhaustiveness-check](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.md)\
+ Although our [no-conditional-statements](./docs/rules/no-conditional-statements.md) rule also performs this check, this rule has a fixer that will implement the unimplemented cases which can be useful.
-### How to publish
+## Contributing
-Publishing is handled by [semantic release](https://github.com/semantic-release/semantic-release#readme) - there shouldn't be any need to publish manually.
+[See our contributing guide](./CONTRIBUTING.md).
## Prior work
diff --git a/ava.config.ts b/ava.config.ts
index 1ee9827b8..aa45de253 100644
--- a/ava.config.ts
+++ b/ava.config.ts
@@ -11,9 +11,9 @@ function getBoolean(value: unknown) {
: Boolean(asNumber);
}
-const testAllFiles = getBoolean(process.env.TEST_ALL_FILES);
-const useCompiledTests = getBoolean(process.env.USE_COMPILED_TESTS);
-const onlyTestWorkFile = getBoolean(process.env.ONLY_TEST_WORK_FILE);
+const testAllFiles = getBoolean(process.env["TEST_ALL_FILES"]);
+const useCompiledTests = getBoolean(process.env["USE_COMPILED_TESTS"]);
+const onlyTestWorkFile = getBoolean(process.env["ONLY_TEST_WORK_FILE"]);
const avaCommonConfig = {
files: testAllFiles
diff --git a/cz-adapter/engine.ts b/cz-adapter/engine.ts
index df23c631e..7dda50bd9 100644
--- a/cz-adapter/engine.ts
+++ b/cz-adapter/engine.ts
@@ -191,7 +191,7 @@ function doCommit(
const scopeValue = answers.scope ?? answers.scopeRules ?? "";
const scope = scopeValue.length > 0 ? `(${scopeValue})` : "";
// Hard limit is applied by the validate.
- const head = `${answers.type + breakingMarker + scope}: ${answers.subject}`;
+ const head = `${answers.type + scope + breakingMarker}: ${answers.subject}`;
const bodyValue = (answers.body ?? "").trim();
const bodyValueWithBreaking =
@@ -224,7 +224,7 @@ function doCommit(
/**
* Filter out falsy values from the given array.
*/
-function arrayFilterFalsy(array: ReadonlyArray) {
+function arrayFilterFalsy(array: T[]) {
return array.filter(Boolean);
}
@@ -287,7 +287,7 @@ function filterSubject(options: Options) {
m_subject.charAt(0).toLowerCase() +
m_subject.slice(1, m_subject.length);
}
- // eslint-disable-next-line functional/no-loop-statement
+ // eslint-disable-next-line functional/no-loop-statements
while (m_subject.endsWith(".")) {
m_subject = m_subject.slice(0, -1);
}
diff --git a/cz-adapter/options.ts b/cz-adapter/options.ts
index d788c6e49..1e9356ec5 100644
--- a/cz-adapter/options.ts
+++ b/cz-adapter/options.ts
@@ -1,5 +1,4 @@
import { types as conventionalCommitTypes } from "conventional-commit-types";
-import type { ReadonlyDeep } from "type-fest";
// Override the descriptions of some of the types.
const types = {
@@ -56,11 +55,11 @@ const defaults: Readonly<{
defaultBody: string | undefined;
defaultIssues: string | undefined;
}> = {
- defaultType: process.env.CZ_TYPE,
- defaultScope: process.env.CZ_SCOPE,
- defaultSubject: process.env.CZ_SUBJECT,
- defaultBody: process.env.CZ_BODY,
- defaultIssues: process.env.CZ_ISSUES,
+ defaultType: process.env["CZ_TYPE"],
+ defaultScope: process.env["CZ_SCOPE"],
+ defaultSubject: process.env["CZ_SUBJECT"],
+ defaultBody: process.env["CZ_BODY"],
+ defaultIssues: process.env["CZ_ISSUES"],
};
const options = {
@@ -72,6 +71,6 @@ const options = {
maxLineWidth: 100,
};
-export type Options = ReadonlyDeep;
+export type Options = typeof options;
export default options;
diff --git a/docs/rules/functional-parameters.md b/docs/rules/functional-parameters.md
index a3467b60a..ae17ac66b 100644
--- a/docs/rules/functional-parameters.md
+++ b/docs/rules/functional-parameters.md
@@ -1,6 +1,10 @@
-# Enforce functional parameters (functional-parameters)
+# Enforce functional parameters (`functional/functional-parameters`)
-Disallow use of rest parameters, the `arguments` keyword and enforces that functions take a least 1 parameter.
+πΌ This rule is enabled in the following configs: `currying`, βοΈ `lite`, β
`recommended`, π `strict`.
+
+
+
+Disallow use of rest parameters, the `arguments` keyword and enforces that functions take at least 1 parameter.
## Rule Details
@@ -11,7 +15,7 @@ When it comes to functional programming, known and explicit parameters must be u
Note: With an unknown number of parameters, currying functions is a lot more difficult/impossible.
-Examples of **incorrect** code for this rule:
+### β Incorrect
@@ -33,7 +37,7 @@ function add(...numbers) {
}
```
-Examples of **correct** code for this rule:
+### β
Correct
```js
/* eslint functional/functional-parameters: "error" */
@@ -51,16 +55,21 @@ This rule accepts an options object of the following type:
type Options = {
allowRestParameter: boolean;
allowArgumentsKeyword: boolean;
- enforceParameterCount: "atLeastOne" | "exactlyOne" | false | {
- count: "atLeastOne" | "exactlyOne";
- ignoreIIFE: boolean;
- };
+ enforceParameterCount:
+ | "atLeastOne"
+ | "exactlyOne"
+ | false
+ | {
+ count: "atLeastOne" | "exactlyOne";
+ ignoreLambdaExpression: boolean;
+ ignoreIIFE: boolean;
+ };
ignorePattern?: string[] | string;
ignorePrefixSelector?: string[] | string;
-}
+};
```
-The default options:
+### Default Options
```ts
const defaults = {
@@ -68,19 +77,31 @@ const defaults = {
allowArgumentsKeyword: false,
enforceParameterCount: {
count: "atLeastOne",
- ignoreIIFE: true
- }
-}
+ ignoreLambdaExpression: false,
+ ignoreIIFE: true,
+ },
+};
```
-Note: the `lite` ruleset overrides the default options to:
+### Preset Overrides
+
+#### `recommended`
```ts
-const liteDefaults = {
- allowRestParameter: false,
- allowArgumentsKeyword: false,
- enforceParameterCount: false
-}
+const recommendedOptions = {
+ enforceParameterCount: {
+ ignoreLambdaExpression: true,
+ ignoreIIFE: true,
+ },
+};
+```
+
+#### `lite`
+
+```ts
+const liteOptions = {
+ enforceParameterCount: false,
+};
```
### `allowRestParameter`
@@ -109,7 +130,7 @@ There's not much point of having a function that doesn't take any parameters in
Require all functions to have exactly one parameter.
-Any function that take takes multiple parameter can be rewritten as a higher-order function that only takes one.
+Any function that takes multiple parameter can be rewritten as a higher-order function that only takes one.
Example:
@@ -133,6 +154,11 @@ See [Currying](https://en.wikipedia.org/wiki/Currying) and [Higher-order functio
See [enforceParameterCount](#enforceparametercount).
+### `enforceParameterCount.ignoreLambdaExpression`
+
+If true, this option allows for the use of lambda function expressions that do not have any parameters.
+Here, a lambda function expression refers to any function being defined in place as passed directly as an argument to another function.
+
#### `enforceParameterCount.ignoreIIFE`
If true, this option allows for the use of [IIFEs](https://developer.mozilla.org/en-US/docs/Glossary/IIFE) that do not have any parameters.
@@ -156,10 +182,7 @@ With the following config:
The following inline callback won't be flagged:
```js
-const sum = [1, 2, 3].reduce(
- (carry, current) => current,
- 0
-);
+const sum = [1, 2, 3].reduce((carry, current) => current, 0);
```
### `ignorePattern`
diff --git a/docs/rules/immutable-data.md b/docs/rules/immutable-data.md
index 05c321231..510854bcd 100644
--- a/docs/rules/immutable-data.md
+++ b/docs/rules/immutable-data.md
@@ -1,4 +1,8 @@
-# Disallow mutating objects and arrays (immutable-data)
+# Enforce treating data as immutable (`functional/immutable-data`)
+
+πΌ This rule is enabled in the following configs: βοΈ `lite`, `no-mutations`, β
`recommended`, π `strict`.
+
+
This rule prohibits syntax that mutates existing objects and arrays via assignment to or deletion of their properties/elements.
@@ -7,7 +11,7 @@ This rule prohibits syntax that mutates existing objects and arrays via assignme
While requiring the `readonly` modifier forces declared types to be immutable,
it won't stop assignment into or modification of untyped objects or external types declared under different rules.
-Examples of **incorrect** code for this rule:
+### β Incorrect
@@ -35,7 +39,7 @@ delete arr[1]; // <- Modifying an existing array is not allowed.
arr.push(3); // <- Modifying an array is not allowed.
```
-Examples of **correct** code for this rule:
+### β
Correct
```js
/* eslint functional/immutable-data: "error" */
@@ -45,10 +49,8 @@ const arr = [0, 1, 2];
const x = {
...obj,
- bar: [
- ...arr, 3, 4
- ]
-}
+ bar: [...arr, 3, 4],
+};
```
## Options
@@ -62,37 +64,37 @@ type Options = {
| {
forArrays: boolean;
forObjects: boolean;
- }
- ignoreClass: boolean | "fieldsOnly";
+ };
+ ignoreClasses: boolean | "fieldsOnly";
ignoreImmediateMutation: boolean;
ignorePattern?: string[] | string;
ignoreAccessorPattern?: string[] | string;
-}
+};
```
-The default options:
+### Default Options
```ts
type Options = {
assumeTypes: true;
- ignoreClass: false;
+ ignoreClasses: false;
ignoreImmediateMutation: true;
};
```
-Note: the `lite` ruleset overrides the default options to:
+### Preset Overrides
+
+#### `lite`
```ts
-const defaults = {
- assumeTypes: true,
- ignoreClass: "fieldsOnly",
- ignoreImmediateMutation: true,
-}
+const liteOptions = {
+ ignoreClasses: "fieldsOnly",
+};
```
### `assumeTypes`
-The rule take advantage of TypeScript's typing engine to check if mutation is taking place.
+The rule takes advantage of TypeScript's typing engine to check if mutation is taking place.
If you are not using TypeScript, type checking cannot be performed; hence this option exists.
This option will make the rule assume the type of the nodes it is checking are of type Array/Object.
@@ -121,7 +123,7 @@ const original = ["foo", "bar", "baz"];
const sorted = [...original].sort((a, b) => a.localeCompare(b)); // This is OK with ignoreImmediateMutation.
```
-### `ignoreClass`
+### `ignoreClasses`
Ignore mutations inside classes.
diff --git a/docs/rules/no-class.md b/docs/rules/no-classes.md
similarity index 81%
rename from docs/rules/no-class.md
rename to docs/rules/no-classes.md
index 81be12635..56785260e 100644
--- a/docs/rules/no-class.md
+++ b/docs/rules/no-classes.md
@@ -1,15 +1,19 @@
-# Disallow classes (no-class)
+# Disallow classes (`functional/no-classes`)
+
+πΌ This rule is enabled in the following configs: βοΈ `lite`, `no-other-paradigms`, β
`recommended`, π `strict`.
+
+
Disallow use of the `class` keyword.
## Rule Details
-Examples of **incorrect** code for this rule:
+### β Incorrect
```js
-/* eslint functional/no-class: "error" */
+/* eslint functional/no-classes: "error" */
class Dog {
constructor(name, age) {
@@ -27,10 +31,10 @@ const dogA = new Dog("Jasper", 2);
console.log(`${dogA.name} is ${dogA.ageInDogYears} in dog years.`);
```
-Examples of **correct** code for this rule:
+### β
Correct
```js
-/* eslint functional/no-class: "error" */
+/* eslint functional/no-classes: "error" */
function getAgeInDogYears(age) {
return 7 * age;
@@ -46,7 +50,7 @@ console.log(`${dogA.name} is ${getAgeInDogYears(dogA.age)} in dog years.`);
### React Examples
-Thanks to libraries like [recompose](https://github.com/acdlite/recompose) and Redux's [React Container components](http://redux.js.org/docs/basics/UsageWithReact.html), there's not much reason to build Components using `React.createClass` or ES6 classes anymore. The `no-this-expression` rule makes this explicit.
+Thanks to libraries like [recompose](https://github.com/acdlite/recompose) and Redux's [React Container components](http://redux.js.org/docs/basics/UsageWithReact.html), there's not much reason to build Components using `React.createClass` or ES6 classes anymore. The `no-this-expressions` rule makes this explicit.
```js
const Message = React.createClass({
@@ -62,7 +66,7 @@ Instead of creating classes, you should use React 0.14's [Stateless Functional C
const Message = ({ message }) => {message}
;
```
-What about lifecycle methods like `shouldComponentUpdate`? We can use the [recompose](https://github.com/acdlite/recompose) library to apply these optimizations to your Stateless Functional Components. The [recompose](https://github.com/acdlite/recompose) library relies on the fact that your Redux state is immutable to efficiently implement shouldComponentUpdate for you.
+What about lifecycle methods like `shouldComponentUpdate`? We can use the [recompose](https://github.com/acdlite/recompose) library to apply these optimizations to your Stateless Functional Components. The [recompose](https://github.com/acdlite/recompose) library relies on the fact that your Redux state is immutable to efficiently implement `shouldComponentUpdate` for you.
```js
import { pure, onlyUpdateForKeys } from "recompose";
@@ -76,7 +80,3 @@ const OptimizedMessage = pure(Message);
// Even more optimized: only updates if specific prop keys have changed
const HyperOptimizedMessage = onlyUpdateForKeys(["message"], Message);
```
-
-## Options
-
-The rule does not accept any options.
diff --git a/docs/rules/no-conditional-statement.md b/docs/rules/no-conditional-statements.md
similarity index 72%
rename from docs/rules/no-conditional-statement.md
rename to docs/rules/no-conditional-statements.md
index 6af26973c..2228cd430 100644
--- a/docs/rules/no-conditional-statement.md
+++ b/docs/rules/no-conditional-statements.md
@@ -1,4 +1,8 @@
-# Disallow conditional statements (no-conditional-statement)
+# Disallow conditional statements (`functional/no-conditional-statements`)
+
+πΌπ« This rule is enabled in the following configs: `no-statements`, β
`recommended`, π `strict`. This rule is _disabled_ in the βοΈ `lite` config.
+
+
This rule disallows conditional statements such as `if` and `switch`.
@@ -9,12 +13,12 @@ Instead consider using the [ternary operator](https://developer.mozilla.org/en-U
For more background see this [blog post](https://hackernoon.com/rethinking-javascript-the-if-statement-b158a61cd6cb) and discussion in [tslint-immutable #54](https://github.com/jonaskello/tslint-immutable/issues/54).
-Examples of **incorrect** code for this rule:
+### β Incorrect
```js
-/* eslint functional/no-conditional-statement: "error" */
+/* eslint functional/no-conditional-statements: "error" */
let x;
if (i === 1) {
@@ -24,25 +28,23 @@ if (i === 1) {
}
```
-Examples of **correct** code for this rule:
+### β
Correct
```js
-/* eslint functional/no-conditional-statement: "error" */
+/* eslint functional/no-conditional-statements: "error" */
const x = i === 1 ? 2 : 3;
```
```js
-/* eslint functional/no-conditional-statement: "error" */
+/* eslint functional/no-conditional-statements: "error" */
function foo(x, y) {
- return (
- x === y // if
+ return x === y // if
? 0
- : x > y // else if
+ : x > y // else if
? 1
- : -1 // else
- );
+ : -1; // else
}
```
@@ -53,15 +55,25 @@ This rule accepts an options object of the following type:
```ts
type Options = {
allowReturningBranches: boolean | "ifExhaustive";
-}
+};
```
-The default options:
+### Default Options
```ts
const defaults = {
- allowReturningBranches: false
-}
+ allowReturningBranches: false,
+};
+```
+
+### Preset Overrides
+
+#### `recommended` and `lite`
+
+```ts
+const recommendedAndLiteOptions = {
+ allowReturningBranches: true,
+};
```
### `allowReturningBranches`
@@ -89,7 +101,7 @@ This allows conditional statements to be used like [do expressions](https://gith
```js
const x = (() => {
- switch(y) {
+ switch (y) {
case "a":
return 1;
case "b":
diff --git a/docs/rules/no-expression-statement.md b/docs/rules/no-expression-statements.md
similarity index 58%
rename from docs/rules/no-expression-statement.md
rename to docs/rules/no-expression-statements.md
index 7773e9105..6fedf753a 100644
--- a/docs/rules/no-expression-statement.md
+++ b/docs/rules/no-expression-statements.md
@@ -1,4 +1,8 @@
-# Using expressions to cause side-effects not allowed (no-expression-statement)
+# Disallow expression statements (`functional/no-expression-statements`)
+
+πΌπ« This rule is enabled in the following configs: `no-statements`, β
`recommended`, π `strict`. This rule is _disabled_ in the βοΈ `lite` config.
+
+
This rule checks that the value of an expression is assigned to a variable and thus helps promote side-effect free (pure) functions.
@@ -6,12 +10,12 @@ This rule checks that the value of an expression is assigned to a variable and t
When you call a function and donβt use itβs return value, chances are high that it is being called for its side effect. e.g.
-Examples of **incorrect** code for this rule:
+### β Incorrect
```js
-/* eslint functional/no-expression-statement: "error" */
+/* eslint functional/no-expression-statements: "error" */
console.log("Hello world!");
```
@@ -19,7 +23,7 @@ console.log("Hello world!");
```js
-/* eslint functional/no-expression-statement: "error" */
+/* eslint functional/no-expression-statements: "error" */
array.push(3);
```
@@ -27,15 +31,15 @@ array.push(3);
```js
-/* eslint functional/no-expression-statement: "error" */
+/* eslint functional/no-expression-statements: "error" */
foo(bar);
```
-Examples of **correct** code for this rule:
+### β
Correct
```js
-/* eslint functional/no-expression-statement: "error" */
+/* eslint functional/no-expression-statements: "error" */
const baz = foo(bar);
```
@@ -43,7 +47,7 @@ const baz = foo(bar);
```js
-/* eslint functional/no-expression-statement: ["error", { "ignoreVoid": true }] */
+/* eslint functional/no-expression-statements: ["error", { "ignoreVoid": true }] */
console.log("hello world");
```
@@ -55,16 +59,16 @@ This rule accepts an options object of the following type:
```ts
type Options = {
ignorePattern?: string[] | string;
- ignoreVoid?: boolean
-}
+ ignoreVoid?: boolean;
+};
```
-The default options:
+### Default Options
```ts
const defaults = {
- ignoreVoid: false
-}
+ ignoreVoid: false,
+};
```
### `ignoreVoid`
diff --git a/docs/rules/no-let.md b/docs/rules/no-let.md
index c13aff50b..2149fd748 100644
--- a/docs/rules/no-let.md
+++ b/docs/rules/no-let.md
@@ -1,4 +1,8 @@
-# Disallow mutable variables (no-let)
+# Disallow mutable variables (`functional/no-let`)
+
+πΌ This rule is enabled in the following configs: βοΈ `lite`, `no-mutations`, β
`recommended`, π `strict`.
+
+
This rule should be combined with ESLint's built-in `no-var` rule to enforce that all variables are declared as `const`.
@@ -6,7 +10,7 @@ This rule should be combined with ESLint's built-in `no-var` rule to enforce tha
In functional programming variables should not be mutable; use `const` instead.
-Examples of **incorrect** code for this rule:
+### β Incorrect
@@ -21,11 +25,10 @@ let x = 5;
```js
/* eslint functional/no-let: "error" */
-for (let i = 0; i < array.length; i++) {
-}
+for (let i = 0; i < array.length; i++) {}
```
-Examples of **correct** code for this rule:
+### β
Correct
```js
/* eslint functional/no-let: "error" */
@@ -53,36 +56,45 @@ This rule accepts an options object of the following type:
```ts
type Options = {
- allowLocalMutation: boolean;
+ allowInFunctions: boolean;
ignorePattern?: string[] | string;
-}
+};
```
-The default options:
+### Default Options
```ts
const defaults = {
allowInForLoopInit: false,
- allowLocalMutation: false
-}
+ allowInFunctions: false,
+};
+```
+
+### Preset Overrides
+
+#### `recommended` and `lite`
+
+```ts
+const recommendedAndLiteOptions = {
+ allowInForLoopInit: true,
+};
```
### `allowInForLoopInit`
If set, `let`s inside of for a loop initializer are allowed. This does not include for...of or for...in loops as they should use `const` instead.
-Examples of **correct** code for this rule:
+#### β
Correct
```js
/* eslint functional/no-let: ["error", { "allowInForLoopInit": true } ] */
-for (let i = 0; i < array.length; i++) {
-}
+for (let i = 0; i < array.length; i++) {}
```
-Examples of **incorrect** code for this rule:
+#### β Incorrect
@@ -102,9 +114,11 @@ for (let [index, element] of array.entries()) {
}
```
-### `allowLocalMutation`
+### `allowInFunctions`
+
+If true, the rule will not flag any statements that are inside of function bodies.
-See the [allowLocalMutation](./options/allow-local-mutation.md) docs.
+See the [allowLocalMutation](./options/allow-local-mutation.md) docs for more information.
### `ignorePattern`
diff --git a/docs/rules/no-loop-statement.md b/docs/rules/no-loop-statements.md
similarity index 69%
rename from docs/rules/no-loop-statement.md
rename to docs/rules/no-loop-statements.md
index 309b25eeb..d36ed693a 100644
--- a/docs/rules/no-loop-statement.md
+++ b/docs/rules/no-loop-statements.md
@@ -1,4 +1,8 @@
-# Disallow imperative loops (no-loop-statement)
+# Disallow imperative loops (`functional/no-loop-statements`)
+
+πΌ This rule is enabled in the following configs: βοΈ `lite`, `no-statements`, β
`recommended`, π `strict`.
+
+
This rule disallows for loop statements, including `for`, `for...of`, `for...in`, `while`, and `do...while`.
@@ -9,12 +13,12 @@ Loops in JavaScript are statements so they are not a good fit for a functional p
Instead consider using `map`, `reduce` or similar.
For more background see this [blog post](https://hackernoon.com/rethinking-javascript-death-of-the-for-loop-c431564c84a8) and discussion in [tslint-immutable #54](https://github.com/jonaskello/tslint-immutable/issues/54).
-Examples of **incorrect** code for this rule:
+### β Incorrect
```js
-/* eslint functional/no-loop-statement: "error" */
+/* eslint functional/no-loop-statements: "error" */
const numbers = [1, 2, 3];
const double = [];
@@ -26,7 +30,7 @@ for (let i = 0; i < numbers.length; i++) {
```js
-/* eslint functional/no-loop-statement: "error" */
+/* eslint functional/no-loop-statements: "error" */
const numbers = [1, 2, 3];
let sum = 0;
@@ -35,21 +39,17 @@ for (const number of numbers) {
}
```
-Examples of **correct** code for this rule:
+### β
Correct
```js
-/* eslint functional/no-loop-statement: "error" */
+/* eslint functional/no-loop-statements: "error" */
const numbers = [1, 2, 3];
const double = numbers.map((n) => n * 2);
```
```js
-/* eslint functional/no-loop-statement: "error" */
+/* eslint functional/no-loop-statements: "error" */
const numbers = [1, 2, 3];
const sum = numbers.reduce((carry, number) => carry + number, 0);
```
-
-## Options
-
-The rule does not accept any options.
diff --git a/docs/rules/no-method-signature.md b/docs/rules/no-method-signature.md
deleted file mode 100644
index 19967f87d..000000000
--- a/docs/rules/no-method-signature.md
+++ /dev/null
@@ -1,74 +0,0 @@
-# Prefer property signatures with readonly modifiers over method signatures (no-method-signature)
-
-Prefer property signatures with readonly modifiers over method signatures.
-
-## Rule Details
-
-There are two ways function members can be declared in interfaces and type aliases; `MethodSignature` and `PropertySignature`.
-
-The `MethodSignature` and the `PropertySignature` forms seem equivalent, but only the `PropertySignature` form can have a `readonly` modifier.
-Because of this any `MethodSignature` will be mutable unless wrapped in the `Readonly` type.
-
-It should be noted however that the `PropertySignature` form for declaring functions does not support overloading.
-
-Examples of **incorrect** code for this rule:
-
-
-
-```ts
-/* eslint functional/no-method-signature: "error" */
-
-type Foo = {
- bar(): string;
-};
-```
-
-Examples of **correct** code for this rule:
-
-
-
-```ts
-/* eslint functional/no-method-signature: "error" */
-
-type Foo = {
- readonly bar: () => string;
-};
-
-type Foo = Readonly<{
- bar(): string;
-}>;
-```
-
-## Options
-
-This rule accepts an options object of the following type:
-
-```ts
-type Options = {
- ignoreIfReadonly: boolean;
-}
-```
-
-The default options:
-
-```ts
-const defaults = {
- ignoreIfReadonly: true
-}
-```
-
-### `ignoreIfReadonly`
-
-If set to `false`, this option allows for the use of method signatures if they are wrapped in the `Readonly` type.
-
-Examples of **incorrect** code for this rule:
-
-
-
-```ts
-/* eslint functional/no-method-signature: ["error", { "ignoreIfReadonly": false } ] */
-
-type Foo = Readonly<{
- bar(): string;
-}>;
-```
diff --git a/docs/rules/no-mixed-type.md b/docs/rules/no-mixed-types.md
similarity index 67%
rename from docs/rules/no-mixed-type.md
rename to docs/rules/no-mixed-types.md
index 88ef13012..a291c3200 100644
--- a/docs/rules/no-mixed-type.md
+++ b/docs/rules/no-mixed-types.md
@@ -1,4 +1,8 @@
-# Restrict types so that only members of the same kind of are allowed in them (no-mixed-type)
+# Restrict types so that only members of the same kind are allowed in them (`functional/no-mixed-types`)
+
+πΌ This rule is enabled in the following configs: βοΈ `lite`, `no-other-paradigms`, β
`recommended`, π `strict`.
+
+
This rule enforces that an aliased type literal or an interface only has one type of members, eg. only data properties or only functions.
@@ -6,12 +10,12 @@ This rule enforces that an aliased type literal or an interface only has one typ
Mixing functions and data properties in the same type is a sign of object-orientation style.
-Examples of **incorrect** code for this rule:
+### β Incorrect
```ts
-/* eslint functional/no-mixed-type: "error" */
+/* eslint functional/no-mixed-types: "error" */
type Foo = {
prop1: string;
@@ -19,10 +23,10 @@ type Foo = {
};
```
-Examples of **correct** code for this rule:
+### β
Correct
```ts
-/* eslint functional/no-mixed-type: "error" */
+/* eslint functional/no-mixed-types: "error" */
type Foo = {
prop1: string;
@@ -31,7 +35,7 @@ type Foo = {
```
```ts
-/* eslint functional/no-mixed-type: "error" */
+/* eslint functional/no-mixed-types: "error" */
type Foo = {
prop1: () => string;
@@ -52,16 +56,16 @@ This rule accepts an options object of the following type:
type Options = {
checkInterfaces: boolean;
checkTypeLiterals: boolean;
-}
+};
```
-The default options:
+### Default Options
```ts
const defaults = {
checkInterfaces: true,
- checkTypeLiterals: true
-}
+ checkTypeLiterals: true,
+};
```
### checkInterfaces
diff --git a/docs/rules/no-promise-reject.md b/docs/rules/no-promise-reject.md
index 45641fc21..7a06feb39 100644
--- a/docs/rules/no-promise-reject.md
+++ b/docs/rules/no-promise-reject.md
@@ -1,4 +1,6 @@
-# Disallow rejecting Promises (no-promise-reject)
+# Disallow try-catch[-finally] and try-finally patterns (`functional/no-promise-reject`)
+
+
This rule disallows use of `Promise.reject()`.
@@ -10,7 +12,7 @@ You can view a `Promise` as a result object with built-in error (something like
You can also view a rejected promise as something similar to an exception and as such something that does not fit with functional programming.
If your view is the latter you can use the `no-promise-reject` rule to disallow rejected promises.
-Examples of **incorrect** code for this rule:
+### β Incorrect
@@ -26,7 +28,7 @@ async function divide(x, y) {
}
```
-Examples of **correct** code for this rule:
+### β
Correct
```js
/* eslint functional/no-promise-reject: "error" */
@@ -34,12 +36,6 @@ Examples of **correct** code for this rule:
async function divide(x, y) {
const [xv, yv] = await Promise.all([x, y]);
- return yv === 0
- ? new Error("Cannot divide by zero.")
- : xv / yv;
+ return yv === 0 ? new Error("Cannot divide by zero.") : xv / yv;
}
```
-
-## Options
-
-The rule does not accept any options.
diff --git a/docs/rules/no-return-void.md b/docs/rules/no-return-void.md
index e80f4e0ad..9ddd568c6 100644
--- a/docs/rules/no-return-void.md
+++ b/docs/rules/no-return-void.md
@@ -1,4 +1,8 @@
-# Disallow returning nothing (no-return-void)
+# Disallow functions that don't return anything (`functional/no-return-void`)
+
+πΌ This rule is enabled in the following configs: βοΈ `lite`, `no-statements`, β
`recommended`, π `strict`.
+
+
Disallow functions that are declared as returning nothing.
@@ -11,26 +15,22 @@ By default, this rule allows function to return `undefined` and `null`.
Note: For performance reasons, this rule does not check implicit return types.
We recommend using the rule [@typescript-eslint/explicit-function-return-type](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/explicit-function-return-type.md) in conjunction with this rule.
-Examples of **incorrect** code for this rule:
+### β Incorrect
```ts
/* eslint functional/no-return-void: "error" */
-function updateText(): void {
-
-}
+function updateText(): void {}
```
-Examples of **correct** code for this rule:
+### β
Correct
```ts
/* eslint functional/no-return-void: "error" */
-function updateText(value: string): string {
-
-}
+function updateText(value: string): string {}
```
## Options
@@ -41,28 +41,28 @@ This rule accepts an options object of the following type:
type Options = {
allowNull: boolean;
allowUndefined: boolean;
- ignoreImplicit: boolean;
-}
+ ignoreInferredTypes: boolean;
+};
```
-The default options:
+### Default Options
```ts
const defaults = {
allowNull: true,
allowUndefined: true,
- ignoreImplicit: false,
-}
+ ignoreInferredTypes: false,
+};
```
-### allowNull
+### `allowNull`
If true allow returning null.
-### allowUndefined
+### `allowUndefined`
If true allow returning undefined.
-### ignoreImplicit
+### `ignoreInferredTypes`
If true ignore functions that don't explicitly specify a return type.
diff --git a/docs/rules/no-this-expression.md b/docs/rules/no-this-expression.md
deleted file mode 100644
index 4c723d75e..000000000
--- a/docs/rules/no-this-expression.md
+++ /dev/null
@@ -1,26 +0,0 @@
-# Disallow this access (no-this-expression)
-
-This rule is companion rule to the [no-class](./no-class.md) rule.
-See the its docs for more info.
-
-Examples of **incorrect** code for this rule:
-
-
-
-```js
-/* eslint functional/no-this-expression: "error" */
-
-const foo = this.value + 17;
-```
-
-Examples of **correct** code for this rule:
-
-```js
-/* eslint functional/no-this-expression: "error" */
-
-const foo = object.value + 17;
-```
-
-## Options
-
-The rule does not accept any options.
diff --git a/docs/rules/no-this-expressions.md b/docs/rules/no-this-expressions.md
new file mode 100644
index 000000000..865699999
--- /dev/null
+++ b/docs/rules/no-this-expressions.md
@@ -0,0 +1,28 @@
+# Disallow this access (`functional/no-this-expressions`)
+
+πΌπ« This rule is enabled in the following configs: `no-other-paradigms`, π `strict`. This rule is _disabled_ in the following configs: βοΈ `lite`, β
`recommended`.
+
+
+
+## Rule Details
+
+This rule is a companion rule to the [no-classes](./no-classes.md) rule.
+See the its docs for more info.
+
+### β Incorrect
+
+
+
+```js
+/* eslint functional/no-this-expressions: "error" */
+
+const foo = this.value + 17;
+```
+
+### β
Correct
+
+```js
+/* eslint functional/no-this-expressions: "error" */
+
+const foo = object.value + 17;
+```
diff --git a/docs/rules/no-throw-statement.md b/docs/rules/no-throw-statements.md
similarity index 62%
rename from docs/rules/no-throw-statement.md
rename to docs/rules/no-throw-statements.md
index efdb96cc6..aed2fb89d 100644
--- a/docs/rules/no-throw-statement.md
+++ b/docs/rules/no-throw-statements.md
@@ -1,4 +1,8 @@
-# Disallow throwing exceptions (no-throw-statement)
+# Disallow throwing exceptions (`functional/no-throw-statements`)
+
+πΌ This rule is enabled in the following configs: βοΈ `lite`, `no-exceptions`, β
`recommended`, π `strict`.
+
+
This rule disallows the `throw` keyword.
@@ -7,20 +11,20 @@ This rule disallows the `throw` keyword.
Exceptions are not part of functional programming.
As an alternative a function should return an error or in the case of an async function, a rejected promise.
-Examples of **incorrect** code for this rule:
+### β Incorrect
```js
-/* eslint functional/no-throw-statement: "error" */
+/* eslint functional/no-throw-statements: "error" */
throw new Error("Something went wrong.");
```
-Examples of **correct** code for this rule:
+### β
Correct
```js
-/* eslint functional/no-throw-statement: "error" */
+/* eslint functional/no-throw-statements: "error" */
function divide(x, y) {
return y === 0 ? new Error("Cannot divide by zero.") : x / y;
@@ -28,7 +32,7 @@ function divide(x, y) {
```
```js
-/* eslint functional/no-throw-statement: "error" */
+/* eslint functional/no-throw-statements: "error" */
async function divide(x, y) {
const [xv, yv] = await Promise.all([x, y]);
@@ -46,15 +50,25 @@ This rule accepts an options object of the following type:
```ts
type Options = {
allowInAsyncFunctions: boolean;
-}
+};
```
-The default options:
+### Default Options
```ts
const defaults = {
allowInAsyncFunctions: false,
-}
+};
+```
+
+### Preset Overrides
+
+#### `recommended` and `lite`
+
+```ts
+const recommendedAndLiteOptions = {
+ allowInAsyncFunctions: true,
+};
```
### `allowInAsyncFunctions`
@@ -62,10 +76,10 @@ const defaults = {
If true, throw statements will be allowed within async functions.\
This essentially allows throw statements to be used as return statements for errors.
-Examples of **correct** code for this rule:
+#### β
Correct
```js
-/* eslint functional/no-throw-statement: ["error", { "allowInAsyncFunctions": true }] */
+/* eslint functional/no-throw-statements: ["error", { "allowInAsyncFunctions": true }] */
async function divide(x, y) {
const [xv, yv] = await Promise.all([x, y]);
diff --git a/docs/rules/no-try-statement.md b/docs/rules/no-try-statements.md
similarity index 56%
rename from docs/rules/no-try-statement.md
rename to docs/rules/no-try-statements.md
index 321adb3e3..e7dd4c3bc 100644
--- a/docs/rules/no-try-statement.md
+++ b/docs/rules/no-try-statements.md
@@ -1,17 +1,21 @@
-# Disallow try-catch[-finally] and try-finally patterns (no-try-statement)
+# Disallow try-catch[-finally] and try-finally patterns (`functional/no-try-statements`)
+
+πΌπ« This rule is enabled in the following configs: `no-exceptions`, π `strict`. This rule is _disabled_ in the following configs: βοΈ `lite`, β
`recommended`.
+
+
This rule disallows the `try` keyword.
## Rule Details
-Try statements are not part of functional programming. See [no-throw-statement](./no-throw-statement.md) for more information.
+Try statements are not part of functional programming. See [no-throw-statements](./no-throw-statements.md) for more information.
-Examples of **incorrect** code for this rule:
+### β Incorrect
```js
-/* eslint functional/no-try-statement: "error" */
+/* eslint functional/no-try-statements: "error" */
try {
doSomethingThatMightGoWrong(); // <-- Might throw an exception.
@@ -20,10 +24,10 @@ try {
}
```
-Examples of **correct** code for this rule:
+### β
Correct
```js
-/* eslint functional/no-try-statement: "error" */
+/* eslint functional/no-try-statements: "error" */
doSomethingThatMightGoWrong() // <-- Returns a Promise
.catch((error) => {
@@ -39,16 +43,16 @@ This rule accepts an options object of the following type:
type Options = {
allowCatch: boolean;
allowFinally: boolean;
-}
+};
```
-The default options:
+### Default Options
```ts
const defaults = {
allowCatch: false,
- allowFinally: false
-}
+ allowFinally: false,
+};
```
### `allowCatch`
diff --git a/docs/rules/options/allow-local-mutation.md b/docs/rules/options/allow-local-mutation.md
index 27b9a7028..bc0fa2ca9 100644
--- a/docs/rules/options/allow-local-mutation.md
+++ b/docs/rules/options/allow-local-mutation.md
@@ -1,5 +1,11 @@
# Using the `allowLocalMutation` option
+If this option is set to true, local state is allowed to be mutated. Local state is simply any code inside of a function.
+
+Note: That using this option can lead to more imperative code in functions so use with care!
+
+## Details
+
> If a tree falls in the woods, does it make a sound?
> If a pure function mutates some local data in order to produce an immutable return value, is that ok?
@@ -8,7 +14,3 @@ In general, it is more important to enforce immutability for state that is passe
For example in Redux, the state going in and out of reducers needs to be immutable while the reducer may be allowed to mutate local state in its calculations in order to achieve higher performance.
This is what the `allowLocalMutation` option enables. With this option enabled immutability will be enforced everywhere but in local state.
Function parameters and return types are not considered local state so they will still be checked.
-
-If this option is set to true, local state is allowed to be mutated. Local state is simply any code inside of a function.
-
-Note: That using this option can lead to more imperative code in functions so use with care!
diff --git a/docs/rules/options/ignore-pattern.md b/docs/rules/options/ignore-pattern.md
index e2ecb8c02..64916a5ca 100644
--- a/docs/rules/options/ignore-pattern.md
+++ b/docs/rules/options/ignore-pattern.md
@@ -1,6 +1,9 @@
# Using the `ignorePattern` option
This option takes a RegExp string or an array of RegExp strings.
+It allows for the ability to ignore violations based on the identifier (name) of node in question.
+
+## Details
Some languages are immutable by default but allows you to explicitly declare mutable variables.
For example in [reason](https://facebook.github.io/reason/) you can declare mutable record fields like this:
diff --git a/docs/rules/prefer-immutable-types.md b/docs/rules/prefer-immutable-types.md
new file mode 100644
index 000000000..bf8b90aea
--- /dev/null
+++ b/docs/rules/prefer-immutable-types.md
@@ -0,0 +1,414 @@
+# Require function parameters to be typed as certain immutability (`functional/prefer-immutable-types`)
+
+πΌ This rule is enabled in the following configs: βοΈ `lite`, `no-mutations`, β
`recommended`, π `strict`.
+
+π§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
+
+
+
+## Rule Details
+
+This rule is deigned to be a replacement for
+[@typescript-eslint/prefer-readonly-parameter-types](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/prefer-readonly-parameter-types.md)
+but also add extra functionality, allowing not just parameters to be checked.
+
+This rule uses the
+[is-immutable-type](https://www.npmjs.com/package/is-immutable-type) library to
+calculated immutability. This library allows for more powerful and customizable
+immutability enforcements to be made.
+
+With parameters specifically, it is also worth noting that as immutable types
+are not assignable to mutable ones, and thus users will not be able to pass
+something like a readonly array to a functional that wants a mutable array; even
+if the function does not actually mutate said array.
+Libraries should therefore always enforce this rule for parameters.
+
+### β Incorrect
+
+
+
+```ts
+/* eslint functional/prefer-immutable-types: "error" */
+
+function array1(arg: string[]) {} // array is not readonly
+function array2(arg: ReadonlyArray) {} // array element is not readonly
+function array3(arg: [string, number]) {} // tuple is not readonly
+function array4(arg: readonly [string[], number]) {} // tuple element is not readonly
+// the above examples work the same if you use ReadonlyArray instead
+
+function object1(arg: { prop: string }) {} // property is not readonly
+function object2(arg: { readonly prop: string; prop2: string }) {} // not all properties are readonly
+function object3(arg: { readonly prop: { prop2: string } }) {} // nested property is not readonly
+// the above examples work the same if you use Readonly instead
+
+interface CustomArrayType extends ReadonlyArray {
+ prop: string; // note: this property is mutable
+}
+function custom1(arg: CustomArrayType) {}
+
+interface CustomFunction {
+ (): void;
+ prop: string; // note: this property is mutable
+}
+function custom2(arg: CustomFunction) {}
+
+function union(arg: string[] | ReadonlyArray) {} // not all types are readonly
+
+// rule also checks function types
+interface Foo1 {
+ (arg: string[]): void;
+}
+interface Foo2 {
+ new (arg: string[]): void;
+}
+const x = { foo(arg: string[]): void; };
+function foo(arg: string[]);
+type Foo3 = (arg: string[]) => void;
+interface Foo4 {
+ foo(arg: string[]): void;
+}
+```
+
+### β
Correct
+
+
+
+```ts
+/* eslint functional/prefer-immutable-types: "error" */
+
+function array1(arg: ReadonlyArray) {}
+function array2(arg: ReadonlyArray>) {}
+function array3(arg: readonly [string, number]) {}
+function array4(arg: readonly [ReadonlyArray, number]) {}
+// the above examples work the same if you use ReadonlyArray instead
+
+function object1(arg: { readonly prop: string }) {}
+function object2(arg: { readonly prop: string; readonly prop2: string }) {}
+function object3(arg: { readonly prop: { readonly prop2: string } }) {}
+// the above examples work the same if you use Readonly instead
+
+interface CustomArrayType extends ReadonlyArray {
+ readonly prop: string;
+}
+function custom1(arg: Readonly) {}
+// interfaces that extend the array types are not considered arrays, and thus must be made readonly.
+
+interface CustomFunction {
+ (): void;
+ readonly prop: string;
+}
+function custom2(arg: CustomFunction) {}
+
+function union(arg: ReadonlyArray | ReadonlyArray) {}
+
+function primitive1(arg: string) {}
+function primitive2(arg: number) {}
+function primitive3(arg: boolean) {}
+function primitive4(arg: unknown) {}
+function primitive5(arg: null) {}
+function primitive6(arg: undefined) {}
+function primitive7(arg: any) {}
+function primitive8(arg: never) {}
+function primitive9(arg: string | number | undefined) {}
+
+function fnSig(arg: () => void) {}
+
+enum Foo { a, b }
+function enum1(arg: Foo) {}
+
+function symb1(arg: symbol) {}
+const customSymbol = Symbol('a');
+function symb2(arg: typeof customSymbol) {}
+
+// function types
+interface Foo1 {
+ (arg: ReadonlyArray): void;
+}
+interface Foo2 {
+ new (arg: ReadonlyArray): void;
+}
+const x = { foo(arg: ReadonlyArray): void; };
+function foo(arg: ReadonlyArray);
+type Foo3 = (arg: ReadonlyArray) => void;
+interface Foo4 {
+ foo(arg: ReadonlyArray): void;
+}
+```
+
+## Settings
+
+This rule can leverage shared settings to configure immutability settings.
+
+See the [immutability](./settings/immutability.md) docs.
+
+## Options
+
+This rule accepts an options object of the following type:
+
+```ts
+type Options = {
+ enforcement: "None" | "ReadonlyShallow" | "ReadonlyDeep" | "Immutable";
+ ignoreInferredTypes: boolean;
+ ignoreClasses: boolean | "fieldsOnly";
+ ignoreNamePattern?: string[] | string;
+ ignoreTypePattern?: string[] | string;
+
+ parameters?: {
+ enforcement: "None" | "ReadonlyShallow" | "ReadonlyDeep" | "Immutable";
+ ignoreInferredTypes: boolean;
+ ignoreClasses: boolean | "fieldsOnly";
+ ignoreNamePattern?: string[] | string;
+ ignoreTypePattern?: string[] | string;
+ };
+
+ returnTypes?: {
+ enforcement: "None" | "ReadonlyShallow" | "ReadonlyDeep" | "Immutable";
+ ignoreInferredTypes: boolean;
+ ignoreClasses: boolean | "fieldsOnly";
+ ignoreNamePattern?: string[] | string;
+ ignoreTypePattern?: string[] | string;
+ };
+
+ variables?: {
+ enforcement: "None" | "ReadonlyShallow" | "ReadonlyDeep" | "Immutable";
+ ignoreInFunctions: boolean;
+ ignoreInferredTypes: boolean;
+ ignoreClasses: boolean | "fieldsOnly";
+ ignoreNamePattern?: string[] | string;
+ ignoreTypePattern?: string[] | string;
+ };
+
+ fixer?:
+ | {
+ ReadonlyShallow?:
+ | { pattern: string; replace: string }
+ | Array<{ pattern: string; replace: string }>
+ | false;
+ ReadonlyDeep?:
+ | { pattern: string; replace: string }
+ | Array<{ pattern: string; replace: string }>
+ | false;
+ Immutable?:
+ | { pattern: string; replace: string }
+ | Array<{ pattern: string; replace: string }>
+ | false;
+ }
+ | false;
+};
+```
+
+### Default Options
+
+```ts
+const defaults = {
+ enforcement: "Immutable",
+ ignoreClasses: false,
+ ignoreInferredTypes: false,
+ fixer: {
+ ReadonlyShallow: [
+ {
+ pattern: "^([_$a-zA-Z\\xA0-\\uFFFF][_$a-zA-Z0-9\\xA0-\\uFFFF]*\\[\\])$",
+ replace: "readonly $1",
+ },
+ {
+ pattern: "^(Array|Map|Set)<(.+)>$",
+ replace: "Readonly$1<$2>",
+ },
+ {
+ pattern: "^(.+)$",
+ replace: "Readonly<$1>",
+ },
+ ],
+ ReadonlyDeep: false,
+ Immutable: false,
+ },
+};
+```
+
+### Preset Overrides
+
+#### `recommended`
+
+```ts
+const recommendedOptions = {
+ enforcement: "None",
+ ignoreInferredTypes: true,
+ parameters: {
+ enforcement: "ReadonlyDeep",
+ },
+};
+```
+
+#### `lite`
+
+```ts
+const liteOptions = {
+ enforcement: "None",
+ ignoreInferredTypes: true,
+ parameters: {
+ enforcement: "ReadonlyShallow",
+ },
+};
+```
+
+### `enforcement`
+
+The level of immutability that should be enforced. One of the following:
+
+- `None` - Don't enforce any immutability.
+- `ReadonlyShallow` - Enforce that the data is shallowly immutable.
+- `ReadonlyDeep` - Enforce that the data is deeply immutable (methods may not be).
+- `Immutable` - Enforce that everything is deeply immutable, nothing can be modified.
+
+#### β Incorrect
+
+
+
+```ts
+/* eslint functional/prefer-immutable-types: ["error", { "enforcement": "Immutable" }] */
+
+function array(arg: ReadonlyArray) {} // ReadonlyArray is not immutable
+function set(arg: ReadonlySet) {} // ReadonlySet is not immutable
+function map(arg: ReadonlyMap) {} // ReadonlyMap is not immutable
+```
+
+#### β
Correct
+
+
+
+```ts
+/* eslint functional/prefer-immutable-types: ["error", { "enforcement": "Immutable" }] */
+
+function set(arg: Readonly>) {}
+function map(arg: Readonly>) {}
+function object(arg: Readonly<{ prop: string }>) {}
+```
+
+
+
+```ts
+/* eslint functional/prefer-immutable-types: ["error", { "enforcement": "ReadonlyShallow" }] */
+
+function array(arg: ReadonlyArray<{ foo: string }>) {}
+function set(arg: ReadonlySet<{ foo: string }>) {}
+function map(arg: ReadonlyMap<{ foo: string }>) {}
+function object(arg: Readonly<{ prop: { foo: string } }>) {}
+```
+
+### `ignoreInferredTypes`
+
+This option allows you to ignore values which don't explicitly specify a type.
+
+One case where this may be desirable is when an external dependency
+specifies a callback with mutable parameters, and manually annotating the
+callback's parameters is undesirable.
+
+`false` by default.
+
+### `ignoreClasses`
+
+A boolean to specify if checking classes should be ignored. `false` by default.
+
+
+
+#### β Incorrect
+
+
+
+```ts
+/* eslint functional/prefer-immutable-types: ["error", { "ignoreInferredTypes": true }] */
+
+import { acceptsCallback, type CallbackOptions } from "external-dependency";
+
+acceptsCallback((options: CallbackOptions) => {});
+```
+
+
+external-dependency.d.ts
+
+```ts
+export interface CallbackOptions {
+ prop: string;
+}
+type Callback = (options: CallbackOptions) => void;
+type AcceptsCallback = (callback: Callback) => void;
+
+export const acceptsCallback: AcceptsCallback;
+```
+
+
+
+#### β
Correct
+
+
+
+```ts
+/* eslint functional/prefer-immutable-types: ["error", { "ignoreInferredTypes": true }] */
+
+import { acceptsCallback } from "external-dependency";
+
+acceptsCallback((options) => {});
+```
+
+
+external-dependency.d.ts
+
+```ts
+export interface CallbackOptions {
+ prop: string;
+}
+type Callback = (options: CallbackOptions) => void;
+type AcceptsCallback = (callback: Callback) => void;
+
+export const acceptsCallback: AcceptsCallback;
+```
+
+
+
+
+
+### `parameters.*`, `returnTypes.*`, `variables.*`
+
+Override the options specifically for the given type of types.
+
+#### `variables.ignoreInFunctions`
+
+If true, the rule will not flag any variables that are inside of function bodies.
+
+See the [allowLocalMutation](./options/allow-local-mutation.md) docs for more information.
+
+### `fixer`
+
+Configure the fixer to work with your setup.
+If set to `false`, the fixer will be disabled.
+
+#### `fixer.*`
+
+By default we only configure the fixer to correct shallow readonly violations as TypeScript itself provides a utility type for this.
+If you have access to other utility types (such as [type-fest's `ReadonlyDeep`](https://github.com/sindresorhus/type-fest#:~:text=set%20to%20optional.-,ReadonlyDeep,-%2D%20Create%20a%20deeply)), you can configure the fixer to use them with this option.
+
+Example using `ReadonlyDeep` instead of `Readonly`:
+
+```jsonc
+{
+ // ...
+ "fixer": {
+ "ReadonlyDeep": [
+ {
+ "pattern": "^(?:Readonly<(.+)>|(.+))$",
+ "replace": "ReadonlyDeep<$1$2>"
+ }
+ ]
+ }
+}
+```
+
+### `ignoreNamePattern`
+
+This option takes a `RegExp` string or an array of `RegExp` strings.
+It allows for the ability to ignore violations based on the identifier (name) of node in question.
+
+### `ignoreTypePattern`
+
+This option takes a `RegExp` string or an array of `RegExp` strings.
+It allows for the ability to ignore violations based on the type (as written, with whitespace removed) of the node in question.
diff --git a/docs/rules/prefer-property-signatures.md b/docs/rules/prefer-property-signatures.md
new file mode 100644
index 000000000..755cfa672
--- /dev/null
+++ b/docs/rules/prefer-property-signatures.md
@@ -0,0 +1,74 @@
+# Prefer property signatures over method signatures (`functional/prefer-property-signatures`)
+
+πΌ This rule is enabled in the π¨ `stylistic` config.
+
+
+
+## Rule Details
+
+There are two ways function members can be declared in interfaces and type aliases; `MethodSignature` and `PropertySignature`.
+
+The `MethodSignature` and the `PropertySignature` forms seem equivalent, but only the `PropertySignature` form can have a `readonly` modifier.
+Because of this any `MethodSignature` will be mutable unless wrapped in the `Readonly` type.
+
+It should be noted however that the `PropertySignature` form does not support overloading.
+
+### β Incorrect
+
+
+
+```ts
+/* eslint functional/prefer-property-signatures: "error" */
+
+type Foo = {
+ bar(): string;
+};
+```
+
+### β
Correct
+
+
+
+```ts
+/* eslint functional/prefer-property-signatures: "error" */
+
+type Foo = {
+ bar: () => string;
+};
+
+type Foo = {
+ readonly bar: () => string;
+};
+```
+
+## Options
+
+This rule accepts an options object of the following type:
+
+```ts
+type Options = {
+ ignoreIfReadonlyWrapped: boolean;
+};
+```
+
+### Default Options
+
+```ts
+const defaults = {
+ ignoreIfReadonlyWrapped: false,
+};
+```
+
+### `ignoreIfReadonlyWrapped`
+
+If set to `true`, method signatures wrapped in the `Readonly` type will not be flagged as violations.
+
+#### β
Correct
+
+```ts
+/* eslint functional/prefer-property-signatures: ["error", { "ignoreIfReadonlyWrapped": true } ] */
+
+type Foo = Readonly<{
+ bar(): string;
+}>;
+```
diff --git a/docs/rules/prefer-readonly-type.md b/docs/rules/prefer-readonly-type.md
index 2972e1115..58de245e7 100644
--- a/docs/rules/prefer-readonly-type.md
+++ b/docs/rules/prefer-readonly-type.md
@@ -1,6 +1,14 @@
-# Prefer readonly types over mutable types (prefer-readonly-type)
+# Prefer readonly types over mutable types (`functional/prefer-readonly-type`)
-This rule enforces use of the readonly modifier and readonly types.
+β This rule is deprecated. It was replaced by [`functional/prefer-immutable-types`](prefer-immutable-types.md),[`functional/type-declaration-immutability`](type-declaration-immutability.md).
+
+π§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
+
+
+
+This rule has been replaced by
+[prefer-immutable-parameter-types](./prefer-immutable-parameter-types.md) and
+[type-declaration-immutability](./type-declaration-immutability.md).
## Rule Details
@@ -8,7 +16,7 @@ This rule enforces use of `readonly T[]` (`ReadonlyArray`) over `T[]` (`Array
The readonly modifier must appear on property signatures in interfaces, property declarations in classes, and index signatures.
-Examples of **incorrect** code for this rule:
+### β Incorrect
@@ -23,7 +31,7 @@ const point: Point = { x: 23, y: 44 };
point.x = 99; // This is perfectly valid.
```
-Examples of **correct** code for this rule:
+### β
Correct
```ts
/* eslint functional/prefer-readonly-type: "error" */
@@ -102,10 +110,10 @@ type Options = {
ignoreInterface: boolean;
ignoreCollections: boolean;
ignorePattern?: string[] | string;
-}
+};
```
-The default options:
+### Default Options
```ts
const defaults = {
@@ -115,7 +123,7 @@ const defaults = {
ignoreClass: false,
ignoreInterface: false,
ignoreCollections: false,
-}
+};
```
### `checkImplicit`
diff --git a/docs/rules/prefer-tacit.md b/docs/rules/prefer-tacit.md
index cd6278906..b6a9e8c96 100644
--- a/docs/rules/prefer-tacit.md
+++ b/docs/rules/prefer-tacit.md
@@ -1,4 +1,10 @@
-# Tacit / Point-Free (prefer-tacit)
+# Replaces `x => f(x)` with just `f` (`functional/prefer-tacit`)
+
+β οΈ This rule _warns_ in the π¨ `stylistic` config.
+
+π‘ This rule is manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
+
+
This rule enforces using functions directly if they can be without wrapping them.
@@ -7,7 +13,7 @@ This rule enforces using functions directly if they can be without wrapping them
Generally there's no reason to wrap a function with a callback wrapper if it's directly called anyway.
Doing so creates extra inline lambdas that slow the runtime down.
-Examples of **incorrect** code for this rule:
+### β Incorrect
@@ -18,10 +24,10 @@ function f(x) {
// ...
}
-const foo = x => f(x);
+const foo = (x) => f(x);
```
-Examples of **correct** code for this rule:
+### β
Correct
```ts
/* eslint functional/prefer-tacit: "error" */
@@ -39,16 +45,12 @@ This rule accepts an options object of the following type:
```ts
type Options = {
- assumeTypes:
- | false
- | {
- allowFixer: boolean;
- }
+ assumeTypes: boolean;
ignorePattern?: string[] | string;
-}
+};
```
-The default options:
+### Default Options
```ts
const defaults = {
@@ -58,7 +60,7 @@ const defaults = {
### `assumeTypes`
-The rule take advantage of TypeScript's typing engine to check if callback wrapper is in fact safe to remove.
+The rule takes advantage of TypeScript's typing engine to check if callback wrapper is in fact safe to remove.
This option will make the rule assume that the function only accepts the arguments given to it in the wrapper.
However this may result in some false positives being picked up.
@@ -66,19 +68,12 @@ However this may result in some false positives being picked up.
```js
-const foo = x => f(x); // If `f` only accepts one parameter then this is violation of the rule.
+const foo = (x) => f(x); // If `f` only accepts one parameter then this is violation of the rule.
const bar = foo(1, 2, 3); // But if `f` accepts more than one parameter then it isn't.
```
Note: Enabling this option is the only way to get this rule to report violations in an environment without TypeScript's typing engine available (e.g. In Vanilla JS).
-### `assumeTypes.allowFixer`
-
-When set types will be assumed.
-
-This option states whether the auto fixer should be enabled for violations that are found when assuming types (violations found without assuming types will ignore this option).
-Because when assuming types, false positives may be found, it's recommended to set this option to `false`.
-
### `ignorePattern`
See the [ignorePattern](./options/ignore-pattern.md) docs.
diff --git a/docs/rules/readonly-type.md b/docs/rules/readonly-type.md
new file mode 100644
index 000000000..5f0e19884
--- /dev/null
+++ b/docs/rules/readonly-type.md
@@ -0,0 +1,90 @@
+# Require consistently using either `readonly` keywords or `Readonly` (`functional/readonly-type`)
+
+πΌ This rule is enabled in the π¨ `stylistic` config.
+
+π§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
+
+
+
+This rule enforces consistently using either `readonly` keywords or `Readonly`.
+
+## Rule Details
+
+There are two ways to declare type literals as readonly, either by specifying that each
+property of the type is readonly using the `readonly` keyword, or by wrapping the type
+in `Readonly`.
+
+This rule is designed to enforce a consistent way of doing this.
+
+### β Incorrect
+
+
+
+```ts
+/* eslint functional/readonly-type: ["error", "keyword"] */
+
+type Foo = Readonly<{
+ bar: string;
+ baz: number;
+}>;
+```
+
+
+
+```ts
+/* eslint functional/readonly-type: ["error", "generic"] */
+
+type Foo = {
+ readonly bar: string;
+ readonly baz: number;
+};
+```
+
+### β
Correct
+
+```ts
+/* eslint functional/readonly-type: ["error", "keyword"] */
+
+type Foo = {
+ readonly bar: string;
+ readonly baz: number;
+};
+
+type Foo2 = {
+ readonly bar: string;
+ baz: number;
+};
+```
+
+```ts
+/* eslint functional/readonly-type: ["error", "generic"] */
+
+type Foo = Readonly<{
+ bar: string;
+ baz: number;
+}>;
+
+// No issue as it's not fully readonly.
+type Foo2 = {
+ readonly bar: string;
+ baz: number;
+};
+```
+
+## Options
+
+This rule takes a single string option, either `generic` | `keyword`.
+
+### Default Options
+
+```ts
+const defaults = "generic";
+```
+
+### `generic`
+
+Enforce using `Readonly` instead of marking each property as readonly with the `readonly` keyword.
+
+### `keyword`
+
+Enforce using `readonly` keyword for each property instead of wrapping with `Readonly`.
diff --git a/docs/rules/settings/immutability.md b/docs/rules/settings/immutability.md
new file mode 100644
index 000000000..c2ec9a416
--- /dev/null
+++ b/docs/rules/settings/immutability.md
@@ -0,0 +1,41 @@
+# Using the `immutability` setting
+
+We are using the
+[is-immutable-type](https://www.npmjs.com/package/is-immutable-type) library to
+determine the immutability of types. This library can be configure for all rules
+at once using a shared setting.
+
+## Overrides
+
+For details see [the overrides
+section](https://github.com/RebeccaStevens/is-immutable-type#overrides) of
+[is-immutable-type](https://www.npmjs.com/package/is-immutable-type).
+
+### Example of configuring immutability overrides
+
+In this example, we are configuring
+[is-immutable-type](https://www.npmjs.com/package/is-immutable-type) to treat
+any readonly array (regardless of the syntax used) as immutable in the case
+where it was found to be deeply readonly. If it was only found to be shallowly
+readonly, then no override will be applied.
+
+```jsonc
+// .eslintrc.json
+{
+ // ...
+ "settings": {
+ "immutability": {
+ "overrides": [
+ {
+ "name": "ReadonlyArray",
+ "to": "Immutable",
+ "from": "ReadonlyDeep"
+ }
+ ]
+ }
+ },
+ "rules": {
+ // ...
+ }
+}
+```
diff --git a/docs/rules/type-declaration-immutability.md b/docs/rules/type-declaration-immutability.md
new file mode 100644
index 000000000..ce555b244
--- /dev/null
+++ b/docs/rules/type-declaration-immutability.md
@@ -0,0 +1,194 @@
+# Enforce the immutability of types based on patterns (`functional/type-declaration-immutability`)
+
+πΌ This rule is enabled in the following configs: βοΈ `lite`, `no-mutations`, β
`recommended`, π `strict`.
+
+π§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
+
+
+
+Require type alias declarations and interfaces that imply some level of
+immutability to comply to it.
+
+## Rule Details
+
+This rule enforces rules on type immutability based on the type's name.
+
+For details on what the different levels of immutability mean, see [the
+immutability
+section](https://github.com/RebeccaStevens/is-immutable-type#immutability) of
+[is-immutable-type](https://www.npmjs.com/package/is-immutable-type).
+
+### β Incorrect
+
+
+
+```ts
+/* eslint functional/type-declaration-immutability: "error" */
+
+type ReadonlyElement = {
+ id: number;
+ data: string[];
+};
+
+type ReadonlyDeepElement = Readonly<{
+ id: number;
+ data: string[];
+}>;
+
+type MutableElement = Readonly<{
+ id: number;
+ data: ReadonlyArray;
+}>;
+```
+
+### β
Correct
+
+
+
+```ts
+/* eslint functional/type-declaration-immutability: "error" */
+
+type ReadonlyElement = Readonly<{
+ id: number;
+ data: string[];
+}>;
+
+type ReadonlyDeepElement = Readonly<{
+ id: number;
+ data: ReadonlyArray;
+}>;
+
+type MutableElement = {
+ readonly id: number;
+ data: ReadonlyArray;
+};
+```
+
+## Settings
+
+This rule can leverage shared settings to configure immutability settings.
+
+See the [immutability](./settings/immutability.md) docs.
+
+## Options
+
+This rule accepts an options object of the following type:
+
+```ts
+type Options = {
+ rules: Array<{
+ identifiers: string | string[];
+ immutability: "Mutable" | "ReadonlyShallow" | "ReadonlyDeep" | "Immutable";
+ comparator?: "Less" | "AtMost" | "Exactly" | "AtLeast" | "More";
+ fixer?:
+ | { pattern: string; replace: string }
+ | Array<{ pattern: string; replace: string }>
+ | false;
+ }>;
+ ignoreInterfaces: boolean;
+ ignorePattern: string[] | string;
+};
+```
+
+### Default Options
+
+```ts
+const defaults = {
+ rules: [
+ {
+ identifiers: "^(?!I?Mutable).+",
+ immutability: "Immutable",
+ comparator: "AtLeast",
+ fixer: false,
+ },
+ ],
+ ignoreInterfaces: false,
+};
+```
+
+### Preset Overrides
+
+#### `recommended` and `lite`
+
+```ts
+const recommendedAndLiteOptions = {
+ rules: [
+ {
+ identifiers: "I?Immutable.+",
+ immutability: "Immutable",
+ comparator: "AtLeast",
+ },
+ {
+ identifiers: "I?ReadonlyDeep.+",
+ immutability: "ReadonlyDeep",
+ comparator: "AtLeast",
+ },
+ {
+ identifiers: "I?Readonly.+",
+ immutability: "ReadonlyShallow",
+ comparator: "AtLeast",
+ fixer: [
+ {
+ pattern: "^(Array|Map|Set)<(.+)>$",
+ replace: "Readonly$1<$2>",
+ },
+ {
+ pattern: "^(.+)$",
+ replace: "Readonly<$1>",
+ },
+ ],
+ },
+ {
+ identifiers: "I?Mutable.+",
+ immutability: "Mutable",
+ comparator: "AtMost",
+ fixer: [
+ {
+ pattern: "^Readonly(Array|Map|Set)<(.+)>$",
+ replace: "$1<$2>",
+ },
+ {
+ pattern: "^Readonly<(.+)>$",
+ replace: "$1",
+ },
+ ],
+ },
+ ],
+};
+```
+
+### `rules`
+
+An array of rules to enforce immutability by.
+
+These rules should be sorted by precedence as each type declaration will only
+enforce the first matching rule to it.
+
+#### `identifiers`
+
+A regex pattern or an array of regex patterns that are used to match against the
+name of the type declarations.
+
+#### `immutability`
+
+The level of immutability to compare against. This value will be compared to the
+calculated immutability using the `comparator`.
+
+#### `comparator`
+
+The comparator to use to compare the calculated immutability to the desired
+immutability. This can be thought of as `<`, `<=`, `==`, `>=` or `>`.
+
+#### `fixer`
+
+Configure the fixer for this rule to work with your setup.
+If not set, or set to `false`, the fixer will be disabled.
+
+### `ignoreInterfaces`
+
+A boolean to specify whether interfaces should be exempt from these rules.
+`false` by default.
+
+### `ignorePattern`
+
+See the [ignorePattern](./options/ignore-pattern.md) docs.
diff --git a/docs/user-guide/migrating-from-tslint.md b/docs/user-guide/migrating-from-tslint.md
index 60af88289..f36977966 100644
--- a/docs/user-guide/migrating-from-tslint.md
+++ b/docs/user-guide/migrating-from-tslint.md
@@ -51,20 +51,20 @@ In order for the parser to have access to type information, it needs access to y
Below is a table mapping the `eslint-plugin-functional` rules to their `tslint-immutable` equivalents.
-| `eslint-plugin-functional` Rule | Equivalent `tslint-immutable` Rules |
-| ----------------------------------------------------------------------------- | ------------------------------------------------------- |
-| [`functional/prefer-readonly-type`](../rules/prefer-readonly-type.md) | `readonly-keyword` & `readonly-array` |
-| [`functional/no-let`](../rules/no-let.md) | `no-let` |
-| [`functional/immutable-data`](../rules/immutable-data.md) | `no-object-mutation`, `no-array-mutation` & `no-delete` |
-| [`functional/no-method-signature`](../rules/no-method-signature.md) | `no-method-signature` |
-| [`functional/no-this-expression`](../rules/no-this-expression.md) | `no-this` |
-| [`functional/no-class`](../rules/no-class.md) | `no-class` |
-| [`functional/no-mixed-type`](../rules/no-mixed-type.md) | `no-mixed-interface` |
-| [`functional/no-expression-statement`](../rules/no-expression-statement.md) | `no-expression-statement` |
-| [`functional/no-conditional-statement`](../rules/no-conditional-statement.md) | `no-if-statement` |
-| [`functional/no-loop-statement`](../rules/no-loop-statement.md) | `no-loop-statement` |
-| [`functional/no-return-void`](../rules/no-return-void.md) | - |
-| [`functional/no-throw-statement`](../rules/no-throw-statement.md) | `no-throw` |
-| [`functional/no-try-statement`](../rules/no-try-statement.md) | `no-try` |
-| [`functional/no-promise-reject`](../rules/no-promise-reject.md) | `no-reject` |
-| [`functional/functional-parameters`](../rules/functional-parameters.md) | - |
+| `eslint-plugin-functional` Rule | Equivalent `tslint-immutable` Rules |
+| ------------------------------------------------------------------------------- | ------------------------------------------------------- |
+| [`functional/prefer-readonly-type`](../rules/prefer-readonly-type.md) | `readonly-keyword` & `readonly-array` |
+| [`functional/no-let`](../rules/no-let.md) | `no-let` |
+| [`functional/immutable-data`](../rules/immutable-data.md) | `no-object-mutation`, `no-array-mutation` & `no-delete` |
+| [`functional/no-method-signature`](../rules/no-method-signature.md) | `no-method-signature` |
+| [`functional/no-this-expressions`](../rules/no-this-expressions.md) | `no-this` |
+| [`functional/no-classes`](../rules/no-classes.md) | `no-classes` |
+| [`functional/no-mixed-types`](../rules/no-mixed-types.md) | `no-mixed-interface` |
+| [`functional/no-expression-statements`](../rules/no-expression-statements.md) | `no-expression-statements` |
+| [`functional/no-conditional-statements`](../rules/no-conditional-statements.md) | `no-if-statement` |
+| [`functional/no-loop-statements`](../rules/no-loop-statements.md) | `no-loop-statements` |
+| [`functional/no-return-void`](../rules/no-return-void.md) | - |
+| [`functional/no-throw-statements`](../rules/no-throw-statements.md) | `no-throw` |
+| [`functional/no-try-statements`](../rules/no-try-statements.md) | `no-try` |
+| [`functional/no-promise-reject`](../rules/no-promise-reject.md) | `no-reject` |
+| [`functional/functional-parameters`](../rules/functional-parameters.md) | - |
diff --git a/package.json b/package.json
index 34f1c4d16..7480124e0 100644
--- a/package.json
+++ b/package.json
@@ -19,14 +19,20 @@
"type": "git",
"url": "git+https://github.com/eslint-functional/eslint-plugin-functional"
},
- "license": "MIT",
- "author": "Jonas Kello",
- "contributors": [
+ "funding": [
{
- "name": "Rebecca Stevens",
- "email": "rebecca.stevens@outlook.co.nz"
+ "type": "ko-fi",
+ "url": "https://ko-fi.com/rebeccastevens"
}
],
+ "license": "MIT",
+ "author": {
+ "name": "Rebecca Stevens",
+ "email": "rebecca.stevens@outlook.co.nz"
+ },
+ "contributors": [
+ "Jonas Kello"
+ ],
"exports": {
"default": "./lib/index.js",
"import": "./lib/index.mjs",
@@ -41,122 +47,108 @@
"README.md"
],
"scripts": {
- "prebuild": "rimraf lib",
- "build": "yarn compile",
- "prebuild-tests": "rimraf build",
- "build-tests": "yarn compile-tests",
- "check-format": "prettier --list-different \"./**/*.{md,ts}\"",
- "check-spelling": "cspell --config=.cspell.json \"**/*.{md,ts}\"",
+ "build": "rimraf lib && yarn compile && yarn build:docs",
+ "build-tests": "rimraf build && yarn compile-tests",
+ "build:docs": "eslint-doc-generator",
"compile": "rollup -c",
- "compile-tests": "ts-node -P scripts/tsconfig.json scripts/compile-tests.ts",
+ "compile-tests": "ts-node -P scripts/tsconfig.json scripts/compile-tests.mts",
"cz": "git-cz",
- "format": "prettier --write \"./**/*.{md,ts}\"",
- "prelint": "yarn build && yarn link && yarn link 'eslint-plugin-functional'",
- "lint": "yarn lint-js && yarn lint-md",
- "lint-js": "eslint .",
- "lint-md": "markdownlint \"**/*.md\" --config=.markdownlint.json --ignore-path=.markdownlintignore",
+ "lint": "yarn build && yarn lint:js && yarn lint:md && yarn lint:eslint-docs && yarn lint:spelling",
+ "lint:eslint-docs": "eslint-doc-generator --check",
+ "lint:js": "eslint .",
+ "lint:md": "markdownlint \"**/*.md\" --config=.markdownlint.json --ignore-path=.markdownlintignore",
+ "lint:spelling": "cspell --config=.cspell.json \"**/*.{md,ts}\"",
"prepare": "yarn husky install",
"test": "nyc ava",
"test-compiled": "USE_COMPILED_TESTS=1 nyc ava",
"test-work": "ONLY_TEST_WORK_FILE=1 ava",
- "verify": "yarn build && yarn lint && yarn build-tests && yarn test-compiled && rimraf build"
+ "verify": "yarn lint && yarn build-tests && yarn test-compiled && rimraf build"
},
"resolutions": {
"npm/chalk": "^4.1.2"
},
"dependencies": {
- "@typescript-eslint/utils": "^5.10.2",
- "deepmerge-ts": "^4.0.3",
+ "@typescript-eslint/utils": "^5.49.0",
+ "deepmerge-ts": "^4.2.2",
"escape-string-regexp": "^4.0.0",
- "semver": "^7.3.7"
+ "is-immutable-type": "^1.2.3",
+ "semver": "^7.3.8"
},
"devDependencies": {
- "@ava/typescript": "^3.0.1",
- "@commitlint/cli": "^17.0.0",
- "@commitlint/config-conventional": "^17.0.0",
- "@google/semantic-release-replace-plugin": "^1.1.0",
- "@istanbuljs/nyc-config-typescript": "^1.0.2",
- "@rebeccastevens/eslint-config": "1.3.23",
- "@rollup/plugin-commonjs": "^24.0.0",
- "@rollup/plugin-json": "^6.0.0",
- "@rollup/plugin-node-resolve": "^15.0.0",
- "@rollup/plugin-typescript": "^11.0.0",
- "@semantic-release/changelog": "^6.0.1",
- "@semantic-release/commit-analyzer": "^9.0.2",
- "@semantic-release/git": "^10.0.1",
- "@semantic-release/github": "^8.0.6",
- "@semantic-release/npm": "^9.0.1",
- "@semantic-release/release-notes-generator": "^10.0.3",
- "@types/dedent": "^0.7.0",
- "@types/eslint": "^8.4.1",
- "@types/estree": "^1.0.0",
- "@types/node": "18.11.18",
- "@types/rollup-plugin-auto-external": "^2.0.2",
- "@types/semver": "^7.3.12",
- "@typescript-eslint/eslint-plugin": "^5.10.2",
- "@typescript-eslint/parser": "^5.10.2",
- "ava": "^5.0.0",
- "babel-eslint": "^10.1.0",
- "chalk": "^4.1.2",
- "codecov": "^3.8.2",
- "commitizen": "^4.2.4",
- "conventional-commit-types": "^3.0.0",
- "cross-env": "^7.0.3",
- "cspell": "^6.4.1",
- "dedent": "^0.7.0",
- "eslint": "^8.8.0",
- "eslint-ava-rule-tester": "^4.0.0",
- "eslint-config-prettier": "^8.3.0",
- "eslint-import-resolver-typescript": "^3.0.0",
- "eslint-plugin-ava": "^13.2.0",
- "eslint-plugin-eslint-comments": "^3.2.0",
- "eslint-plugin-eslint-plugin": "^5.0.0",
- "eslint-plugin-import": "^2.25.4",
- "eslint-plugin-jsdoc": "^39.0.0",
- "eslint-plugin-markdown": "^3.0.0",
- "eslint-plugin-node": "^11.1.0",
- "eslint-plugin-optimize-regex": "^1.2.1",
- "eslint-plugin-prettier": "^4.0.0",
- "eslint-plugin-promise": "^6.0.0",
- "eslint-plugin-sonarjs": "^0.17.0",
- "eslint-plugin-tsdoc": "^0.2.14",
- "eslint-plugin-unicorn": "^43.0.0",
- "espree": "^9.3.0",
- "husky": "^8.0.0",
- "json-schema": "^0.4.0",
- "jsonc-parser": "^3.0.0",
- "lint-staged": "^13.0.0",
- "markdownlint-cli": "^0.33.0",
- "nyc": "^15.1.0",
- "prettier": "^2.5.1",
- "rimraf": "^3.0.2",
- "rollup": "^3.0.0",
- "rollup-plugin-auto-external": "^2.0.0",
- "semantic-release": "^19.0.5",
- "ts-node": "^10.4.0",
- "tsc-prog": "^2.2.1",
- "tsconfig-paths": "^4.0.0",
- "tslib": "^2.3.1",
- "tsutils": "^3.21.0",
- "type-fest": "^3.0.0",
- "typescript": "^4.5.5",
- "word-wrap": "^1.2.3"
+ "@ava/typescript": "3.0.1",
+ "@commitlint/cli": "17.4.2",
+ "@commitlint/config-conventional": "17.4.2",
+ "@cspell/dict-cryptocurrencies": "3.0.1",
+ "@istanbuljs/nyc-config-typescript": "1.0.2",
+ "@rebeccastevens/eslint-config": "1.4.6",
+ "@rollup/plugin-commonjs": "24.0.1",
+ "@rollup/plugin-json": "6.0.0",
+ "@rollup/plugin-node-resolve": "15.0.1",
+ "@rollup/plugin-typescript": "11.0.0",
+ "@types/dedent": "0.7.0",
+ "@types/eslint": "8.4.10",
+ "@types/estree": "1.0.0",
+ "@types/node": "16.10.0",
+ "@types/rollup-plugin-auto-external": "2.0.2",
+ "@types/semver": "7.3.13",
+ "@typescript-eslint/eslint-plugin": "5.49.0",
+ "@typescript-eslint/parser": "5.49.0",
+ "ava": "5.1.1",
+ "babel-eslint": "10.1.0",
+ "chalk": "4.1.2",
+ "codecov": "3.8.2",
+ "commitizen": "4.3.0",
+ "conventional-commit-types": "3.0.0",
+ "cross-env": "7.0.3",
+ "cspell": "6.19.2",
+ "dedent": "0.7.0",
+ "eslint": "8.32.0",
+ "eslint-ava-rule-tester": "4.0.0",
+ "eslint-config-prettier": "8.6.0",
+ "eslint-doc-generator": "1.4.2",
+ "eslint-import-resolver-typescript": "3.5.3",
+ "eslint-plugin-ava": "14.0.0",
+ "eslint-plugin-eslint-comments": "3.2.0",
+ "eslint-plugin-eslint-plugin": "5.0.8",
+ "eslint-plugin-import": "2.27.5",
+ "eslint-plugin-jsdoc": "39.6.9",
+ "eslint-plugin-markdown": "3.0.0",
+ "eslint-plugin-node": "11.1.0",
+ "eslint-plugin-optimize-regex": "1.2.1",
+ "eslint-plugin-prettier": "4.2.1",
+ "eslint-plugin-promise": "6.1.1",
+ "eslint-plugin-sonarjs": "0.18.0",
+ "eslint-plugin-unicorn": "45.0.2",
+ "espree": "9.4.1",
+ "husky": "8.0.3",
+ "json-schema": "0.4.0",
+ "jsonc-parser": "3.2.0",
+ "lint-staged": "13.1.0",
+ "markdownlint-cli": "0.33.0",
+ "nyc": "15.1.0",
+ "prettier": "2.8.3",
+ "prettier-plugin-packagejson": "2.4.0",
+ "rimraf": "4.1.2",
+ "rollup": "3.11.0",
+ "rollup-plugin-auto-external": "2.0.0",
+ "ts-node": "10.9.1",
+ "tsc-prog": "2.2.1",
+ "tsconfig-paths": "4.1.2",
+ "tslib": "2.5.0",
+ "typescript": "4.9.4",
+ "word-wrap": "1.2.3"
},
"peerDependencies": {
"eslint": "^8.0.0",
- "tsutils": "^3.0.0",
- "typescript": "^3.4.1 || ^4.0.0"
+ "typescript": ">=4.0.2"
},
"peerDependenciesMeta": {
- "tsutils": {
- "optional": true
- },
"typescript": {
"optional": true
}
},
"packageManager": "yarn@3.3.1",
"engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ "node": ">=16.10.0"
}
}
diff --git a/scripts/compile-tests.ts b/scripts/compile-tests.mts
similarity index 77%
rename from scripts/compile-tests.ts
rename to scripts/compile-tests.mts
index b47121393..acbda0441 100644
--- a/scripts/compile-tests.ts
+++ b/scripts/compile-tests.mts
@@ -1,15 +1,16 @@
-import { promises as fs } from "fs";
+import { promises as fs } from "node:fs";
import * as JSONC from "jsonc-parser";
import * as tsc from "tsc-prog";
-/**
- * The script.
- */
-async function run() {
- transpileTests();
- await Promise.all([createTestsTsConfig(), createTestsHelpersTsConfig()]);
-}
+transpileTests();
+await Promise.all(
+ /* eslint-disable unicorn/prefer-top-level-await -- See https://github.com/sindresorhus/eslint-plugin-unicorn/issues/1919 */ [
+ createTestsTsConfig(),
+ createTestsHelpersTsConfig(),
+ ]
+ /* eslint-enable unicorn/prefer-top-level-await */
+);
/**
* Transpile the tests.
@@ -61,6 +62,3 @@ async function createTestsHelpersTsConfig() {
"build/tests/helpers/test-tsconfig.json"
);
}
-
-// Run the script.
-void run();
diff --git a/scripts/tsconfig.json b/scripts/tsconfig.json
index 034159ece..34688238a 100644
--- a/scripts/tsconfig.json
+++ b/scripts/tsconfig.json
@@ -1,7 +1,7 @@
{
"compilerOptions": {
- "target": "ES2018",
- "module": "CommonJS",
+ "target": "ES2020",
+ "module": "ESNext",
"lib": ["esnext"],
"alwaysStrict": true,
"strict": true,
@@ -17,5 +17,8 @@
"newLine": "LF",
"noEmitOnError": true,
"removeComments": true
+ },
+ "ts-node": {
+ "esm": true
}
}
diff --git a/src/configs/all.ts b/src/configs/all.ts
index 0db73d9d0..6bf356e8d 100644
--- a/src/configs/all.ts
+++ b/src/configs/all.ts
@@ -1,32 +1,45 @@
import type { Linter } from "eslint";
+import * as functionalParameters from "~/rules/functional-parameters";
+import * as immutableData from "~/rules/immutable-data";
+import * as noClasses from "~/rules/no-classes";
+import * as noConditionalStatements from "~/rules/no-conditional-statements";
+import * as noExpressionStatements from "~/rules/no-expression-statements";
+import * as noLet from "~/rules/no-let";
+import * as noLoopStatements from "~/rules/no-loop-statements";
+import * as noMixedTypes from "~/rules/no-mixed-types";
+import * as noPromiseReject from "~/rules/no-promise-reject";
+import * as noReturnVoid from "~/rules/no-return-void";
+import * as noThisExpressions from "~/rules/no-this-expressions";
+import * as noThrowStatements from "~/rules/no-throw-statements";
+import * as noTryStatements from "~/rules/no-try-statements";
+import * as preferImmutableTypes from "~/rules/prefer-immutable-types";
+import * as preferPropertySignatures from "~/rules/prefer-property-signatures";
+import * as preferTacit from "~/rules/prefer-tacit";
+import * as readonlyType from "~/rules/readonly-type";
+import * as typeDeclarationImmutability from "~/rules/type-declaration-immutability";
+
const config: Linter.Config = {
rules: {
- "functional/functional-parameters": "error",
- "functional/immutable-data": "error",
- "functional/no-class": "error",
- "functional/no-conditional-statement": "error",
- "functional/no-expression-statement": "error",
- "functional/no-let": "error",
- "functional/no-loop-statement": "error",
- "functional/no-promise-reject": "error",
- "functional/no-this-expression": "error",
- "functional/no-throw-statement": "error",
- "functional/no-try-statement": "error",
- "functional/prefer-tacit": ["warn", { assumeTypes: { allowFixer: false } }],
+ [`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", { assumeTypes: true }],
+ [`functional/${readonlyType.name}`]: "error",
+ [`functional/${typeDeclarationImmutability.name}`]: "error",
},
- overrides: [
- {
- files: ["*.ts", "*.tsx"],
- rules: {
- "functional/no-method-signature": "error",
- "functional/no-mixed-type": "error",
- "functional/prefer-readonly-type": "error",
- "functional/prefer-tacit": ["error", { assumeTypes: false }],
- "functional/no-return-void": "error",
- },
- },
- ],
};
export default config;
diff --git a/src/configs/currying.ts b/src/configs/currying.ts
index 856c0b2f4..154860c58 100644
--- a/src/configs/currying.ts
+++ b/src/configs/currying.ts
@@ -1,8 +1,10 @@
import type { Linter } from "eslint";
+import * as functionalParameters from "~/rules/functional-parameters";
+
const config: Linter.Config = {
rules: {
- "functional/functional-parameters": "error",
+ [`functional/${functionalParameters.name}`]: "error",
},
};
diff --git a/src/configs/deprecated.ts b/src/configs/deprecated.ts
new file mode 100644
index 000000000..97be073eb
--- /dev/null
+++ b/src/configs/deprecated.ts
@@ -0,0 +1,11 @@
+import type { Linter } from "eslint";
+
+import * as preferReadonlyType from "~/rules/prefer-readonly-type";
+
+const config: Linter.Config = {
+ rules: {
+ [`functional/${preferReadonlyType.name}`]: "warn",
+ },
+};
+
+export default config;
diff --git a/src/configs/external-recommended.ts b/src/configs/external-recommended.ts
deleted file mode 100644
index 9a2e2de91..000000000
--- a/src/configs/external-recommended.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import type { Linter } from "eslint";
-
-const config: Linter.Config = {
- rules: {
- "prefer-const": "error",
- "no-param-reassign": "error",
- "no-var": "error",
- },
- overrides: [
- {
- files: ["*.ts", "*.tsx"],
- rules: {
- "@typescript-eslint/prefer-readonly": "error",
- "@typescript-eslint/prefer-readonly-parameter-types": "warn",
- "@typescript-eslint/switch-exhaustiveness-check": "error",
- },
- },
- ],
-};
-
-export default config;
diff --git a/src/configs/external-typescript-recommended.ts b/src/configs/external-typescript-recommended.ts
new file mode 100644
index 000000000..de2d6188b
--- /dev/null
+++ b/src/configs/external-typescript-recommended.ts
@@ -0,0 +1,18 @@
+import type { Linter } from "eslint";
+
+import externalVanillaRecommended from "~/configs/external-vanilla-recommended";
+import { mergeConfigs } from "~/utils/merge-configs";
+
+const tsConfig: Linter.Config = {
+ rules: {
+ "@typescript-eslint/prefer-readonly": "error",
+ "@typescript-eslint/switch-exhaustiveness-check": "error",
+ },
+};
+
+const fullConfig: Linter.Config = mergeConfigs(
+ externalVanillaRecommended,
+ tsConfig
+);
+
+export default fullConfig;
diff --git a/src/configs/external-vanilla-recommended.ts b/src/configs/external-vanilla-recommended.ts
new file mode 100644
index 000000000..d960b48c4
--- /dev/null
+++ b/src/configs/external-vanilla-recommended.ts
@@ -0,0 +1,11 @@
+import type { Linter } from "eslint";
+
+const config: Linter.Config = {
+ rules: {
+ "prefer-const": "error",
+ "no-param-reassign": "error",
+ "no-var": "error",
+ },
+};
+
+export default config;
diff --git a/src/configs/functional-lite.ts b/src/configs/functional-lite.ts
deleted file mode 100644
index d62244f71..000000000
--- a/src/configs/functional-lite.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { deepmerge } from "deepmerge-ts";
-import type { Linter } from "eslint";
-
-import functional from "./functional";
-
-const overrides: Linter.Config = {
- rules: {
- "functional/immutable-data": ["error", { ignoreClass: "fieldsOnly" }],
- "functional/no-conditional-statement": "off",
- "functional/no-expression-statement": "off",
- "functional/no-try-statement": "off",
- "functional/functional-parameters": [
- "error",
- {
- enforceParameterCount: false,
- },
- ],
- },
-};
-
-const config: Linter.Config = deepmerge(functional, overrides);
-
-export default config;
diff --git a/src/configs/functional.ts b/src/configs/functional.ts
deleted file mode 100644
index 8314cba97..000000000
--- a/src/configs/functional.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import { deepmerge } from "deepmerge-ts";
-import type { Linter } from "eslint";
-
-import currying from "~/configs/currying";
-import noExceptions from "~/configs/no-exceptions";
-import noMutations from "~/configs/no-mutations";
-import noObjectOrientation from "~/configs/no-object-orientation";
-import noStatements from "~/configs/no-statements";
-import stylistic from "~/configs/stylistic";
-
-const overrides: Linter.Config = {
- rules: {
- "functional/prefer-tacit": "off",
- },
- overrides: [
- {
- files: ["*.ts", "*.tsx"],
- rules: {
- "functional/prefer-tacit": "off",
- },
- },
- ],
-};
-
-const config: Linter.Config = deepmerge(
- currying,
- noMutations,
- noExceptions,
- noObjectOrientation,
- noStatements,
- stylistic,
- overrides
-);
-
-export default config;
diff --git a/src/configs/lite.ts b/src/configs/lite.ts
new file mode 100644
index 000000000..643db9c14
--- /dev/null
+++ b/src/configs/lite.ts
@@ -0,0 +1,41 @@
+import type { Linter } from "eslint";
+
+import * as functionalParameters from "~/rules/functional-parameters";
+import * as immutableData from "~/rules/immutable-data";
+import * as noConditionalStatements from "~/rules/no-conditional-statements";
+import * as noExpressionStatements from "~/rules/no-expression-statements";
+import * as preferImmutableTypes from "~/rules/prefer-immutable-types";
+import { mergeConfigs } from "~/utils/merge-configs";
+
+import recommended from "./recommended";
+
+const overrides: Linter.Config = {
+ rules: {
+ [`functional/${functionalParameters.name}`]: [
+ "error",
+ {
+ enforceParameterCount: false,
+ },
+ ],
+ [`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",
+ },
+ },
+ ],
+ },
+};
+
+const config: Linter.Config = mergeConfigs(recommended, overrides);
+
+export default config;
diff --git a/src/configs/no-exceptions.ts b/src/configs/no-exceptions.ts
index bbac17993..8fec821b0 100644
--- a/src/configs/no-exceptions.ts
+++ b/src/configs/no-exceptions.ts
@@ -1,9 +1,12 @@
import type { Linter } from "eslint";
+import * as noThrowStatements from "~/rules/no-throw-statements";
+import * as noTryStatements from "~/rules/no-try-statements";
+
const config: Linter.Config = {
rules: {
- "functional/no-throw-statement": "error",
- "functional/no-try-statement": "error",
+ [`functional/${noThrowStatements.name}`]: "error",
+ [`functional/${noTryStatements.name}`]: "error",
},
};
diff --git a/src/configs/no-mutations.ts b/src/configs/no-mutations.ts
index bc5cc4600..ec0ebf2eb 100644
--- a/src/configs/no-mutations.ts
+++ b/src/configs/no-mutations.ts
@@ -1,19 +1,17 @@
import type { Linter } from "eslint";
+import * as immutableData from "~/rules/immutable-data";
+import * as noLet from "~/rules/no-let";
+import * as preferImmutableTypes from "~/rules/prefer-immutable-types";
+import * as typeDeclarationImmutability from "~/rules/type-declaration-immutability";
+
const config: Linter.Config = {
rules: {
- "functional/no-let": "error",
- "functional/immutable-data": "error",
+ [`functional/${immutableData.name}`]: "error",
+ [`functional/${noLet.name}`]: "error",
+ [`functional/${preferImmutableTypes.name}`]: "error",
+ [`functional/${typeDeclarationImmutability.name}`]: "error",
},
- overrides: [
- {
- files: ["*.ts", "*.tsx"],
- rules: {
- "functional/no-method-signature": "warn",
- "functional/prefer-readonly-type": "error",
- },
- },
- ],
};
export default config;
diff --git a/src/configs/no-object-orientation.ts b/src/configs/no-object-orientation.ts
deleted file mode 100644
index 4681ba459..000000000
--- a/src/configs/no-object-orientation.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import type { Linter } from "eslint";
-
-const config: Linter.Config = {
- rules: {
- "functional/no-this-expression": "error",
- "functional/no-class": "error",
- },
- overrides: [
- {
- files: ["*.ts", "*.tsx"],
- rules: {
- "functional/no-mixed-type": "error",
- },
- },
- ],
-};
-
-export default config;
diff --git a/src/configs/no-other-paradigms.ts b/src/configs/no-other-paradigms.ts
new file mode 100644
index 000000000..5d7f114a6
--- /dev/null
+++ b/src/configs/no-other-paradigms.ts
@@ -0,0 +1,15 @@
+import type { Linter } from "eslint";
+
+import * as noClasses from "~/rules/no-classes";
+import * as noMixedTypes from "~/rules/no-mixed-types";
+import * as noThisExpressions from "~/rules/no-this-expressions";
+
+const config: Linter.Config = {
+ rules: {
+ [`functional/${noClasses.name}`]: "error",
+ [`functional/${noMixedTypes.name}`]: "error",
+ [`functional/${noThisExpressions.name}`]: "error",
+ },
+};
+
+export default config;
diff --git a/src/configs/no-statements.ts b/src/configs/no-statements.ts
index 6f4d55487..d394c99b3 100644
--- a/src/configs/no-statements.ts
+++ b/src/configs/no-statements.ts
@@ -1,19 +1,17 @@
import type { Linter } from "eslint";
+import * as noConditionalStatements from "~/rules/no-conditional-statements";
+import * as noExpressionStatements from "~/rules/no-expression-statements";
+import * as noLoopStatements from "~/rules/no-loop-statements";
+import * as noReturnVoid from "~/rules/no-return-void";
+
const config: Linter.Config = {
rules: {
- "functional/no-expression-statement": "error",
- "functional/no-conditional-statement": "error",
- "functional/no-loop-statement": "error",
+ [`functional/${noConditionalStatements.name}`]: "error",
+ [`functional/${noExpressionStatements.name}`]: "error",
+ [`functional/${noLoopStatements.name}`]: "error",
+ [`functional/${noReturnVoid.name}`]: "error",
},
- overrides: [
- {
- files: ["*.ts", "*.tsx"],
- rules: {
- "functional/no-return-void": "error",
- },
- },
- ],
};
export default config;
diff --git a/src/configs/off.ts b/src/configs/off.ts
index bf3bda5a1..a9865030c 100644
--- a/src/configs/off.ts
+++ b/src/configs/off.ts
@@ -1,11 +1,12 @@
import type { Linter } from "eslint";
import all from "./all";
+import deprecated from "./deprecated";
/**
* Turn the given rules off.
*/
-function turnRulesOff(rules: ReadonlyArray): Linter.Config["rules"] {
+function turnRulesOff(rules: string[] | undefined): Linter.Config["rules"] {
return rules === undefined
? undefined
: Object.fromEntries(rules.map((name) => [name, "off"]));
@@ -13,8 +14,7 @@ function turnRulesOff(rules: ReadonlyArray): Linter.Config["rules"] {
const allRulesNames = new Set([
...Object.keys(all.rules ?? {}),
- ...(all.overrides?.flatMap((override) => Object.keys(override.rules ?? {})) ??
- []),
+ ...Object.keys(deprecated.rules ?? {}),
]);
const config: Linter.Config = {
diff --git a/src/configs/recommended.ts b/src/configs/recommended.ts
new file mode 100644
index 000000000..4368dd736
--- /dev/null
+++ b/src/configs/recommended.ts
@@ -0,0 +1,110 @@
+import type { Linter } from "eslint";
+import { Immutability } from "is-immutable-type";
+
+import * as functionalParameters from "~/rules/functional-parameters";
+import * as noConditionalStatements from "~/rules/no-conditional-statements";
+import * as noLet from "~/rules/no-let";
+import * as noThisExpressions from "~/rules/no-this-expressions";
+import * as noThrowStatements from "~/rules/no-throw-statements";
+import * as noTryStatements from "~/rules/no-try-statements";
+import * as preferImmutableTypes from "~/rules/prefer-immutable-types";
+import * as typeDeclarationImmutability from "~/rules/type-declaration-immutability";
+import { RuleEnforcementComparator } from "~/rules/type-declaration-immutability";
+import { mergeConfigs } from "~/utils/merge-configs";
+
+import strict from "./strict";
+
+const overrides: Linter.Config = {
+ rules: {
+ [`functional/${functionalParameters.name}`]: [
+ "error",
+ {
+ enforceParameterCount: {
+ ignoreLambdaExpression: true,
+ ignoreIIFE: true,
+ },
+ },
+ ],
+ [`functional/${noConditionalStatements.name}`]: [
+ "error",
+ {
+ allowReturningBranches: true,
+ },
+ ],
+ [`functional/${noLet.name}`]: [
+ "error",
+ {
+ allowInForLoopInit: true,
+ },
+ ],
+ [`functional/${noThisExpressions.name}`]: "off",
+ [`functional/${noThrowStatements.name}`]: [
+ "error",
+ {
+ allowInAsyncFunctions: true,
+ },
+ ],
+ [`functional/${noTryStatements.name}`]: "off",
+ [`functional/${preferImmutableTypes.name}`]: [
+ "error",
+ {
+ enforcement: "None",
+ ignoreInferredTypes: true,
+ parameters: {
+ enforcement: "ReadonlyDeep",
+ },
+ },
+ ],
+ [`functional/${typeDeclarationImmutability.name}`]: [
+ "error",
+ {
+ rules: [
+ {
+ identifiers: [/^I?Immutable.+/u],
+ immutability: Immutability.Immutable,
+ comparator: RuleEnforcementComparator.AtLeast,
+ },
+ {
+ identifiers: [/^I?ReadonlyDeep.+/u],
+ immutability: Immutability.ReadonlyDeep,
+ comparator: RuleEnforcementComparator.AtLeast,
+ },
+ {
+ identifiers: [/^I?Readonly.+/u],
+ immutability: Immutability.ReadonlyShallow,
+ comparator: RuleEnforcementComparator.AtLeast,
+ fixer: [
+ {
+ pattern: "^(Array|Map|Set)<(.+)>$",
+ replace: "Readonly$1<$2>",
+ },
+ {
+ pattern: "^(.+)$",
+ replace: "Readonly<$1>",
+ },
+ ],
+ },
+ {
+ identifiers: [/^I?Mutable.+/u],
+ 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);
+
+export default config;
diff --git a/src/configs/strict.ts b/src/configs/strict.ts
new file mode 100644
index 000000000..883bb4015
--- /dev/null
+++ b/src/configs/strict.ts
@@ -0,0 +1,18 @@
+import type { Linter } from "eslint";
+
+import currying from "~/configs/currying";
+import noExceptions from "~/configs/no-exceptions";
+import noMutations from "~/configs/no-mutations";
+import noOtherParadigms from "~/configs/no-other-paradigms";
+import noStatements from "~/configs/no-statements";
+import { mergeConfigs } from "~/utils/merge-configs";
+
+const config: Linter.Config = mergeConfigs(
+ currying,
+ noMutations,
+ noExceptions,
+ noOtherParadigms,
+ noStatements
+);
+
+export default config;
diff --git a/src/configs/stylistic.ts b/src/configs/stylistic.ts
index 0c30872c5..5a2a7389d 100644
--- a/src/configs/stylistic.ts
+++ b/src/configs/stylistic.ts
@@ -1,17 +1,15 @@
import type { Linter } from "eslint";
+import * as preferPropertySignatures from "~/rules/prefer-property-signatures";
+import * as preferTacit from "~/rules/prefer-tacit";
+import * as readonlyType from "~/rules/readonly-type";
+
const config: Linter.Config = {
rules: {
- "functional/prefer-tacit": ["warn", { assumeTypes: { allowFixer: false } }],
+ [`functional/${preferPropertySignatures.name}`]: "error",
+ [`functional/${preferTacit.name}`]: ["warn", { assumeTypes: true }],
+ [`functional/${readonlyType.name}`]: "error",
},
- overrides: [
- {
- files: ["*.ts", "*.tsx"],
- rules: {
- "functional/prefer-tacit": ["error", { assumeTypes: false }],
- },
- },
- ],
};
export default config;
diff --git a/src/index.ts b/src/index.ts
index 7a5541946..f76e76626 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -2,14 +2,16 @@ import type { Linter, Rule } from "eslint";
import all from "~/configs/all";
import currying from "~/configs/currying";
-import externalRecommended from "~/configs/external-recommended";
-import functional from "~/configs/functional";
-import functionalLite from "~/configs/functional-lite";
+import externalTypeScriptRecommended from "~/configs/external-typescript-recommended";
+import externalVanillaRecommended from "~/configs/external-vanilla-recommended";
+import lite from "~/configs/lite";
import noExceptions from "~/configs/no-exceptions";
import noMutations from "~/configs/no-mutations";
-import noObjectOrientation from "~/configs/no-object-orientation";
+import noOtherParadigms from "~/configs/no-other-paradigms";
import noStatements from "~/configs/no-statements";
import off from "~/configs/off";
+import recommended from "~/configs/recommended";
+import strict from "~/configs/strict";
import stylistic from "~/configs/stylistic";
import { rules } from "~/rules";
@@ -25,15 +27,17 @@ const config: EslintPluginConfig = {
rules,
configs: {
all,
- recommended: functional,
- "external-recommended": externalRecommended,
- lite: functionalLite,
+ lite,
+ recommended,
+ strict,
off,
- "no-mutations": noMutations,
+ "external-vanilla-recommended": externalVanillaRecommended,
+ "external-typescript-recommended": externalTypeScriptRecommended,
+ currying,
"no-exceptions": noExceptions,
- "no-object-orientation": noObjectOrientation,
+ "no-mutations": noMutations,
+ "no-other-paradigms": noOtherParadigms,
"no-statements": noStatements,
- currying,
stylistic,
},
};
diff --git a/src/common/ignore-options.ts b/src/options/ignore.ts
similarity index 58%
rename from src/common/ignore-options.ts
rename to src/options/ignore.ts
index eeb014856..7b8ab319b 100644
--- a/src/common/ignore-options.ts
+++ b/src/options/ignore.ts
@@ -1,41 +1,24 @@
import type { TSESLint, TSESTree } from "@typescript-eslint/utils";
import escapeRegExp from "escape-string-regexp";
import type { JSONSchema4 } from "json-schema";
-import type { ReadonlyDeep } from "type-fest";
-import { getNodeIdentifierTexts } from "~/util/misc";
-import type { BaseOptions } from "~/util/rule";
-import { inClass, inFunctionBody, inInterface } from "~/util/tree";
+import { getNodeIdentifierTexts } from "~/utils/misc";
+import type { BaseOptions } from "~/utils/rule";
+import { isInClass, isInFunctionBody } from "~/utils/tree";
import {
isAssignmentExpression,
+ isClassLike,
isPropertyDefinition,
isMemberExpression,
- isReadonlyArray,
isThisExpression,
-} from "~/util/typeguard";
-
-/**
- * The option to allow local mutations.
- */
-export type AllowLocalMutationOption = {
- readonly allowLocalMutation: boolean;
-};
-
-/**
- * The schema for the option to allow local mutations.
- */
-export const allowLocalMutationOptionSchema: JSONSchema4["properties"] = {
- allowLocalMutation: {
- type: "boolean",
- },
-};
+} from "~/utils/type-guards";
/**
* The option to ignore patterns.
*/
-export type IgnorePatternOption = {
- readonly ignorePattern?: ReadonlyArray | string;
-};
+export type IgnorePatternOption = Readonly<{
+ ignorePattern?: string[] | string;
+}>;
/**
* The schema for the option to ignore patterns.
@@ -52,9 +35,9 @@ export const ignorePatternOptionSchema: JSONSchema4["properties"] = {
/**
* The option to ignore accessor patterns.
*/
-export type IgnoreAccessorPatternOption = {
- readonly ignoreAccessorPattern?: ReadonlyArray | string;
-};
+export type IgnoreAccessorPatternOption = Readonly<{
+ ignoreAccessorPattern?: string[] | string;
+}>;
/**
* The schema for the option to ignore accessor patterns.
@@ -71,15 +54,15 @@ export const ignoreAccessorPatternOptionSchema: JSONSchema4["properties"] = {
/**
* The option to ignore classes.
*/
-export type IgnoreClassOption = {
- readonly ignoreClass: boolean | "fieldsOnly";
-};
+export type IgnoreClassesOption = Readonly<{
+ ignoreClasses: boolean | "fieldsOnly";
+}>;
/**
* The schema for the option to ignore classes.
*/
-export const ignoreClassOptionSchema: JSONSchema4["properties"] = {
- ignoreClass: {
+export const ignoreClassesOptionSchema: JSONSchema4["properties"] = {
+ ignoreClasses: {
oneOf: [
{
type: "boolean",
@@ -92,28 +75,12 @@ export const ignoreClassOptionSchema: JSONSchema4["properties"] = {
},
};
-/**
- * The option to ignore interfaces.
- */
-export type IgnoreInterfaceOption = {
- readonly ignoreInterface: boolean;
-};
-
-/**
- * The schema for the option to ignore interfaces.
- */
-export const ignoreInterfaceOptionSchema: JSONSchema4["properties"] = {
- ignoreInterface: {
- type: "boolean",
- },
-};
-
/**
* The option to ignore prefix selector.
*/
-export type IgnorePrefixSelectorOption = {
- readonly ignorePrefixSelector?: ReadonlyArray | string;
-};
+export type IgnorePrefixSelectorOption = Readonly<{
+ ignorePrefixSelector?: string[] | string;
+}>;
/**
* The schema for the option to ignore prefix selector.
@@ -134,9 +101,9 @@ export const ignorePrefixSelectorOptionSchema: JSONSchema4["properties"] = {
*/
function shouldIgnoreViaPattern(
text: string,
- ignorePattern: ReadonlyArray | string
+ ignorePattern: string[] | string
): boolean {
- const patterns: ReadonlyArray = isReadonlyArray(ignorePattern)
+ const patterns = Array.isArray(ignorePattern)
? ignorePattern
: [ignorePattern];
@@ -152,8 +119,8 @@ function shouldIgnoreViaPattern(
* Does the given text match the given pattern.
*/
function accessorPatternMatch(
- [pattern, ...remainingPatternParts]: ReadonlyArray,
- textParts: ReadonlyArray,
+ [pattern, ...remainingPatternParts]: string[],
+ textParts: string[],
allowExtra = false
): boolean {
return pattern === undefined
@@ -181,9 +148,9 @@ function accessorPatternMatch(
)
: // Text matches pattern?
new RegExp(
- `^${escapeRegExp(pattern).replace(/\\\*/gu, ".*")}$`,
+ `^${escapeRegExp(pattern).replaceAll("\\*", ".*")}$`,
"u"
- ).test(textParts[0]) &&
+ ).test(textParts[0]!) &&
accessorPatternMatch(
remainingPatternParts,
textParts.slice(1),
@@ -198,9 +165,9 @@ function accessorPatternMatch(
*/
function shouldIgnoreViaAccessorPattern(
text: string,
- ignorePattern: ReadonlyArray | string
+ ignorePattern: string[] | string
): boolean {
- const patterns: ReadonlyArray = isReadonlyArray(ignorePattern)
+ const patterns = Array.isArray(ignorePattern)
? ignorePattern
: [ignorePattern];
@@ -213,50 +180,37 @@ function shouldIgnoreViaAccessorPattern(
/**
* Should the given node be allowed base off the following rule options?
*
- * - AllowLocalMutationOption.
+ * - AllowInFunctionOption.
*/
-export function shouldIgnoreLocalMutation(
- node: ReadonlyDeep,
- context: ReadonlyDeep>,
- { allowLocalMutation }: Partial
+export function shouldIgnoreInFunction(
+ node: TSESTree.Node,
+ context: TSESLint.RuleContext,
+ allowInFunction: boolean | undefined
): boolean {
- return allowLocalMutation === true && inFunctionBody(node);
+ return allowInFunction === true && isInFunctionBody(node);
}
/**
* Should the given node be allowed base off the following rule options?
*
- * - IgnoreClassOption.
+ * - IgnoreClassesOption.
*/
-export function shouldIgnoreClass(
- node: ReadonlyDeep,
- context: ReadonlyDeep>,
- { ignoreClass }: Partial
+export function shouldIgnoreClasses(
+ node: TSESTree.Node,
+ context: TSESLint.RuleContext,
+ ignoreClasses: Partial["ignoreClasses"]
): boolean {
return (
- (ignoreClass === true && inClass(node)) ||
- (ignoreClass === "fieldsOnly" &&
+ (ignoreClasses === true && (isClassLike(node) || isInClass(node))) ||
+ (ignoreClasses === "fieldsOnly" &&
(isPropertyDefinition(node) ||
(isAssignmentExpression(node) &&
- inClass(node) &&
+ isInClass(node) &&
isMemberExpression(node.left) &&
isThisExpression(node.left.object))))
);
}
-/**
- * Should the given node be allowed base off the following rule options?
- *
- * - IgnoreInterfaceOption.
- */
-export function shouldIgnoreInterface(
- node: ReadonlyDeep,
- context: ReadonlyDeep>,
- { ignoreInterface }: Partial
-): boolean {
- return ignoreInterface === true && inInterface(node);
-}
-
/**
* Should the given node be allowed base off the following rule options?
*
@@ -264,12 +218,10 @@ export function shouldIgnoreInterface(
* - IgnorePatternOption.
*/
export function shouldIgnorePattern(
- node: ReadonlyDeep,
- context: ReadonlyDeep>,
- {
- ignorePattern,
- ignoreAccessorPattern,
- }: Partial
+ node: TSESTree.Node,
+ context: TSESLint.RuleContext,
+ ignorePattern: Partial["ignorePattern"],
+ ignoreAccessorPattern?: Partial["ignoreAccessorPattern"]
): boolean {
const texts = getNodeIdentifierTexts(node, context);
diff --git a/src/options/index.ts b/src/options/index.ts
new file mode 100644
index 000000000..a2ca8f7c3
--- /dev/null
+++ b/src/options/index.ts
@@ -0,0 +1 @@
+export * from "./ignore";
diff --git a/src/rules/functional-parameters.ts b/src/rules/functional-parameters.ts
index 136cf292b..b2e84edb5 100644
--- a/src/rules/functional-parameters.ts
+++ b/src/rules/functional-parameters.ts
@@ -1,22 +1,26 @@
-import type { ESLintUtils, TSESLint, TSESTree } from "@typescript-eslint/utils";
+import type { TSESLint, TSESTree } from "@typescript-eslint/utils";
import { deepmerge } from "deepmerge-ts";
import type { JSONSchema4 } from "json-schema";
-import type { ReadonlyDeep } from "type-fest";
import type {
IgnorePatternOption,
IgnorePrefixSelectorOption,
-} from "~/common/ignore-options";
+} from "~/options";
import {
shouldIgnorePattern,
ignorePatternOptionSchema,
ignorePrefixSelectorOptionSchema,
-} from "~/common/ignore-options";
-import type { ESFunction } from "~/src/util/node-types";
-import type { RuleResult } from "~/util/rule";
-import { createRuleUsingFunction } from "~/util/rule";
-import { isIIFE, isPropertyAccess, isPropertyName } from "~/util/tree";
-import { isRestElement } from "~/util/typeguard";
+} from "~/options";
+import type { ESFunction } from "~/utils/node-types";
+import type { RuleResult, NamedCreateRuleMetaWithCategory } from "~/utils/rule";
+import { createRuleUsingFunction } from "~/utils/rule";
+import {
+ isArgument,
+ isIIFE,
+ isPropertyAccess,
+ isPropertyName,
+} from "~/utils/tree";
+import { isRestElement } from "~/utils/type-guards";
/**
* The name of this rule.
@@ -31,20 +35,20 @@ type ParameterCountOptions = "atLeastOne" | "exactlyOne";
/**
* The options this rule can take.
*/
-type Options = readonly [
+type Options = [
IgnorePatternOption &
- IgnorePrefixSelectorOption &
- Readonly<{
+ IgnorePrefixSelectorOption & {
allowRestParameter: boolean;
allowArgumentsKeyword: boolean;
enforceParameterCount:
| ParameterCountOptions
| false
- | Readonly<{
+ | {
count: ParameterCountOptions;
+ ignoreLambdaExpression: boolean;
ignoreIIFE: boolean;
- }>;
- }>
+ };
+ }
];
/**
@@ -80,6 +84,9 @@ const schema: JSONSchema4 = [
type: "string",
enum: ["atLeastOne", "exactlyOne"],
},
+ ignoreLambdaExpression: {
+ type: "boolean",
+ },
ignoreIIFE: {
type: "boolean",
},
@@ -103,6 +110,7 @@ const defaultOptions: Options = [
allowArgumentsKeyword: false,
enforceParameterCount: {
count: "atLeastOne",
+ ignoreLambdaExpression: false,
ignoreIIFE: true,
},
},
@@ -123,9 +131,10 @@ const errorMessages = {
/**
* The meta data for this rule.
*/
-const meta: ESLintUtils.NamedCreateRuleMeta = {
+const meta: NamedCreateRuleMetaWithCategory = {
type: "suggestion",
docs: {
+ category: "Currying",
description: "Enforce functional parameters.",
recommended: "error",
},
@@ -138,14 +147,14 @@ const meta: ESLintUtils.NamedCreateRuleMeta = {
*/
function getRestParamViolations(
[{ allowRestParameter }]: Options,
- node: ReadonlyDeep
+ node: ESFunction
): RuleResult["descriptors"] {
return !allowRestParameter &&
node.params.length > 0 &&
- isRestElement(node.params[node.params.length - 1])
+ isRestElement(node.params.at(-1)!)
? [
{
- node: node.params[node.params.length - 1],
+ node: node.params.at(-1)!,
messageId: "restParam",
},
]
@@ -157,14 +166,14 @@ function getRestParamViolations(
*/
function getParamCountViolations(
[{ enforceParameterCount }]: Options,
- node: ReadonlyDeep
+ node: ESFunction
): RuleResult["descriptors"] {
if (
enforceParameterCount === false ||
(node.params.length === 0 &&
typeof enforceParameterCount === "object" &&
- enforceParameterCount.ignoreIIFE &&
- isIIFE(node))
+ ((enforceParameterCount.ignoreIIFE && isIIFE(node)) ||
+ (enforceParameterCount.ignoreLambdaExpression && isArgument(node))))
) {
return [];
}
@@ -201,15 +210,14 @@ function getParamCountViolations(
* Check if the given function node has a reset parameter this rule.
*/
function checkFunction(
- node: ReadonlyDeep,
- context: ReadonlyDeep<
- TSESLint.RuleContext
- >,
+ node: ESFunction,
+ context: TSESLint.RuleContext,
options: Options
): RuleResult {
const [optionsObject] = options;
+ const { ignorePattern } = optionsObject;
- if (shouldIgnorePattern(node, context, optionsObject)) {
+ if (shouldIgnorePattern(node, context, ignorePattern)) {
return {
context,
descriptors: [],
@@ -229,15 +237,14 @@ function checkFunction(
* Check if the given identifier is for the "arguments" keyword.
*/
function checkIdentifier(
- node: ReadonlyDeep,
- context: ReadonlyDeep<
- TSESLint.RuleContext
- >,
+ node: TSESTree.Identifier,
+ context: TSESLint.RuleContext,
options: Options
): RuleResult {
const [optionsObject] = options;
+ const { ignorePattern } = optionsObject;
- if (shouldIgnorePattern(node, context, optionsObject)) {
+ if (shouldIgnorePattern(node, context, ignorePattern)) {
return {
context,
descriptors: [],
@@ -277,7 +284,7 @@ export const rule = createRuleUsingFunction<
"FunctionExpression",
];
- const ignoreSelectors: ReadonlyArray | undefined =
+ const ignoreSelectors =
ignorePrefixSelector === undefined
? undefined
: Array.isArray(ignorePrefixSelector)
diff --git a/src/rules/immutable-data.ts b/src/rules/immutable-data.ts
index afe5a2ecf..dd842bc5f 100644
--- a/src/rules/immutable-data.ts
+++ b/src/rules/immutable-data.ts
@@ -1,24 +1,23 @@
-import type { ESLintUtils, TSESLint, TSESTree } from "@typescript-eslint/utils";
+import type { TSESLint, TSESTree } from "@typescript-eslint/utils";
import { deepmerge } from "deepmerge-ts";
import type { JSONSchema4 } from "json-schema";
-import type { ReadonlyDeep } from "type-fest";
import type {
IgnoreAccessorPatternOption,
IgnorePatternOption,
- IgnoreClassOption,
-} from "~/common/ignore-options";
+ IgnoreClassesOption,
+} from "~/options";
import {
shouldIgnorePattern,
- shouldIgnoreClass,
+ shouldIgnoreClasses,
ignoreAccessorPatternOptionSchema,
- ignoreClassOptionSchema,
+ ignoreClassesOptionSchema,
ignorePatternOptionSchema,
-} from "~/common/ignore-options";
-import { isExpected } from "~/util/misc";
-import { createRule, getTypeOfNode } from "~/util/rule";
-import type { RuleResult } from "~/util/rule";
-import { inConstructor } from "~/util/tree";
+} from "~/options";
+import { isExpected } from "~/utils/misc";
+import { createRule, getTypeOfNode } from "~/utils/rule";
+import type { RuleResult, NamedCreateRuleMetaWithCategory } from "~/utils/rule";
+import { isInConstructor } from "~/utils/tree";
import {
isArrayConstructorType,
isArrayExpression,
@@ -28,7 +27,7 @@ import {
isMemberExpression,
isNewExpression,
isObjectConstructorType,
-} from "~/util/typeguard";
+} from "~/utils/type-guards";
/**
* The name of this rule.
@@ -38,19 +37,18 @@ export const name = "immutable-data" as const;
/**
* The options this rule can take.
*/
-type Options = readonly [
+type Options = [
IgnoreAccessorPatternOption &
- IgnoreClassOption &
- IgnorePatternOption &
- Readonly<{
+ IgnoreClassesOption &
+ IgnorePatternOption & {
ignoreImmediateMutation: boolean;
assumeTypes:
| boolean
- | Readonly<{
+ | {
forArrays: boolean;
forObjects: boolean;
- }>;
- }>
+ };
+ }
];
/**
@@ -62,7 +60,7 @@ const schema: JSONSchema4 = [
properties: deepmerge(
ignorePatternOptionSchema,
ignoreAccessorPatternOptionSchema,
- ignoreClassOptionSchema,
+ ignoreClassesOptionSchema,
{
ignoreImmediateMutation: {
type: "boolean",
@@ -97,7 +95,7 @@ const schema: JSONSchema4 = [
*/
const defaultOptions: Options = [
{
- ignoreClass: false,
+ ignoreClasses: false,
ignoreImmediateMutation: true,
assumeTypes: {
forArrays: true,
@@ -118,9 +116,10 @@ const errorMessages = {
/**
* The meta data for this rule.
*/
-const meta: ESLintUtils.NamedCreateRuleMeta = {
+const meta: NamedCreateRuleMetaWithCategory = {
type: "suggestion",
docs: {
+ category: "No Mutations",
description: "Enforce treating data as immutable.",
recommended: "error",
},
@@ -183,18 +182,17 @@ const objectConstructorMutatorFunctions = new Set([
* Check if the given assignment expression violates this rule.
*/
function checkAssignmentExpression(
- node: ReadonlyDeep,
- context: ReadonlyDeep<
- TSESLint.RuleContext
- >,
+ node: TSESTree.AssignmentExpression,
+ context: TSESLint.RuleContext,
options: Options
): RuleResult {
const [optionsObject] = options;
+ const { ignorePattern, ignoreAccessorPattern, ignoreClasses } = optionsObject;
if (
!isMemberExpression(node.left) ||
- shouldIgnoreClass(node, context, optionsObject) ||
- shouldIgnorePattern(node, context, optionsObject)
+ shouldIgnoreClasses(node, context, ignoreClasses) ||
+ shouldIgnorePattern(node, context, ignorePattern, ignoreAccessorPattern)
) {
return {
context,
@@ -206,7 +204,7 @@ function checkAssignmentExpression(
context,
descriptors:
// Allow if in a constructor - allow for field initialization.
- !inConstructor(node) ? [{ node, messageId: "generic" }] : [],
+ isInConstructor(node) ? [] : [{ node, messageId: "generic" }],
};
}
@@ -214,18 +212,17 @@ function checkAssignmentExpression(
* Check if the given node violates this rule.
*/
function checkUnaryExpression(
- node: ReadonlyDeep,
- context: ReadonlyDeep<
- TSESLint.RuleContext
- >,
+ node: TSESTree.UnaryExpression,
+ context: TSESLint.RuleContext,
options: Options
): RuleResult {
const [optionsObject] = options;
+ const { ignorePattern, ignoreAccessorPattern, ignoreClasses } = optionsObject;
if (
!isMemberExpression(node.argument) ||
- shouldIgnoreClass(node, context, optionsObject) ||
- shouldIgnorePattern(node, context, optionsObject)
+ shouldIgnoreClasses(node, context, ignoreClasses) ||
+ shouldIgnorePattern(node, context, ignorePattern, ignoreAccessorPattern)
) {
return {
context,
@@ -244,18 +241,22 @@ function checkUnaryExpression(
* Check if the given node violates this rule.
*/
function checkUpdateExpression(
- node: ReadonlyDeep,
- context: ReadonlyDeep<
- TSESLint.RuleContext
- >,
+ node: TSESTree.UpdateExpression,
+ context: TSESLint.RuleContext,
options: Options
): RuleResult {
const [optionsObject] = options;
+ const { ignorePattern, ignoreAccessorPattern, ignoreClasses } = optionsObject;
if (
!isMemberExpression(node.argument) ||
- shouldIgnoreClass(node.argument, context, optionsObject) ||
- shouldIgnorePattern(node.argument, context, optionsObject)
+ shouldIgnoreClasses(node.argument, context, ignoreClasses) ||
+ shouldIgnorePattern(
+ node.argument,
+ context,
+ ignorePattern,
+ ignoreAccessorPattern
+ )
) {
return {
context,
@@ -277,10 +278,8 @@ function checkUpdateExpression(
* a mutator method call.
*/
function isInChainCallAndFollowsNew(
- node: ReadonlyDeep,
- context: ReadonlyDeep<
- TSESLint.RuleContext
- >,
+ node: TSESTree.MemberExpression,
+ context: TSESLint.RuleContext,
assumeArrayTypes: boolean
): boolean {
return (
@@ -289,10 +288,9 @@ function isInChainCallAndFollowsNew(
// Check for: new Array()
(isNewExpression(node.object) &&
isArrayConstructorType(
- // `isNewExpression` type guard doesn't seem to be working? so use `as`.
- getTypeOfNode((node.object as TSESTree.NewExpression).callee, context),
+ getTypeOfNode(node.object.callee, context),
assumeArrayTypes,
- (node.object as TSESTree.NewExpression).callee
+ node.object.callee
)) ||
(isCallExpression(node.object) &&
isMemberExpression(node.object.callee) &&
@@ -317,20 +315,24 @@ function isInChainCallAndFollowsNew(
* Check if the given node violates this rule.
*/
function checkCallExpression(
- node: ReadonlyDeep,
- context: ReadonlyDeep<
- TSESLint.RuleContext
- >,
+ node: TSESTree.CallExpression,
+ context: TSESLint.RuleContext,
options: Options
): RuleResult {
const [optionsObject] = options;
+ const { ignorePattern, ignoreAccessorPattern, ignoreClasses } = optionsObject;
// Not potential object mutation?
if (
!isMemberExpression(node.callee) ||
!isIdentifier(node.callee.property) ||
- shouldIgnoreClass(node.callee.object, context, optionsObject) ||
- shouldIgnorePattern(node.callee.object, context, optionsObject)
+ shouldIgnoreClasses(node.callee.object, context, ignoreClasses) ||
+ shouldIgnorePattern(
+ node.callee.object,
+ context,
+ ignorePattern,
+ ignoreAccessorPattern
+ )
) {
return {
context,
@@ -373,10 +375,15 @@ function checkCallExpression(
if (
objectConstructorMutatorFunctions.has(node.callee.property.name) &&
node.arguments.length >= 2 &&
- (isIdentifier(node.arguments[0]) ||
- isMemberExpression(node.arguments[0])) &&
- !shouldIgnoreClass(node.arguments[0], context, optionsObject) &&
- !shouldIgnorePattern(node.arguments[0], context, optionsObject) &&
+ (isIdentifier(node.arguments[0]!) ||
+ isMemberExpression(node.arguments[0]!)) &&
+ !shouldIgnoreClasses(node.arguments[0], context, ignoreClasses) &&
+ !shouldIgnorePattern(
+ node.arguments[0],
+ context,
+ ignorePattern,
+ ignoreAccessorPattern
+ ) &&
isObjectConstructorType(
getTypeOfNode(node.callee.object, context),
assumeTypesForObjects,
diff --git a/src/rules/index.ts b/src/rules/index.ts
index 06eb7baef..7abddf629 100644
--- a/src/rules/index.ts
+++ b/src/rules/index.ts
@@ -1,19 +1,22 @@
import * as functionalParameters from "./functional-parameters";
import * as immutableData from "./immutable-data";
-import * as noClass from "./no-class";
-import * as noConditionalStatement from "./no-conditional-statement";
-import * as noExpressionStatement from "./no-expression-statement";
+import * as noClasses from "./no-classes";
+import * as noConditionalStatements from "./no-conditional-statements";
+import * as noExpressionStatements from "./no-expression-statements";
import * as noLet from "./no-let";
-import * as noLoop from "./no-loop-statement";
-import * as noMethodSignature from "./no-method-signature";
-import * as noMixedType from "./no-mixed-type";
+import * as noLoopStatements from "./no-loop-statements";
+import * as noMixedTypes from "./no-mixed-types";
import * as noPromiseReject from "./no-promise-reject";
import * as noReturnVoid from "./no-return-void";
-import * as noThisExpression from "./no-this-expression";
-import * as noThrowStatement from "./no-throw-statement";
-import * as noTryStatement from "./no-try-statement";
+import * as noThisExpressions from "./no-this-expressions";
+import * as noThrowStatements from "./no-throw-statements";
+import * as noTryStatements from "./no-try-statements";
+import * as preferImmutableTypes from "./prefer-immutable-types";
+import * as preferPropertySignatures from "./prefer-property-signatures";
import * as preferReadonlyTypes from "./prefer-readonly-type";
import * as preferTacit from "./prefer-tacit";
+import * as readonlyType from "./readonly-type";
+import * as typeDeclarationImmutability from "./type-declaration-immutability";
/**
* All of the custom rules.
@@ -21,18 +24,21 @@ import * as preferTacit from "./prefer-tacit";
export const rules = {
[functionalParameters.name]: functionalParameters.rule,
[immutableData.name]: immutableData.rule,
- [noClass.name]: noClass.rule,
- [noConditionalStatement.name]: noConditionalStatement.rule,
- [noExpressionStatement.name]: noExpressionStatement.rule,
+ [noClasses.name]: noClasses.rule,
+ [noConditionalStatements.name]: noConditionalStatements.rule,
+ [noExpressionStatements.name]: noExpressionStatements.rule,
[noLet.name]: noLet.rule,
- [noLoop.name]: noLoop.rule,
- [noMethodSignature.name]: noMethodSignature.rule,
- [noMixedType.name]: noMixedType.rule,
+ [noLoopStatements.name]: noLoopStatements.rule,
+ [noMixedTypes.name]: noMixedTypes.rule,
[noPromiseReject.name]: noPromiseReject.rule,
[noReturnVoid.name]: noReturnVoid.rule,
- [noThisExpression.name]: noThisExpression.rule,
- [noThrowStatement.name]: noThrowStatement.rule,
- [noTryStatement.name]: noTryStatement.rule,
+ [noThisExpressions.name]: noThisExpressions.rule,
+ [noThrowStatements.name]: noThrowStatements.rule,
+ [noTryStatements.name]: noTryStatements.rule,
+ [preferImmutableTypes.name]: preferImmutableTypes.rule,
+ [preferPropertySignatures.name]: preferPropertySignatures.rule,
[preferReadonlyTypes.name]: preferReadonlyTypes.rule,
[preferTacit.name]: preferTacit.rule,
+ [readonlyType.name]: readonlyType.rule,
+ [typeDeclarationImmutability.name]: typeDeclarationImmutability.rule,
};
diff --git a/src/rules/no-class.ts b/src/rules/no-classes.ts
similarity index 67%
rename from src/rules/no-class.ts
rename to src/rules/no-classes.ts
index f5b955f4e..4f5b03030 100644
--- a/src/rules/no-class.ts
+++ b/src/rules/no-classes.ts
@@ -1,20 +1,19 @@
-import type { ESLintUtils, TSESLint } from "@typescript-eslint/utils";
+import type { TSESLint } from "@typescript-eslint/utils";
import type { JSONSchema4 } from "json-schema";
-import type { ReadonlyDeep } from "type-fest";
-import type { ESClass } from "~/src/util/node-types";
-import type { RuleResult } from "~/util/rule";
-import { createRule } from "~/util/rule";
+import type { ESClass } from "~/utils/node-types";
+import type { RuleResult, NamedCreateRuleMetaWithCategory } from "~/utils/rule";
+import { createRule } from "~/utils/rule";
/**
* The name of this rule.
*/
-export const name = "no-class" as const;
+export const name = "no-classes" as const;
/**
* The options this rule can take.
*/
-type Options = readonly [{}];
+type Options = [{}];
/**
* The schema for the rule options.
@@ -36,9 +35,10 @@ const errorMessages = {
/**
* The meta data for this rule.
*/
-const meta: ESLintUtils.NamedCreateRuleMeta = {
+const meta: NamedCreateRuleMetaWithCategory = {
type: "suggestion",
docs: {
+ category: "No Other Paradigms",
description: "Disallow classes.",
recommended: "error",
},
@@ -50,10 +50,8 @@ const meta: ESLintUtils.NamedCreateRuleMeta = {
* Check if the given class node violates this rule.
*/
function checkClass(
- node: ReadonlyDeep,
- context: ReadonlyDeep<
- TSESLint.RuleContext
- >
+ node: ESClass,
+ context: TSESLint.RuleContext
): RuleResult {
// All class nodes violate this rule.
return { context, descriptors: [{ node, messageId: "generic" }] };
diff --git a/src/rules/no-conditional-statement.ts b/src/rules/no-conditional-statements.ts
similarity index 78%
rename from src/rules/no-conditional-statement.ts
rename to src/rules/no-conditional-statements.ts
index 878cb04be..937a5c932 100644
--- a/src/rules/no-conditional-statement.ts
+++ b/src/rules/no-conditional-statements.ts
@@ -1,11 +1,10 @@
-import type { ESLintUtils, TSESLint, TSESTree } from "@typescript-eslint/utils";
+import type { TSESLint, TSESTree } from "@typescript-eslint/utils";
import type { JSONSchema4 } from "json-schema";
-import type { ReadonlyDeep } from "type-fest";
import type { Type } from "typescript";
-import tsutils from "~/conditional-imports/tsutils";
-import type { RuleResult } from "~/util/rule";
-import { createRule, getTypeOfNode } from "~/util/rule";
+import type { RuleResult, NamedCreateRuleMetaWithCategory } from "~/utils/rule";
+import { createRule, getTypeOfNode } from "~/utils/rule";
+import { unionTypeParts } from "~/utils/tsutils";
import {
isBlockStatement,
isBreakStatement,
@@ -16,20 +15,20 @@ import {
isReturnStatement,
isSwitchStatement,
isThrowStatement,
-} from "~/util/typeguard";
+} from "~/utils/type-guards";
/**
* The name of this rule.
*/
-export const name = "no-conditional-statement" as const;
+export const name = "no-conditional-statements" as const;
/**
* The options this rule can take.
*/
-type Options = readonly [
- Readonly<{
+type Options = [
+ {
allowReturningBranches: boolean | "ifExhaustive";
- }>
+ }
];
/**
@@ -79,9 +78,10 @@ const errorMessages = {
/**
* The meta data for this rule.
*/
-const meta: ESLintUtils.NamedCreateRuleMeta = {
+const meta: NamedCreateRuleMetaWithCategory = {
type: "suggestion",
docs: {
+ category: "No Statements",
description: "Disallow conditional statements.",
recommended: "error",
},
@@ -96,7 +96,7 @@ const meta: ESLintUtils.NamedCreateRuleMeta = {
* @returns A violation rule result.
*/
function incompleteBranchViolation(
- node: ReadonlyDeep
+ node: TSESTree.Node
): RuleResult["descriptors"] {
return [{ node, messageId: "incompleteBranch" }];
}
@@ -105,11 +105,9 @@ function incompleteBranchViolation(
* Get a function that tests if the given statement is never returning.
*/
function getIsNeverExpressions(
- context: ReadonlyDeep<
- TSESLint.RuleContext
- >
+ context: TSESLint.RuleContext
) {
- return (statement: ReadonlyDeep) => {
+ return (statement: TSESTree.Statement) => {
if (isExpressionStatement(statement)) {
const expressionStatementType = getTypeOfNode(
statement.expression,
@@ -126,7 +124,7 @@ function getIsNeverExpressions(
/**
* Is the given statement, when inside an if statement, a returning branch?
*/
-function isIfReturningBranch(statement: ReadonlyDeep) {
+function isIfReturningBranch(statement: TSESTree.Statement) {
return (
// Another instance of this rule will check nested if statements.
isIfStatement(statement) ||
@@ -142,13 +140,11 @@ function isIfReturningBranch(statement: ReadonlyDeep) {
* are allowed.
*/
function getIfBranchViolations(
- node: ReadonlyDeep,
- context: ReadonlyDeep<
- TSESLint.RuleContext
- >
+ node: TSESTree.IfStatement,
+ context: TSESLint.RuleContext
): RuleResult["descriptors"] {
const branches = [node.consequent, node.alternate];
- const violations = branches.filter>(
+ const violations = branches.filter>(
(branch): branch is NonNullable => {
if (branch === null || isIfReturningBranch(branch)) {
return false;
@@ -189,7 +185,7 @@ function getIfBranchViolations(
/**
* Is the given statement, when inside a switch statement, a returning branch?
*/
-function isSwitchReturningBranch(statement: ReadonlyDeep) {
+function isSwitchReturningBranch(statement: TSESTree.Statement) {
return (
// Another instance of this rule will check nested switch statements.
isSwitchStatement(statement) ||
@@ -203,10 +199,8 @@ function isSwitchReturningBranch(statement: ReadonlyDeep) {
* statements are allowed.
*/
function getSwitchViolations(
- node: ReadonlyDeep,
- context: ReadonlyDeep<
- TSESLint.RuleContext
- >
+ node: TSESTree.SwitchStatement,
+ context: TSESLint.RuleContext
): RuleResult["descriptors"] {
const isNeverExpressions = getIsNeverExpressions(context);
@@ -219,7 +213,7 @@ function getSwitchViolations(
}
if (branch.consequent.every(isBlockStatement)) {
- const lastBlock = branch.consequent[branch.consequent.length - 1];
+ const lastBlock = branch.consequent.at(-1)!;
if (lastBlock.body.some(isSwitchReturningBranch)) {
return false;
@@ -239,9 +233,7 @@ function getSwitchViolations(
/**
* Does the given if statement violate this rule if it must be exhaustive.
*/
-function isExhaustiveIfViolation(
- node: ReadonlyDeep
-): boolean {
+function isExhaustiveIfViolation(node: TSESTree.IfStatement): boolean {
return node.alternate === null;
}
@@ -249,21 +241,15 @@ function isExhaustiveIfViolation(
* Does the given typed switch statement violate this rule if it must be exhaustive.
*/
function isExhaustiveTypeSwitchViolation(
- node: ReadonlyDeep,
- context: ReadonlyDeep<
- TSESLint.RuleContext
- >
+ node: TSESTree.SwitchStatement,
+ context: TSESLint.RuleContext
): boolean {
- if (tsutils === undefined) {
- return true;
- }
-
const discriminantType = getTypeOfNode(node.discriminant, context);
if (discriminantType === null || !discriminantType.isUnion()) {
return true;
}
- const unionTypes = tsutils.unionTypeParts(discriminantType);
+ const unionTypes = unionTypeParts(discriminantType);
const caseTypes = node.cases.reduce>(
(types, c) => new Set([...types, getTypeOfNode(c.test!, context)!]),
new Set()
@@ -276,10 +262,8 @@ function isExhaustiveTypeSwitchViolation(
* Does the given switch statement violate this rule if it must be exhaustive.
*/
function isExhaustiveSwitchViolation(
- node: ReadonlyDeep,
- context: ReadonlyDeep<
- TSESLint.RuleContext
- >
+ node: TSESTree.SwitchStatement,
+ context: TSESLint.RuleContext
): boolean {
return (
// No cases defined.
@@ -293,10 +277,8 @@ function isExhaustiveSwitchViolation(
* Check if the given IfStatement violates this rule.
*/
function checkIfStatement(
- node: ReadonlyDeep,
- context: ReadonlyDeep<
- TSESLint.RuleContext
- >,
+ node: TSESTree.IfStatement,
+ context: TSESLint.RuleContext,
options: Options
): RuleResult {
const [{ allowReturningBranches }] = options;
@@ -318,10 +300,8 @@ function checkIfStatement(
* Check if the given SwitchStatement violates this rule.
*/
function checkSwitchStatement(
- node: ReadonlyDeep,
- context: ReadonlyDeep<
- TSESLint.RuleContext
- >,
+ node: TSESTree.SwitchStatement,
+ context: TSESLint.RuleContext,
options: Options
): RuleResult {
const [{ allowReturningBranches }] = options;
diff --git a/src/rules/no-expression-statement.ts b/src/rules/no-expression-statements.ts
similarity index 66%
rename from src/rules/no-expression-statement.ts
rename to src/rules/no-expression-statements.ts
index 3c9ea32b5..dafeb0ca4 100644
--- a/src/rules/no-expression-statement.ts
+++ b/src/rules/no-expression-statements.ts
@@ -1,31 +1,26 @@
-import type { ESLintUtils, TSESLint, TSESTree } from "@typescript-eslint/utils";
+import type { TSESLint, TSESTree } from "@typescript-eslint/utils";
import { deepmerge } from "deepmerge-ts";
import type { JSONSchema4 } from "json-schema";
-import type { ReadonlyDeep } from "type-fest";
-import type { IgnorePatternOption } from "~/common/ignore-options";
-import {
- shouldIgnorePattern,
- ignorePatternOptionSchema,
-} from "~/common/ignore-options";
-import { isDirectivePrologue } from "~/util/misc";
-import type { RuleResult } from "~/util/rule";
-import { createRule, getTypeOfNode } from "~/util/rule";
-import { isVoidType } from "~/util/typeguard";
+import type { IgnorePatternOption } from "~/options";
+import { shouldIgnorePattern, ignorePatternOptionSchema } from "~/options";
+import { isDirectivePrologue } from "~/utils/misc";
+import type { RuleResult, NamedCreateRuleMetaWithCategory } from "~/utils/rule";
+import { createRule, getTypeOfNode } from "~/utils/rule";
+import { isVoidType } from "~/utils/type-guards";
/**
* The name of this rule.
*/
-export const name = "no-expression-statement" as const;
+export const name = "no-expression-statements" as const;
/**
* The options this rule can take.
*/
-type Options = readonly [
- IgnorePatternOption &
- Readonly<{
- ignoreVoid: boolean;
- }>
+type Options = [
+ IgnorePatternOption & {
+ ignoreVoid: boolean;
+ }
];
/**
@@ -62,11 +57,12 @@ const errorMessages = {
/**
* The meta data for this rule.
*/
-const meta: ESLintUtils.NamedCreateRuleMeta = {
+const meta: NamedCreateRuleMetaWithCategory = {
type: "suggestion",
docs: {
+ category: "No Statements",
description: "Disallow expression statements.",
- recommended: "error",
+ recommended: "strict",
},
messages: errorMessages,
schema,
@@ -76,15 +72,14 @@ const meta: ESLintUtils.NamedCreateRuleMeta = {
* Check if the given ExpressionStatement violates this rule.
*/
function checkExpressionStatement(
- node: ReadonlyDeep,
- context: ReadonlyDeep<
- TSESLint.RuleContext
- >,
+ node: TSESTree.ExpressionStatement,
+ context: TSESLint.RuleContext,
options: Options
): RuleResult {
const [optionsObject] = options;
+ const { ignorePattern } = optionsObject;
- if (shouldIgnorePattern(node, context, optionsObject)) {
+ if (shouldIgnorePattern(node, context, ignorePattern)) {
return {
context,
descriptors: [],
diff --git a/src/rules/no-let.ts b/src/rules/no-let.ts
index fe4c9c832..a0d494537 100644
--- a/src/rules/no-let.ts
+++ b/src/rules/no-let.ts
@@ -1,21 +1,16 @@
-import type { ESLintUtils, TSESLint, TSESTree } from "@typescript-eslint/utils";
+import type { TSESLint, TSESTree } from "@typescript-eslint/utils";
import { deepmerge } from "deepmerge-ts";
import type { JSONSchema4 } from "json-schema";
-import type { ReadonlyDeep } from "type-fest";
-import type {
- AllowLocalMutationOption,
- IgnorePatternOption,
-} from "~/common/ignore-options";
+import type { IgnorePatternOption } from "~/options";
import {
shouldIgnorePattern,
- shouldIgnoreLocalMutation,
- allowLocalMutationOptionSchema,
+ shouldIgnoreInFunction,
ignorePatternOptionSchema,
-} from "~/common/ignore-options";
-import type { RuleResult } from "~/util/rule";
-import { createRule } from "~/util/rule";
-import { inForLoopInitializer } from "~/util/tree";
+} from "~/options";
+import type { RuleResult, NamedCreateRuleMetaWithCategory } from "~/utils/rule";
+import { createRule } from "~/utils/rule";
+import { isInForLoopInitializer } from "~/utils/tree";
/**
* The name of this rule.
@@ -25,12 +20,11 @@ export const name = "no-let" as const;
/**
* The options this rule can take.
*/
-type Options = readonly [
- AllowLocalMutationOption &
- IgnorePatternOption &
- Readonly<{
- allowInForLoopInit: boolean;
- }>
+type Options = [
+ IgnorePatternOption & {
+ allowInForLoopInit: boolean;
+ allowInFunctions: boolean;
+ }
];
/**
@@ -39,15 +33,14 @@ type Options = readonly [
const schema: JSONSchema4 = [
{
type: "object",
- properties: deepmerge(
- allowLocalMutationOptionSchema,
- ignorePatternOptionSchema,
- {
- allowInForLoopInit: {
- type: "boolean",
- },
- }
- ),
+ properties: deepmerge(ignorePatternOptionSchema, {
+ allowInForLoopInit: {
+ type: "boolean",
+ },
+ allowInFunctions: {
+ type: "boolean",
+ },
+ }),
additionalProperties: false,
},
];
@@ -58,7 +51,7 @@ const schema: JSONSchema4 = [
const defaultOptions: Options = [
{
allowInForLoopInit: false,
- allowLocalMutation: false,
+ allowInFunctions: false,
},
];
@@ -72,14 +65,14 @@ const errorMessages = {
/**
* The meta data for this rule.
*/
-const meta: ESLintUtils.NamedCreateRuleMeta = {
+const meta: NamedCreateRuleMetaWithCategory = {
type: "suggestion",
docs: {
+ category: "No Mutations",
description: "Disallow mutable variables.",
recommended: "error",
},
messages: errorMessages,
- fixable: "code",
schema,
};
@@ -87,20 +80,18 @@ const meta: ESLintUtils.NamedCreateRuleMeta = {
* Check if the given VariableDeclaration violates this rule.
*/
function checkVariableDeclaration(
- node: ReadonlyDeep,
- context: ReadonlyDeep<
- TSESLint.RuleContext
- >,
+ node: TSESTree.VariableDeclaration,
+ context: TSESLint.RuleContext,
options: Options
): RuleResult {
const [optionsObject] = options;
- const { allowInForLoopInit } = optionsObject;
+ const { allowInForLoopInit, ignorePattern, allowInFunctions } = optionsObject;
if (
node.kind !== "let" ||
- shouldIgnoreLocalMutation(node, context, optionsObject) ||
- shouldIgnorePattern(node, context, optionsObject) ||
- (allowInForLoopInit && inForLoopInitializer(node))
+ shouldIgnoreInFunction(node, context, allowInFunctions) ||
+ shouldIgnorePattern(node, context, ignorePattern) ||
+ (allowInForLoopInit && isInForLoopInitializer(node))
) {
return {
context,
diff --git a/src/rules/no-loop-statement.ts b/src/rules/no-loop-statements.ts
similarity index 68%
rename from src/rules/no-loop-statement.ts
rename to src/rules/no-loop-statements.ts
index f9c012c99..dfc5be40c 100644
--- a/src/rules/no-loop-statement.ts
+++ b/src/rules/no-loop-statements.ts
@@ -1,20 +1,19 @@
-import type { ESLintUtils, TSESLint } from "@typescript-eslint/utils";
+import type { TSESLint } from "@typescript-eslint/utils";
import type { JSONSchema4 } from "json-schema";
-import type { ReadonlyDeep } from "type-fest";
-import type { ESLoop } from "~/src/util/node-types";
-import type { RuleResult } from "~/util/rule";
-import { createRule } from "~/util/rule";
+import type { ESLoop } from "~/utils/node-types";
+import type { RuleResult, NamedCreateRuleMetaWithCategory } from "~/utils/rule";
+import { createRule } from "~/utils/rule";
/**
* The name of this rule.
*/
-export const name = "no-loop-statement" as const;
+export const name = "no-loop-statements" as const;
/**
* The options this rule can take.
*/
-type Options = readonly [{}];
+type Options = [{}];
/**
* The schema for the rule options.
@@ -36,9 +35,10 @@ const errorMessages = {
/**
* The meta data for this rule.
*/
-const meta: ESLintUtils.NamedCreateRuleMeta = {
+const meta: NamedCreateRuleMetaWithCategory = {
type: "suggestion",
docs: {
+ category: "No Statements",
description: "Disallow imperative loops.",
recommended: "error",
},
@@ -50,10 +50,8 @@ const meta: ESLintUtils.NamedCreateRuleMeta = {
* Check if the given loop violates this rule.
*/
function checkLoop(
- node: ReadonlyDeep,
- context: ReadonlyDeep<
- TSESLint.RuleContext
- >
+ node: ESLoop,
+ context: TSESLint.RuleContext
): RuleResult {
// All loops violate this rule.
return { context, descriptors: [{ node, messageId: "generic" }] };
diff --git a/src/rules/no-mixed-type.ts b/src/rules/no-mixed-types.ts
similarity index 57%
rename from src/rules/no-mixed-type.ts
rename to src/rules/no-mixed-types.ts
index 0b38ff9ac..1ca27a717 100644
--- a/src/rules/no-mixed-type.ts
+++ b/src/rules/no-mixed-types.ts
@@ -1,25 +1,29 @@
-import type { ESLintUtils, TSESLint, TSESTree } from "@typescript-eslint/utils";
+import type { TSESLint, TSESTree } from "@typescript-eslint/utils";
import { AST_NODE_TYPES } from "@typescript-eslint/utils";
import type { JSONSchema4 } from "json-schema";
-import type { ReadonlyDeep } from "type-fest";
-import type { RuleResult } from "~/util/rule";
-import { createRule } from "~/util/rule";
-import { isTSPropertySignature, isTSTypeLiteral } from "~/util/typeguard";
+import type { RuleResult, NamedCreateRuleMetaWithCategory } from "~/utils/rule";
+import { createRuleUsingFunction } from "~/utils/rule";
+import {
+ isIdentifier,
+ isTSPropertySignature,
+ isTSTypeLiteral,
+ isTSTypeReference,
+} from "~/utils/type-guards";
/**
* The name of this rule.
*/
-export const name = "no-mixed-type" as const;
+export const name = "no-mixed-types" as const;
/**
* The options this rule can take.
*/
-type Options = readonly [
- Readonly<{
+type Options = [
+ {
checkInterfaces: boolean;
checkTypeLiterals: boolean;
- }>
+ }
];
/**
@@ -60,11 +64,12 @@ const errorMessages = {
/**
* The meta data for this rule.
*/
-const meta: ESLintUtils.NamedCreateRuleMeta = {
+const meta: NamedCreateRuleMetaWithCategory = {
type: "suggestion",
docs: {
+ category: "No Other Paradigms",
description:
- "Restrict types so that only members of the same kind of are allowed in them.",
+ "Restrict types so that only members of the same kind are allowed in them.",
recommended: "error",
},
messages: errorMessages,
@@ -75,7 +80,7 @@ const meta: ESLintUtils.NamedCreateRuleMeta = {
* Does the given type elements violate the rule.
*/
function hasTypeElementViolations(
- typeElements: ReadonlyArray>
+ typeElements: TSESTree.TypeElement[]
): boolean {
type CarryType = {
readonly prevMemberType: AST_NODE_TYPES | undefined;
@@ -118,20 +123,15 @@ function hasTypeElementViolations(
* Check if the given TSInterfaceDeclaration violates this rule.
*/
function checkTSInterfaceDeclaration(
- node: ReadonlyDeep,
- context: ReadonlyDeep<
- TSESLint.RuleContext
- >,
+ node: TSESTree.TSInterfaceDeclaration,
+ context: TSESLint.RuleContext,
options: Options
): RuleResult {
- const [{ checkInterfaces }] = options;
-
return {
context,
- descriptors:
- checkInterfaces && hasTypeElementViolations(node.body.body)
- ? [{ node, messageId: "generic" }]
- : [],
+ descriptors: hasTypeElementViolations(node.body.body)
+ ? [{ node, messageId: "generic" }]
+ : [],
};
}
@@ -139,32 +139,50 @@ function checkTSInterfaceDeclaration(
* Check if the given TSTypeAliasDeclaration violates this rule.
*/
function checkTSTypeAliasDeclaration(
- node: ReadonlyDeep,
- context: ReadonlyDeep<
- TSESLint.RuleContext
- >,
+ node: TSESTree.TSTypeAliasDeclaration,
+ context: TSESLint.RuleContext,
options: Options
): RuleResult {
- const [{ checkTypeLiterals }] = options;
-
return {
context,
descriptors:
- checkTypeLiterals &&
- isTSTypeLiteral(node.typeAnnotation) &&
- hasTypeElementViolations(node.typeAnnotation.members)
+ // TypeLiteral.
+ (isTSTypeLiteral(node.typeAnnotation) &&
+ hasTypeElementViolations(node.typeAnnotation.members)) ||
+ // TypeLiteral inside `Readonly<>`.
+ (isTSTypeReference(node.typeAnnotation) &&
+ isIdentifier(node.typeAnnotation.typeName) &&
+ node.typeAnnotation.typeParameters !== undefined &&
+ node.typeAnnotation.typeParameters.params.length === 1 &&
+ isTSTypeLiteral(node.typeAnnotation.typeParameters.params[0]!) &&
+ hasTypeElementViolations(
+ node.typeAnnotation.typeParameters.params[0].members
+ ))
? [{ node, messageId: "generic" }]
: [],
};
}
// Create the rule.
-export const rule = createRule(
- name,
- meta,
- defaultOptions,
- {
- TSInterfaceDeclaration: checkTSInterfaceDeclaration,
- TSTypeAliasDeclaration: checkTSTypeAliasDeclaration,
- }
-);
+export const rule = createRuleUsingFunction<
+ keyof typeof errorMessages,
+ Options
+>(name, meta, defaultOptions, (context, options) => {
+ const [{ checkInterfaces, checkTypeLiterals }] = options;
+
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ return Object.fromEntries(
+ (
+ [
+ [
+ "TSInterfaceDeclaration",
+ checkInterfaces ? checkTSInterfaceDeclaration : undefined,
+ ],
+ [
+ "TSTypeAliasDeclaration",
+ checkTypeLiterals ? checkTSTypeAliasDeclaration : undefined,
+ ],
+ ] as const
+ ).filter(([sel, fn]) => fn !== undefined)
+ );
+});
diff --git a/src/rules/no-promise-reject.ts b/src/rules/no-promise-reject.ts
index 9548a9acc..ee3f1d546 100644
--- a/src/rules/no-promise-reject.ts
+++ b/src/rules/no-promise-reject.ts
@@ -1,10 +1,9 @@
-import type { ESLintUtils, TSESLint, TSESTree } from "@typescript-eslint/utils";
+import type { TSESLint, TSESTree } from "@typescript-eslint/utils";
import type { JSONSchema4 } from "json-schema";
-import type { ReadonlyDeep } from "type-fest";
-import type { RuleResult } from "~/util/rule";
-import { createRule } from "~/util/rule";
-import { isIdentifier, isMemberExpression } from "~/util/typeguard";
+import type { RuleResult, NamedCreateRuleMetaWithCategory } from "~/utils/rule";
+import { createRule } from "~/utils/rule";
+import { isIdentifier, isMemberExpression } from "~/utils/type-guards";
/**
* The name of this rule.
@@ -14,7 +13,7 @@ export const name = "no-promise-reject" as const;
/**
* The options this rule can take.
*/
-type Options = readonly [{}];
+type Options = [{}];
/**
* The schema for the rule options.
@@ -36,9 +35,10 @@ const errorMessages = {
/**
* The meta data for this rule.
*/
-const meta: ESLintUtils.NamedCreateRuleMeta = {
+const meta: NamedCreateRuleMetaWithCategory = {
type: "suggestion",
docs: {
+ category: "No Exceptions",
description: "Disallow try-catch[-finally] and try-finally patterns.",
recommended: false,
},
@@ -50,10 +50,8 @@ const meta: ESLintUtils.NamedCreateRuleMeta = {
* Check if the given CallExpression violates this rule.
*/
function checkCallExpression(
- node: ReadonlyDeep,
- context: ReadonlyDeep<
- TSESLint.RuleContext
- >
+ node: TSESTree.CallExpression,
+ context: TSESLint.RuleContext
): RuleResult {
return {
context,
diff --git a/src/rules/no-return-void.ts b/src/rules/no-return-void.ts
index 9259c9871..f810ab731 100644
--- a/src/rules/no-return-void.ts
+++ b/src/rules/no-return-void.ts
@@ -1,10 +1,9 @@
-import type { ESLintUtils, TSESLint } from "@typescript-eslint/utils";
+import type { TSESLint } from "@typescript-eslint/utils";
import type { JSONSchema4 } from "json-schema";
-import type { ReadonlyDeep } from "type-fest";
-import type { ESFunctionType } from "~/src/util/node-types";
-import type { RuleResult } from "~/util/rule";
-import { createRule, getTypeOfNode } from "~/util/rule";
+import type { ESFunctionType } from "~/utils/node-types";
+import type { RuleResult, NamedCreateRuleMetaWithCategory } from "~/utils/rule";
+import { createRule, getTypeOfNode } from "~/utils/rule";
import {
isFunctionLike,
isNullType,
@@ -13,7 +12,7 @@ import {
isTSVoidKeyword,
isUndefinedType,
isVoidType,
-} from "~/util/typeguard";
+} from "~/utils/type-guards";
/**
* The name of this rule.
@@ -23,12 +22,12 @@ export const name = "no-return-void" as const;
/**
* The options this rule can take.
*/
-type Options = readonly [
- Readonly<{
+type Options = [
+ {
allowNull: boolean;
allowUndefined: boolean;
- ignoreImplicit: boolean;
- }>
+ ignoreInferredTypes: boolean;
+ }
];
/**
@@ -44,7 +43,7 @@ const schema: JSONSchema4 = [
allowUndefined: {
type: "boolean",
},
- ignoreImplicit: {
+ ignoreInferredTypes: {
type: "boolean",
},
},
@@ -59,7 +58,7 @@ const defaultOptions: Options = [
{
allowNull: true,
allowUndefined: true,
- ignoreImplicit: false,
+ ignoreInferredTypes: false,
},
];
@@ -73,9 +72,10 @@ const errorMessages = {
/**
* The meta data for this rule.
*/
-const meta: ESLintUtils.NamedCreateRuleMeta = {
+const meta: NamedCreateRuleMetaWithCategory = {
type: "suggestion",
docs: {
+ category: "No Statements",
description: "Disallow functions that don't return anything.",
recommended: "error",
},
@@ -87,16 +87,14 @@ const meta: ESLintUtils.NamedCreateRuleMeta = {
* Check if the given function node violates this rule.
*/
function checkFunction(
- node: ReadonlyDeep,
- context: ReadonlyDeep<
- TSESLint.RuleContext
- >,
+ node: ESFunctionType,
+ context: TSESLint.RuleContext,
options: Options
): RuleResult {
- const [{ ignoreImplicit, allowNull, allowUndefined }] = options;
+ const [{ ignoreInferredTypes, allowNull, allowUndefined }] = options;
if (node.returnType === undefined) {
- if (!ignoreImplicit && isFunctionLike(node)) {
+ if (!ignoreInferredTypes && isFunctionLike(node)) {
const functionType = getTypeOfNode(node, context);
const returnType = functionType
?.getCallSignatures()?.[0]
diff --git a/src/rules/no-this-expression.ts b/src/rules/no-this-expressions.ts
similarity index 66%
rename from src/rules/no-this-expression.ts
rename to src/rules/no-this-expressions.ts
index 96f012856..a93271402 100644
--- a/src/rules/no-this-expression.ts
+++ b/src/rules/no-this-expressions.ts
@@ -1,19 +1,18 @@
-import type { ESLintUtils, TSESLint, TSESTree } from "@typescript-eslint/utils";
+import type { TSESLint, TSESTree } from "@typescript-eslint/utils";
import type { JSONSchema4 } from "json-schema";
-import type { ReadonlyDeep } from "type-fest";
-import type { RuleResult } from "~/util/rule";
-import { createRule } from "~/util/rule";
+import type { RuleResult, NamedCreateRuleMetaWithCategory } from "~/utils/rule";
+import { createRule } from "~/utils/rule";
/**
* The name of this rule.
*/
-export const name = "no-this-expression" as const;
+export const name = "no-this-expressions" as const;
/**
* The options this rule can take.
*/
-type Options = readonly [{}];
+type Options = [{}];
/**
* The schema for the rule options.
@@ -35,11 +34,12 @@ const errorMessages = {
/**
* The meta data for this rule.
*/
-const meta: ESLintUtils.NamedCreateRuleMeta = {
+const meta: NamedCreateRuleMetaWithCategory = {
type: "suggestion",
docs: {
+ category: "No Other Paradigms",
description: "Disallow this access.",
- recommended: "error",
+ recommended: "strict",
},
messages: errorMessages,
schema,
@@ -49,10 +49,8 @@ const meta: ESLintUtils.NamedCreateRuleMeta = {
* Check if the given ThisExpression violates this rule.
*/
function checkThisExpression(
- node: ReadonlyDeep,
- context: ReadonlyDeep<
- TSESLint.RuleContext
- >
+ node: TSESTree.ThisExpression,
+ context: TSESLint.RuleContext
): RuleResult {
// All throw statements violate this rule.
return { context, descriptors: [{ node, messageId: "generic" }] };
diff --git a/src/rules/no-throw-statement.ts b/src/rules/no-throw-statements.ts
similarity index 68%
rename from src/rules/no-throw-statement.ts
rename to src/rules/no-throw-statements.ts
index 9cd011aee..7adf2acb9 100644
--- a/src/rules/no-throw-statement.ts
+++ b/src/rules/no-throw-statements.ts
@@ -1,23 +1,22 @@
-import type { ESLintUtils, TSESLint, TSESTree } from "@typescript-eslint/utils";
+import type { TSESLint, TSESTree } from "@typescript-eslint/utils";
import type { JSONSchema4 } from "json-schema";
-import type { ReadonlyDeep } from "type-fest";
-import { inFunctionBody } from "~/src/util/tree";
-import type { RuleResult } from "~/util/rule";
-import { createRule } from "~/util/rule";
+import type { RuleResult, NamedCreateRuleMetaWithCategory } from "~/utils/rule";
+import { createRule } from "~/utils/rule";
+import { isInFunctionBody } from "~/utils/tree";
/**
* The name of this rule.
*/
-export const name = "no-throw-statement" as const;
+export const name = "no-throw-statements" as const;
/**
* The options this rule can take.
*/
-type Options = readonly [
- Readonly<{
+type Options = [
+ {
allowInAsyncFunctions: boolean;
- }>
+ }
];
/**
@@ -54,9 +53,10 @@ const errorMessages = {
/**
* The meta data for this rule.
*/
-const meta: ESLintUtils.NamedCreateRuleMeta = {
+const meta: NamedCreateRuleMetaWithCategory = {
type: "suggestion",
docs: {
+ category: "No Exceptions",
description: "Disallow throwing exceptions.",
recommended: "error",
},
@@ -68,15 +68,13 @@ const meta: ESLintUtils.NamedCreateRuleMeta = {
* Check if the given ThrowStatement violates this rule.
*/
function checkThrowStatement(
- node: ReadonlyDeep,
- context: ReadonlyDeep<
- TSESLint.RuleContext
- >,
+ node: TSESTree.ThrowStatement,
+ context: TSESLint.RuleContext,
options: Options
): RuleResult {
const [{ allowInAsyncFunctions }] = options;
- if (!allowInAsyncFunctions || !inFunctionBody(node, true)) {
+ if (!allowInAsyncFunctions || !isInFunctionBody(node, true)) {
return { context, descriptors: [{ node, messageId: "generic" }] };
}
diff --git a/src/rules/no-try-statement.ts b/src/rules/no-try-statements.ts
similarity index 75%
rename from src/rules/no-try-statement.ts
rename to src/rules/no-try-statements.ts
index cf88e7a73..9548eda15 100644
--- a/src/rules/no-try-statement.ts
+++ b/src/rules/no-try-statements.ts
@@ -1,23 +1,22 @@
-import type { ESLintUtils, TSESLint, TSESTree } from "@typescript-eslint/utils";
+import type { TSESLint, TSESTree } from "@typescript-eslint/utils";
import type { JSONSchema4 } from "json-schema";
-import type { ReadonlyDeep } from "type-fest";
-import type { RuleResult } from "~/util/rule";
-import { createRule } from "~/util/rule";
+import type { RuleResult, NamedCreateRuleMetaWithCategory } from "~/utils/rule";
+import { createRule } from "~/utils/rule";
/**
* The name of this rule.
*/
-export const name = "no-try-statement" as const;
+export const name = "no-try-statements" as const;
/**
* The options this rule can take.
*/
-type Options = readonly [
- Readonly<{
+type Options = [
+ {
allowCatch: boolean;
allowFinally: boolean;
- }>
+ }
];
/**
@@ -59,11 +58,12 @@ const errorMessages = {
/**
* The meta data for this rule.
*/
-const meta: ESLintUtils.NamedCreateRuleMeta = {
+const meta: NamedCreateRuleMetaWithCategory = {
type: "suggestion",
docs: {
+ category: "No Exceptions",
description: "Disallow try-catch[-finally] and try-finally patterns.",
- recommended: "error",
+ recommended: "strict",
},
messages: errorMessages,
schema,
@@ -73,10 +73,8 @@ const meta: ESLintUtils.NamedCreateRuleMeta = {
* Check if the given TryStatement violates this rule.
*/
function checkTryStatement(
- node: ReadonlyDeep,
- context: ReadonlyDeep<
- TSESLint.RuleContext
- >,
+ node: TSESTree.TryStatement,
+ context: TSESLint.RuleContext,
options: Options
): RuleResult {
const [{ allowCatch, allowFinally }] = options;
diff --git a/src/rules/prefer-immutable-types.ts b/src/rules/prefer-immutable-types.ts
new file mode 100644
index 000000000..29e28b348
--- /dev/null
+++ b/src/rules/prefer-immutable-types.ts
@@ -0,0 +1,748 @@
+import type { TSESLint, TSESTree } from "@typescript-eslint/utils";
+import { deepmerge } from "deepmerge-ts";
+import { Immutability } from "is-immutable-type";
+import type { JSONSchema4 } from "json-schema";
+
+import type { IgnoreClassesOption } from "~/options";
+import {
+ ignoreClassesOptionSchema,
+ shouldIgnoreClasses,
+ shouldIgnoreInFunction,
+ shouldIgnorePattern,
+} from "~/options";
+import type { ESFunctionType } from "~/utils/node-types";
+import type { RuleResult, NamedCreateRuleMetaWithCategory } from "~/utils/rule";
+import {
+ createRule,
+ getReturnTypesOfFunction,
+ getTypeImmutabilityOfNode,
+ getTypeImmutabilityOfType,
+ isImplementationOfOverload,
+} from "~/utils/rule";
+import {
+ hasID,
+ isDefined,
+ isFunctionLike,
+ isIdentifier,
+ isPropertyDefinition,
+ isTSParameterProperty,
+ isTSTypePredicate,
+} from "~/utils/type-guards";
+
+/**
+ * The name of this rule.
+ */
+export const name = "prefer-immutable-types" as const;
+
+type RawEnforcement =
+ | Exclude
+ | "None"
+ | false;
+
+type Option = IgnoreClassesOption & {
+ enforcement: RawEnforcement;
+ ignoreInferredTypes: boolean;
+ ignoreNamePattern?: string[] | string;
+ ignoreTypePattern?: string[] | string;
+};
+
+type FixerConfigRaw = {
+ pattern: string;
+ replace: string;
+};
+
+type FixerConfig = {
+ pattern: RegExp;
+ replace: string;
+};
+
+/**
+ * The options this rule can take.
+ */
+type Options = [
+ Option & {
+ parameters?: Partial