eslint-plugin-maintainability 3.0.3 → 3.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +47 -0
- package/README.md +52 -24
- package/package.json +20 -4
- package/src/index.d.ts +76 -4
- package/src/index.js +27 -6
- package/src/oxlint.d.ts +20 -0
- package/src/oxlint.js +21 -0
- package/src/rules/error-handling/error-message.js +2 -0
- package/src/rules/error-handling/no-missing-error-context.js +2 -1
- package/src/rules/error-handling/no-silent-errors.js +3 -2
- package/src/rules/error-handling/no-unhandled-promise.js +5 -2
- package/src/rules/maintainability/cognitive-complexity.js +5 -1
- package/src/rules/maintainability/consistent-function-scoping.js +56 -8
- package/src/rules/maintainability/identical-functions.js +7 -2
- package/src/rules/maintainability/max-parameters.js +1 -0
- package/src/rules/maintainability/nested-complexity-hotspots.js +2 -1
- package/src/rules/maintainability/no-lonely-if.js +2 -0
- package/src/rules/maintainability/no-nested-ternary.js +1 -0
- package/src/rules/maintainability/no-unreadable-iife.js +31 -32
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,50 @@
|
|
|
1
|
+
## [3.0.3] - 2026-02-08
|
|
2
|
+
|
|
3
|
+
## 3.0.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#197](https://github.com/ofri-peretz/eslint/pull/197) [`ecb8491`](https://github.com/ofri-peretz/eslint/commit/ecb849121833bf63b00256fa837f329bb721fbac) Thanks [@ofri-peretz](https://github.com/ofri-peretz)! - fix: republish `recommended` with correctly-namespaced, unscoped rule ids
|
|
8
|
+
|
|
9
|
+
`eslint-plugin-maintainability@3.0.3` and `eslint-plugin-operability@3.0.5`
|
|
10
|
+
shipped a `recommended` preset whose rule ids carried a doubled, scoped plugin
|
|
11
|
+
segment (`@interlace/maintainability/maintainability/cognitive-complexity`)
|
|
12
|
+
that no registered plugin key matched. Enabling the preset alongside any other
|
|
13
|
+
config made ESLint throw at load:
|
|
14
|
+
|
|
15
|
+
```text
|
|
16
|
+
Could not find plugin "@interlace/maintainability/maintainability".
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
(and the equivalent `@interlace/operability/operability` for operability.)
|
|
20
|
+
|
|
21
|
+
The source was already corrected to the bare, unscoped form
|
|
22
|
+
(`maintainability/cognitive-complexity` under a `maintainability` plugin key)
|
|
23
|
+
but was never republished, so npm still served the broken build. This release
|
|
24
|
+
ships the corrected build. `plugin.meta.name` is also fixed to the unscoped
|
|
25
|
+
`eslint-plugin-maintainability` (was `@interlace/eslint-plugin-maintainability`,
|
|
26
|
+
which drifted from the package name and every other plugin).
|
|
27
|
+
|
|
28
|
+
Each plugin is configured on its own — there is no unified config. No rule
|
|
29
|
+
behaviour changes.
|
|
30
|
+
|
|
31
|
+
New regression locks in each plugin's `index.test.ts` reproduce ESLint's rule-id
|
|
32
|
+
resolution, pin the plugin name and key as unscoped, and load each `recommended`
|
|
33
|
+
preset in a real ESLint instance — failing closed if a scoped or doubly
|
|
34
|
+
namespaced config could ship again.
|
|
35
|
+
|
|
36
|
+
### Bug Fixes
|
|
37
|
+
|
|
38
|
+
- align codecov component IDs with full package names ([2831b968](https://github.com/ofri-peretz/eslint/commit/2831b968))
|
|
39
|
+
|
|
40
|
+
### Documentation
|
|
41
|
+
|
|
42
|
+
- fix changelog header format across all packages ([c3a15082](https://github.com/ofri-peretz/eslint/commit/c3a15082))
|
|
43
|
+
|
|
44
|
+
### ❤️ Thank You
|
|
45
|
+
|
|
46
|
+
- Ofri Peretz
|
|
47
|
+
|
|
1
48
|
## [3.0.2] - 2026-02-06
|
|
2
49
|
|
|
3
50
|
### Bug Fixes
|
package/README.md
CHANGED
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<a href="https://eslint.interlace.tools" target="blank"><img src="https://eslint.interlace.tools/eslint-interlace-logo-light.svg" alt="ESLint Interlace Logo" width="120" /></a>
|
|
2
|
+
<a href="https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-maintainability" target="blank"><img src="https://eslint.interlace.tools/eslint-interlace-logo-light.svg" alt="ESLint Interlace Logo" width="120" /></a>
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
6
|
-
|
|
6
|
+
Maintainability rules — complexity ceilings, dead code, and readability guardrails.
|
|
7
7
|
</p>
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
10
10
|
<a href="https://www.npmjs.com/package/eslint-plugin-maintainability" target="_blank"><img src="https://img.shields.io/npm/v/eslint-plugin-maintainability.svg" alt="NPM Version" /></a>
|
|
11
11
|
<a href="https://www.npmjs.com/package/eslint-plugin-maintainability" target="_blank"><img src="https://img.shields.io/npm/dm/eslint-plugin-maintainability.svg" alt="NPM Downloads" /></a>
|
|
12
12
|
<a href="https://opensource.org/licenses/MIT" target="_blank"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="Package License" /></a>
|
|
13
|
-
<a href="https://app.codecov.io/gh/ofri-peretz/eslint/components?components%5B0%5D=maintainability" target="_blank"><img src="https://codecov.io/gh/ofri-peretz/eslint/graph/badge.svg?component=
|
|
13
|
+
<a href="https://app.codecov.io/gh/ofri-peretz/eslint/components?components%5B0%5D=maintainability" target="_blank"><img src="https://codecov.io/gh/ofri-peretz/eslint/graph/badge.svg?component=maintainability" alt="Codecov" /></a>
|
|
14
14
|
<a href="https://github.com/ofri-peretz/eslint" target="_blank"><img src="https://img.shields.io/badge/Since-Dec_2025-blue?logo=rocket&logoColor=white" alt="Since Dec 2025" /></a>
|
|
15
15
|
</p>
|
|
16
16
|
|
|
17
|
+
> ⭐ If this plugin caught a real bug for you, [**star the repo**](https://github.com/ofri-peretz/eslint) — it's the signal that keeps these rules maintained.
|
|
18
|
+
|
|
17
19
|
## Description
|
|
18
20
|
|
|
19
|
-
This plugin provides
|
|
20
|
-
By using this plugin, you can proactively identify and mitigate security risks across your entire codebase.
|
|
21
|
+
This plugin provides Maintainability rules — complexity ceilings, dead code, and readability guardrails.
|
|
21
22
|
|
|
22
23
|
## Philosophy
|
|
23
24
|
|
|
@@ -25,12 +26,12 @@ By using this plugin, you can proactively identify and mitigate security risks a
|
|
|
25
26
|
|
|
26
27
|
## Getting Started
|
|
27
28
|
|
|
28
|
-
- To check out the [guide](https://eslint.interlace.tools/docs/quality/plugin-maintainability), visit [eslint.interlace.tools](https://eslint.interlace.tools). 📚
|
|
29
|
-
- 要查看中文 [指南](https://eslint.interlace.tools/docs/quality/plugin-maintainability), 请访问 [eslint.interlace.tools](https://eslint.interlace.tools). 📚
|
|
30
|
-
- [가이드](https://eslint.interlace.tools/docs/quality/plugin-maintainability) 문서는 [eslint.interlace.tools](https://eslint.interlace.tools)에서 확인하실 수 있습니다. 📚
|
|
31
|
-
- [ガイド](https://eslint.interlace.tools/docs/quality/plugin-maintainability)は [eslint.interlace.tools](https://eslint.interlace.tools)でご確認ください。 📚
|
|
32
|
-
- Para ver la [guía](https://eslint.interlace.tools/docs/quality/plugin-maintainability), visita [eslint.interlace.tools](https://eslint.interlace.tools). 📚
|
|
33
|
-
- للاطلاع على [الدليل](https://eslint.interlace.tools/docs/quality/plugin-maintainability)، قم بزيارة [eslint.interlace.tools](https://eslint.interlace.tools). 📚
|
|
29
|
+
- To check out the [guide](https://eslint.interlace.tools/docs/quality/plugin-maintainability?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-maintainability), visit [eslint.interlace.tools](https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-maintainability). 📚
|
|
30
|
+
- 要查看中文 [指南](https://eslint.interlace.tools/docs/quality/plugin-maintainability?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-maintainability), 请访问 [eslint.interlace.tools](https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-maintainability). 📚
|
|
31
|
+
- [가이드](https://eslint.interlace.tools/docs/quality/plugin-maintainability?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-maintainability) 문서는 [eslint.interlace.tools](https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-maintainability)에서 확인하실 수 있습니다. 📚
|
|
32
|
+
- [ガイド](https://eslint.interlace.tools/docs/quality/plugin-maintainability?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-maintainability)は [eslint.interlace.tools](https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-maintainability)でご確認ください。 📚
|
|
33
|
+
- Para ver la [guía](https://eslint.interlace.tools/docs/quality/plugin-maintainability?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-maintainability), visita [eslint.interlace.tools](https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-maintainability). 📚
|
|
34
|
+
- للاطلاع على [الدليل](https://eslint.interlace.tools/docs/quality/plugin-maintainability?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-maintainability)، قم بزيارة [eslint.interlace.tools](https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-maintainability). 📚
|
|
34
35
|
|
|
35
36
|
```bash
|
|
36
37
|
npm install eslint-plugin-maintainability --save-dev
|
|
@@ -41,6 +42,14 @@ npm install eslint-plugin-maintainability --save-dev
|
|
|
41
42
|
| :------------ | :----------------------------------------- |
|
|
42
43
|
| `recommended` | Recommended code quality rules as warnings |
|
|
43
44
|
|
|
45
|
+
## 📦 Compatibility
|
|
46
|
+
| Package | Version |
|
|
47
|
+
| :--- | :--- |
|
|
48
|
+
| ESLint | `^8.0.0 \|\| ^9.0.0 \|\| ^10.0.0` |
|
|
49
|
+
| Node.js | `>=18.0.0` |
|
|
50
|
+
|
|
51
|
+
See the [ESLint Version Support Policy](../../docs/ESLINT_VERSION_SUPPORT.md) — current ecosystem share data, the 20% gate, and the forward-looking exception that covers v10.
|
|
52
|
+
|
|
44
53
|
## Rules
|
|
45
54
|
|
|
46
55
|
**Legend**
|
|
@@ -48,20 +57,30 @@ npm install eslint-plugin-maintainability --save-dev
|
|
|
48
57
|
| Icon | Description |
|
|
49
58
|
| :---: | :--- |
|
|
50
59
|
| 💼 | **Recommended**: Included in the recommended preset. |
|
|
51
|
-
| ⚠️ | **Warns**: Set
|
|
60
|
+
| ⚠️ | **Warns**: Set to warn in recommended preset. |
|
|
52
61
|
| 🔧 | **Auto-fixable**: Automatically fixable by the `--fix` CLI option. |
|
|
53
62
|
| 💡 | **Suggestions**: Providing code suggestions in IDE. |
|
|
54
63
|
| 🚫 | **Deprecated**: This rule is deprecated. |
|
|
55
|
-
|
|
56
|
-
|
|
|
57
|
-
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
|
61
|
-
|
|
|
62
|
-
| [
|
|
63
|
-
| [
|
|
64
|
-
|
|
64
|
+
| 🟢 | **Type-unaware**: AST-only, runs in oxlint JS-plugin tier. |
|
|
65
|
+
| 🟡 | **Type-aware (refining)**: pure-AST primary path; types refine precision. |
|
|
66
|
+
| 🟠 | **Type-aware (graceful)**: requires TS program; silent without it. |
|
|
67
|
+
|
|
68
|
+
<!-- AUTO-GENERATED:RULES_TABLE:START - Do not edit manually -->
|
|
69
|
+
| Rule | CWE | OWASP | CVSS | Description | 🧠 | 💼 | ⚠️ | 🔧 | 💡 | 🚫 |
|
|
70
|
+
| :--- | :---: | :---: | :---: | :--- | :---: | :---: | :---: | :---: | :---: | :---: |
|
|
71
|
+
| [cognitive-complexity](https://eslint.interlace.tools/docs/quality/plugin-maintainability/rules/cognitive-complexity?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-maintainability) | | | | Enforces a maximum cognitive complexity threshold with refactoring guidance | 🟢 | | ⚠️ | | 💡 | |
|
|
72
|
+
| [consistent-function-scoping](https://eslint.interlace.tools/docs/quality/plugin-maintainability/rules/consistent-function-scoping?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-maintainability) | | | | Move functions to the highest possible scope | 🟢 | | | | 💡 | |
|
|
73
|
+
| [error-message](https://eslint.interlace.tools/docs/quality/plugin-maintainability/rules/error-message?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-maintainability) | | | | Enforce providing a message when creating built-in Error objects for better debugging | 🟢 | | | | 💡 | |
|
|
74
|
+
| [identical-functions](https://eslint.interlace.tools/docs/quality/plugin-maintainability/rules/identical-functions?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-maintainability) | CWE-1104 | | | Detects duplicate function implementations with DRY refactoring suggestions | 🟢 | | ⚠️ | | 💡 | |
|
|
75
|
+
| [max-parameters](https://eslint.interlace.tools/docs/quality/plugin-maintainability/rules/max-parameters?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-maintainability) | | | | ESLint Rule: max-parameters with LLM-optimized suggestions and auto-fix capabilities. | 🟢 | | ⚠️ | | 💡 | |
|
|
76
|
+
| [nested-complexity-hotspots](https://eslint.interlace.tools/docs/quality/plugin-maintainability/rules/nested-complexity-hotspots?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-maintainability) | | | | ESLint Rule: nested-complexity-hotspots with LLM-optimized suggestions and auto-fix capabilities. | 🟢 | | | | 💡 | |
|
|
77
|
+
| [no-lonely-if](https://eslint.interlace.tools/docs/quality/plugin-maintainability/rules/no-lonely-if?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-maintainability) | | | | Disallow if statements as the only statement in else blocks | 🟢 | | | | 💡 | |
|
|
78
|
+
| [no-missing-error-context](https://eslint.interlace.tools/docs/quality/plugin-maintainability/rules/no-missing-error-context?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-maintainability) | | | | ESLint Rule: no-missing-error-context with LLM-optimized suggestions and auto-fix capabilities. | 🟢 | | | | 💡 | |
|
|
79
|
+
| [no-nested-ternary](https://eslint.interlace.tools/docs/quality/plugin-maintainability/rules/no-nested-ternary?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-maintainability) | | | | Prevent nested ternary expressions for better readability | 🟢 | | | | 💡 | |
|
|
80
|
+
| [no-silent-errors](https://eslint.interlace.tools/docs/quality/plugin-maintainability/rules/no-silent-errors?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-maintainability) | | | | ESLint Rule: no-silent-errors with LLM-optimized suggestions and auto-fix capabilities. | 🟢 | | | | 💡 | |
|
|
81
|
+
| [no-unhandled-promise](https://eslint.interlace.tools/docs/quality/plugin-maintainability/rules/no-unhandled-promise?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-maintainability) | CWE-1024 | | | Disallow unhandled Promise rejections with LLM-optimized suggestions for proper async error handling | 🟢 | | | | 💡 | |
|
|
82
|
+
| [no-unreadable-iife](https://eslint.interlace.tools/docs/quality/plugin-maintainability/rules/no-unreadable-iife?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-maintainability) | | | | Disallow unreadable IIFE (Immediately Invoked Function Expression) patterns | 🟢 | | | | 💡 | |
|
|
83
|
+
<!-- AUTO-GENERATED:RULES_TABLE:END -->
|
|
65
84
|
## 🔗 Related ESLint Plugins
|
|
66
85
|
|
|
67
86
|
Part of the **Interlace ESLint Ecosystem** — AI-native security plugins with LLM-optimized error messages:
|
|
@@ -70,7 +89,7 @@ Part of the **Interlace ESLint Ecosystem** — AI-native security plugins with L
|
|
|
70
89
|
| :--- | :---: | :--- |
|
|
71
90
|
| [`eslint-plugin-secure-coding`](https://www.npmjs.com/package/eslint-plugin-secure-coding) | [](https://www.npmjs.com/package/eslint-plugin-secure-coding) | General security rules & OWASP guidelines. |
|
|
72
91
|
| [`eslint-plugin-pg`](https://www.npmjs.com/package/eslint-plugin-pg) | [](https://www.npmjs.com/package/eslint-plugin-pg) | PostgreSQL security & best practices. |
|
|
73
|
-
| [`eslint-plugin-
|
|
92
|
+
| [`eslint-plugin-node-security`](https://www.npmjs.com/package/eslint-plugin-node-security) | [](https://www.npmjs.com/package/eslint-plugin-node-security) | Node.js core-module security (fs, child_process, vm, crypto, Buffer). |
|
|
74
93
|
| [`eslint-plugin-jwt`](https://www.npmjs.com/package/eslint-plugin-jwt) | [](https://www.npmjs.com/package/eslint-plugin-jwt) | JWT security & best practices. |
|
|
75
94
|
| [`eslint-plugin-browser-security`](https://www.npmjs.com/package/eslint-plugin-browser-security) | [](https://www.npmjs.com/package/eslint-plugin-browser-security) | Browser-specific security & XSS prevention. |
|
|
76
95
|
| [`eslint-plugin-express-security`](https://www.npmjs.com/package/eslint-plugin-express-security) | [](https://www.npmjs.com/package/eslint-plugin-express-security) | Express.js security hardening rules. |
|
|
@@ -80,10 +99,19 @@ Part of the **Interlace ESLint Ecosystem** — AI-native security plugins with L
|
|
|
80
99
|
| [`eslint-plugin-vercel-ai-security`](https://www.npmjs.com/package/eslint-plugin-vercel-ai-security) | [](https://www.npmjs.com/package/eslint-plugin-vercel-ai-security) | Vercel AI SDK security hardening. |
|
|
81
100
|
| [`eslint-plugin-import-next`](https://www.npmjs.com/package/eslint-plugin-import-next) | [](https://www.npmjs.com/package/eslint-plugin-import-next) | Next-gen import sorting & architecture. |
|
|
82
101
|
|
|
102
|
+
<!-- INTERLACE:STAR_CTA:START -->
|
|
103
|
+
## ⭐ Support & follow
|
|
104
|
+
|
|
105
|
+
If this plugin caught a real bug for you, **[star the repo](https://github.com/ofri-peretz/eslint)** — stars are the signal that keeps the Interlace ESLint ecosystem maintained — and **[follow the writeups on Dev.to](https://dev.to/ofri-peretz)** for the benchmarks and security research behind these rules.
|
|
106
|
+
|
|
107
|
+
[](https://github.com/ofri-peretz/eslint)
|
|
108
|
+
|
|
109
|
+
<!-- INTERLACE:STAR_CTA:END -->
|
|
110
|
+
|
|
83
111
|
## 📄 License
|
|
84
112
|
|
|
85
113
|
MIT © [Ofri Peretz](https://github.com/ofri-peretz)
|
|
86
114
|
|
|
87
115
|
<p align="center">
|
|
88
|
-
<a href="https://eslint.interlace.tools/docs/quality/plugin-maintainability"><img src="https://eslint.interlace.tools/images/og-maintainability.png" alt="ESLint Interlace Plugin" width="100%" /></a>
|
|
116
|
+
<a href="https://eslint.interlace.tools/docs/quality/plugin-maintainability?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-maintainability"><img src="https://eslint.interlace.tools/images/og-maintainability.png" alt="ESLint Interlace Plugin" width="100%" /></a>
|
|
89
117
|
</p>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-maintainability",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.4",
|
|
4
4
|
"description": "ESLint rules for reducing cognitive load and ensuring code readability.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
"./types": {
|
|
14
14
|
"types": "./src/types/index.d.ts",
|
|
15
15
|
"default": "./src/types/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./oxlint": {
|
|
18
|
+
"types": "./src/oxlint.d.ts",
|
|
19
|
+
"default": "./src/oxlint.js"
|
|
16
20
|
}
|
|
17
21
|
},
|
|
18
22
|
"author": "Ofri Peretz <ofriperetzdev@gmail.com>",
|
|
@@ -39,7 +43,9 @@
|
|
|
39
43
|
"engines": {
|
|
40
44
|
"node": ">=18.0.0"
|
|
41
45
|
},
|
|
42
|
-
"peerDependencies": {
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"eslint": "^8.0.0 || ^9.0.0 || ^10.0.0"
|
|
48
|
+
},
|
|
43
49
|
"peerDependenciesMeta": {
|
|
44
50
|
"typescript": {
|
|
45
51
|
"optional": true
|
|
@@ -58,8 +64,18 @@
|
|
|
58
64
|
"url": "https://github.com/ofri-peretz/eslint",
|
|
59
65
|
"directory": "packages/eslint-plugin-maintainability"
|
|
60
66
|
},
|
|
61
|
-
"homepage": "https://
|
|
67
|
+
"homepage": "https://eslint.interlace.tools/docs/quality/plugin-maintainability?utm_source=npm&utm_medium=referral&utm_campaign=eslint-plugin-maintainability",
|
|
62
68
|
"bugs": {
|
|
63
69
|
"url": "https://github.com/ofri-peretz/eslint/issues"
|
|
70
|
+
},
|
|
71
|
+
"scripts": {
|
|
72
|
+
"build": "tsx ../../scripts/build-package.ts",
|
|
73
|
+
"test": "vitest run",
|
|
74
|
+
"test:coverage": "vitest run --coverage",
|
|
75
|
+
"typecheck": "tsc -p tsconfig.lib.json --noEmit"
|
|
76
|
+
},
|
|
77
|
+
"funding": {
|
|
78
|
+
"type": "github",
|
|
79
|
+
"url": "https://github.com/ofri-peretz/eslint"
|
|
64
80
|
}
|
|
65
|
-
}
|
|
81
|
+
}
|
package/src/index.d.ts
CHANGED
|
@@ -29,6 +29,18 @@ export declare const rules: {
|
|
|
29
29
|
'no-unreadable-iife': TSESLint.RuleModule<"unreadableIIFE" | "suggestNamedFunction" | "suggestBlockScope" | "complexIIFE", [(import("./rules/maintainability/no-unreadable-iife").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
30
30
|
name: string;
|
|
31
31
|
};
|
|
32
|
+
'error-message': TSESLint.RuleModule<"missingErrorMessage" | "addErrorMessage", [(import("./rules/error-handling/error-message").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
33
|
+
name: string;
|
|
34
|
+
};
|
|
35
|
+
'no-missing-error-context': TSESLint.RuleModule<"addErrorMessage" | "missingErrorContext" | "addErrorStack" | "useErrorClass", [(import("./rules/error-handling/no-missing-error-context").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
36
|
+
name: string;
|
|
37
|
+
};
|
|
38
|
+
'no-silent-errors': TSESLint.RuleModule<"silentError" | "addErrorLogging" | "addErrorHandling" | "rethrowError", [(import("./rules/error-handling/no-silent-errors").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
39
|
+
name: string;
|
|
40
|
+
};
|
|
41
|
+
'no-unhandled-promise': TSESLint.RuleModule<"unhandledPromise" | "addCatch" | "useTryCatch" | "useAwait", [(import("./rules/error-handling/no-unhandled-promise").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
42
|
+
name: string;
|
|
43
|
+
};
|
|
32
44
|
'maintainability/cognitive-complexity': TSESLint.RuleModule<"highCognitiveComplexity" | "extractMethod" | "simplifyLogic" | "useStrategy", [(import("./rules/maintainability/cognitive-complexity").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
33
45
|
name: string;
|
|
34
46
|
};
|
|
@@ -53,6 +65,18 @@ export declare const rules: {
|
|
|
53
65
|
'maintainability/no-unreadable-iife': TSESLint.RuleModule<"unreadableIIFE" | "suggestNamedFunction" | "suggestBlockScope" | "complexIIFE", [(import("./rules/maintainability/no-unreadable-iife").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
54
66
|
name: string;
|
|
55
67
|
};
|
|
68
|
+
'maintainability/error-message': TSESLint.RuleModule<"missingErrorMessage" | "addErrorMessage", [(import("./rules/error-handling/error-message").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
69
|
+
name: string;
|
|
70
|
+
};
|
|
71
|
+
'maintainability/no-missing-error-context': TSESLint.RuleModule<"addErrorMessage" | "missingErrorContext" | "addErrorStack" | "useErrorClass", [(import("./rules/error-handling/no-missing-error-context").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
72
|
+
name: string;
|
|
73
|
+
};
|
|
74
|
+
'maintainability/no-silent-errors': TSESLint.RuleModule<"silentError" | "addErrorLogging" | "addErrorHandling" | "rethrowError", [(import("./rules/error-handling/no-silent-errors").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
75
|
+
name: string;
|
|
76
|
+
};
|
|
77
|
+
'maintainability/no-unhandled-promise': TSESLint.RuleModule<"unhandledPromise" | "addCatch" | "useTryCatch" | "useAwait", [(import("./rules/error-handling/no-unhandled-promise").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
78
|
+
name: string;
|
|
79
|
+
};
|
|
56
80
|
};
|
|
57
81
|
export declare const plugin: {
|
|
58
82
|
meta: {
|
|
@@ -84,6 +108,18 @@ export declare const plugin: {
|
|
|
84
108
|
'no-unreadable-iife': TSESLint.RuleModule<"unreadableIIFE" | "suggestNamedFunction" | "suggestBlockScope" | "complexIIFE", [(import("./rules/maintainability/no-unreadable-iife").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
85
109
|
name: string;
|
|
86
110
|
};
|
|
111
|
+
'error-message': TSESLint.RuleModule<"missingErrorMessage" | "addErrorMessage", [(import("./rules/error-handling/error-message").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
112
|
+
name: string;
|
|
113
|
+
};
|
|
114
|
+
'no-missing-error-context': TSESLint.RuleModule<"addErrorMessage" | "missingErrorContext" | "addErrorStack" | "useErrorClass", [(import("./rules/error-handling/no-missing-error-context").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
115
|
+
name: string;
|
|
116
|
+
};
|
|
117
|
+
'no-silent-errors': TSESLint.RuleModule<"silentError" | "addErrorLogging" | "addErrorHandling" | "rethrowError", [(import("./rules/error-handling/no-silent-errors").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
118
|
+
name: string;
|
|
119
|
+
};
|
|
120
|
+
'no-unhandled-promise': TSESLint.RuleModule<"unhandledPromise" | "addCatch" | "useTryCatch" | "useAwait", [(import("./rules/error-handling/no-unhandled-promise").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
121
|
+
name: string;
|
|
122
|
+
};
|
|
87
123
|
'maintainability/cognitive-complexity': TSESLint.RuleModule<"highCognitiveComplexity" | "extractMethod" | "simplifyLogic" | "useStrategy", [(import("./rules/maintainability/cognitive-complexity").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
88
124
|
name: string;
|
|
89
125
|
};
|
|
@@ -108,12 +144,24 @@ export declare const plugin: {
|
|
|
108
144
|
'maintainability/no-unreadable-iife': TSESLint.RuleModule<"unreadableIIFE" | "suggestNamedFunction" | "suggestBlockScope" | "complexIIFE", [(import("./rules/maintainability/no-unreadable-iife").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
109
145
|
name: string;
|
|
110
146
|
};
|
|
147
|
+
'maintainability/error-message': TSESLint.RuleModule<"missingErrorMessage" | "addErrorMessage", [(import("./rules/error-handling/error-message").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
148
|
+
name: string;
|
|
149
|
+
};
|
|
150
|
+
'maintainability/no-missing-error-context': TSESLint.RuleModule<"addErrorMessage" | "missingErrorContext" | "addErrorStack" | "useErrorClass", [(import("./rules/error-handling/no-missing-error-context").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
151
|
+
name: string;
|
|
152
|
+
};
|
|
153
|
+
'maintainability/no-silent-errors': TSESLint.RuleModule<"silentError" | "addErrorLogging" | "addErrorHandling" | "rethrowError", [(import("./rules/error-handling/no-silent-errors").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
154
|
+
name: string;
|
|
155
|
+
};
|
|
156
|
+
'maintainability/no-unhandled-promise': TSESLint.RuleModule<"unhandledPromise" | "addCatch" | "useTryCatch" | "useAwait", [(import("./rules/error-handling/no-unhandled-promise").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
157
|
+
name: string;
|
|
158
|
+
};
|
|
111
159
|
};
|
|
112
160
|
};
|
|
113
161
|
export declare const configs: {
|
|
114
162
|
recommended: {
|
|
115
163
|
plugins: {
|
|
116
|
-
|
|
164
|
+
maintainability: {
|
|
117
165
|
meta: {
|
|
118
166
|
name: string;
|
|
119
167
|
version: string;
|
|
@@ -143,6 +191,18 @@ export declare const configs: {
|
|
|
143
191
|
'no-unreadable-iife': TSESLint.RuleModule<"unreadableIIFE" | "suggestNamedFunction" | "suggestBlockScope" | "complexIIFE", [(import("./rules/maintainability/no-unreadable-iife").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
144
192
|
name: string;
|
|
145
193
|
};
|
|
194
|
+
'error-message': TSESLint.RuleModule<"missingErrorMessage" | "addErrorMessage", [(import("./rules/error-handling/error-message").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
195
|
+
name: string;
|
|
196
|
+
};
|
|
197
|
+
'no-missing-error-context': TSESLint.RuleModule<"addErrorMessage" | "missingErrorContext" | "addErrorStack" | "useErrorClass", [(import("./rules/error-handling/no-missing-error-context").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
198
|
+
name: string;
|
|
199
|
+
};
|
|
200
|
+
'no-silent-errors': TSESLint.RuleModule<"silentError" | "addErrorLogging" | "addErrorHandling" | "rethrowError", [(import("./rules/error-handling/no-silent-errors").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
201
|
+
name: string;
|
|
202
|
+
};
|
|
203
|
+
'no-unhandled-promise': TSESLint.RuleModule<"unhandledPromise" | "addCatch" | "useTryCatch" | "useAwait", [(import("./rules/error-handling/no-unhandled-promise").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
204
|
+
name: string;
|
|
205
|
+
};
|
|
146
206
|
'maintainability/cognitive-complexity': TSESLint.RuleModule<"highCognitiveComplexity" | "extractMethod" | "simplifyLogic" | "useStrategy", [(import("./rules/maintainability/cognitive-complexity").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
147
207
|
name: string;
|
|
148
208
|
};
|
|
@@ -167,13 +227,25 @@ export declare const configs: {
|
|
|
167
227
|
'maintainability/no-unreadable-iife': TSESLint.RuleModule<"unreadableIIFE" | "suggestNamedFunction" | "suggestBlockScope" | "complexIIFE", [(import("./rules/maintainability/no-unreadable-iife").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
168
228
|
name: string;
|
|
169
229
|
};
|
|
230
|
+
'maintainability/error-message': TSESLint.RuleModule<"missingErrorMessage" | "addErrorMessage", [(import("./rules/error-handling/error-message").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
231
|
+
name: string;
|
|
232
|
+
};
|
|
233
|
+
'maintainability/no-missing-error-context': TSESLint.RuleModule<"addErrorMessage" | "missingErrorContext" | "addErrorStack" | "useErrorClass", [(import("./rules/error-handling/no-missing-error-context").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
234
|
+
name: string;
|
|
235
|
+
};
|
|
236
|
+
'maintainability/no-silent-errors': TSESLint.RuleModule<"silentError" | "addErrorLogging" | "addErrorHandling" | "rethrowError", [(import("./rules/error-handling/no-silent-errors").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
237
|
+
name: string;
|
|
238
|
+
};
|
|
239
|
+
'maintainability/no-unhandled-promise': TSESLint.RuleModule<"unhandledPromise" | "addCatch" | "useTryCatch" | "useAwait", [(import("./rules/error-handling/no-unhandled-promise").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
240
|
+
name: string;
|
|
241
|
+
};
|
|
170
242
|
};
|
|
171
243
|
};
|
|
172
244
|
};
|
|
173
245
|
rules: {
|
|
174
|
-
'
|
|
175
|
-
'
|
|
176
|
-
'
|
|
246
|
+
'maintainability/cognitive-complexity': "warn";
|
|
247
|
+
'maintainability/identical-functions': "warn";
|
|
248
|
+
'maintainability/max-parameters': "warn";
|
|
177
249
|
};
|
|
178
250
|
};
|
|
179
251
|
};
|
package/src/index.js
CHANGED
|
@@ -15,6 +15,15 @@ const no_lonely_if_1 = require("./rules/maintainability/no-lonely-if");
|
|
|
15
15
|
const no_nested_ternary_1 = require("./rules/maintainability/no-nested-ternary");
|
|
16
16
|
const consistent_function_scoping_1 = require("./rules/maintainability/consistent-function-scoping");
|
|
17
17
|
const no_unreadable_iife_1 = require("./rules/maintainability/no-unreadable-iife");
|
|
18
|
+
// Error-handling rules (wired 2026-05-09 — implementations + docs both
|
|
19
|
+
// existed but the plugin index didn't register them; users following the
|
|
20
|
+
// docs couldn't enable them. They are also exported by `eslint-plugin-
|
|
21
|
+
// reliability` under the same names — the dual export is intentional
|
|
22
|
+
// because the rules apply to both maintainability and reliability concerns).
|
|
23
|
+
const error_message_1 = require("./rules/error-handling/error-message");
|
|
24
|
+
const no_missing_error_context_1 = require("./rules/error-handling/no-missing-error-context");
|
|
25
|
+
const no_silent_errors_1 = require("./rules/error-handling/no-silent-errors");
|
|
26
|
+
const no_unhandled_promise_1 = require("./rules/error-handling/no-unhandled-promise");
|
|
18
27
|
exports.rules = {
|
|
19
28
|
'cognitive-complexity': cognitive_complexity_1.cognitiveComplexity,
|
|
20
29
|
'nested-complexity-hotspots': nested_complexity_hotspots_1.nestedComplexityHotspots,
|
|
@@ -24,6 +33,10 @@ exports.rules = {
|
|
|
24
33
|
'no-nested-ternary': no_nested_ternary_1.noNestedTernary,
|
|
25
34
|
'consistent-function-scoping': consistent_function_scoping_1.consistentFunctionScoping,
|
|
26
35
|
'no-unreadable-iife': no_unreadable_iife_1.noUnreadableIife,
|
|
36
|
+
'error-message': error_message_1.errorMessage,
|
|
37
|
+
'no-missing-error-context': no_missing_error_context_1.noMissingErrorContext,
|
|
38
|
+
'no-silent-errors': no_silent_errors_1.noSilentErrors,
|
|
39
|
+
'no-unhandled-promise': no_unhandled_promise_1.noUnhandledPromise,
|
|
27
40
|
// Categorized names
|
|
28
41
|
'maintainability/cognitive-complexity': cognitive_complexity_1.cognitiveComplexity,
|
|
29
42
|
'maintainability/nested-complexity-hotspots': nested_complexity_hotspots_1.nestedComplexityHotspots,
|
|
@@ -33,23 +46,31 @@ exports.rules = {
|
|
|
33
46
|
'maintainability/no-nested-ternary': no_nested_ternary_1.noNestedTernary,
|
|
34
47
|
'maintainability/consistent-function-scoping': consistent_function_scoping_1.consistentFunctionScoping,
|
|
35
48
|
'maintainability/no-unreadable-iife': no_unreadable_iife_1.noUnreadableIife,
|
|
49
|
+
'maintainability/error-message': error_message_1.errorMessage,
|
|
50
|
+
'maintainability/no-missing-error-context': no_missing_error_context_1.noMissingErrorContext,
|
|
51
|
+
'maintainability/no-silent-errors': no_silent_errors_1.noSilentErrors,
|
|
52
|
+
'maintainability/no-unhandled-promise': no_unhandled_promise_1.noUnhandledPromise,
|
|
36
53
|
};
|
|
37
54
|
exports.plugin = {
|
|
38
55
|
meta: {
|
|
39
|
-
name
|
|
40
|
-
|
|
56
|
+
// Unscoped, matching package.json `name` and every other plugin in the
|
|
57
|
+
// ecosystem (`eslint-plugin-*`). These packages are published WITHOUT the
|
|
58
|
+
// `@interlace` scope; a scoped name here drifts from how consumers install
|
|
59
|
+
// and reference the plugin. Locked in index.test.ts.
|
|
60
|
+
name: 'eslint-plugin-maintainability',
|
|
61
|
+
version: '3.0.4',
|
|
41
62
|
},
|
|
42
63
|
rules: exports.rules,
|
|
43
64
|
};
|
|
44
65
|
exports.configs = {
|
|
45
66
|
recommended: {
|
|
46
67
|
plugins: {
|
|
47
|
-
'
|
|
68
|
+
'maintainability': exports.plugin,
|
|
48
69
|
},
|
|
49
70
|
rules: {
|
|
50
|
-
'
|
|
51
|
-
'
|
|
52
|
-
'
|
|
71
|
+
'maintainability/cognitive-complexity': 'warn',
|
|
72
|
+
'maintainability/identical-functions': 'warn',
|
|
73
|
+
'maintainability/max-parameters': 'warn',
|
|
53
74
|
},
|
|
54
75
|
},
|
|
55
76
|
};
|
package/src/oxlint.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Ofri Peretz
|
|
3
|
+
* Licensed under the MIT License. Use of this source code is governed by the
|
|
4
|
+
* MIT license that can be found in the LICENSE file.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* oxlint sub-export.
|
|
8
|
+
*
|
|
9
|
+
* Consumers wire oxlint to this entry via:
|
|
10
|
+
* { "jsPlugins": ["eslint-plugin-maintainability/oxlint"] }
|
|
11
|
+
*
|
|
12
|
+
* oxlint's JS plugin loader does `require('eslint-plugin-maintainability/oxlint')` and reads
|
|
13
|
+
* `.rules` off the result. The compiled output of `export = plugin` is
|
|
14
|
+
* `module.exports = plugin`, which gives oxlint the plugin object directly
|
|
15
|
+
* (`{ meta, rules }`) — no wrapper, no `.default` indirection.
|
|
16
|
+
*
|
|
17
|
+
* Generated by scripts/generate-oxlint-shims.mjs. Do not edit by hand.
|
|
18
|
+
*/
|
|
19
|
+
import { plugin } from './index';
|
|
20
|
+
export = plugin;
|
package/src/oxlint.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) 2025 Ofri Peretz
|
|
4
|
+
* Licensed under the MIT License. Use of this source code is governed by the
|
|
5
|
+
* MIT license that can be found in the LICENSE file.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* oxlint sub-export.
|
|
9
|
+
*
|
|
10
|
+
* Consumers wire oxlint to this entry via:
|
|
11
|
+
* { "jsPlugins": ["eslint-plugin-maintainability/oxlint"] }
|
|
12
|
+
*
|
|
13
|
+
* oxlint's JS plugin loader does `require('eslint-plugin-maintainability/oxlint')` and reads
|
|
14
|
+
* `.rules` off the result. The compiled output of `export = plugin` is
|
|
15
|
+
* `module.exports = plugin`, which gives oxlint the plugin object directly
|
|
16
|
+
* (`{ meta, rules }`) — no wrapper, no `.default` indirection.
|
|
17
|
+
*
|
|
18
|
+
* Generated by scripts/generate-oxlint-shims.mjs. Do not edit by hand.
|
|
19
|
+
*/
|
|
20
|
+
const index_1 = require("./index");
|
|
21
|
+
module.exports = index_1.plugin;
|
|
@@ -13,6 +13,7 @@ exports.errorMessage = (0, eslint_devkit_1.createRule)({
|
|
|
13
13
|
meta: {
|
|
14
14
|
type: 'problem',
|
|
15
15
|
docs: {
|
|
16
|
+
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-maintainability/docs/rules/error-message.md',
|
|
16
17
|
description: 'Enforce providing a message when creating built-in Error objects for better debugging',
|
|
17
18
|
},
|
|
18
19
|
hasSuggestions: true,
|
|
@@ -64,6 +65,7 @@ exports.errorMessage = (0, eslint_devkit_1.createRule)({
|
|
|
64
65
|
function isErrorConstructor(name) {
|
|
65
66
|
return errorConstructors.has(name);
|
|
66
67
|
}
|
|
68
|
+
// oxlint-disable-next-line consistent-function-scoping
|
|
67
69
|
function hasMessageArgument(node) {
|
|
68
70
|
// Check if there are arguments
|
|
69
71
|
if (!node.arguments || node.arguments.length === 0) {
|
|
@@ -65,6 +65,7 @@ exports.noMissingErrorContext = (0, eslint_devkit_2.createRule)({
|
|
|
65
65
|
meta: {
|
|
66
66
|
type: 'suggestion',
|
|
67
67
|
docs: {
|
|
68
|
+
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-maintainability/docs/rules/no-missing-error-context.md',
|
|
68
69
|
description: 'Detects thrown errors without context',
|
|
69
70
|
},
|
|
70
71
|
hasSuggestions: true,
|
|
@@ -135,7 +136,7 @@ exports.noMissingErrorContext = (0, eslint_devkit_2.createRule)({
|
|
|
135
136
|
],
|
|
136
137
|
create(context, [options = {}]) {
|
|
137
138
|
const { requireMessage = true, requireStackTrace = false, ignoreInTests = true, } = options || {};
|
|
138
|
-
const filename = context.
|
|
139
|
+
const filename = context.filename;
|
|
139
140
|
const isTestFile = ignoreInTests && /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);
|
|
140
141
|
if (isTestFile) {
|
|
141
142
|
return {};
|
|
@@ -71,6 +71,7 @@ exports.noSilentErrors = (0, eslint_devkit_2.createRule)({
|
|
|
71
71
|
meta: {
|
|
72
72
|
type: 'problem',
|
|
73
73
|
docs: {
|
|
74
|
+
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-maintainability/docs/rules/no-silent-errors.md',
|
|
74
75
|
description: 'Detects empty catch blocks',
|
|
75
76
|
},
|
|
76
77
|
hasSuggestions: true,
|
|
@@ -135,12 +136,12 @@ exports.noSilentErrors = (0, eslint_devkit_2.createRule)({
|
|
|
135
136
|
],
|
|
136
137
|
create(context, [options = {}]) {
|
|
137
138
|
const { allowWithComment = false, ignoreInTests = true } = options || {};
|
|
138
|
-
const filename = context.
|
|
139
|
+
const filename = context.filename;
|
|
139
140
|
const isTestFile = ignoreInTests && /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);
|
|
140
141
|
if (isTestFile) {
|
|
141
142
|
return {};
|
|
142
143
|
}
|
|
143
|
-
const sourceCode = context.sourceCode
|
|
144
|
+
const sourceCode = context.sourceCode;
|
|
144
145
|
/**
|
|
145
146
|
* Check catch clauses
|
|
146
147
|
*/
|
|
@@ -136,7 +136,10 @@ exports.noUnhandledPromise = (0, eslint_devkit_2.createRule)({
|
|
|
136
136
|
meta: {
|
|
137
137
|
type: 'problem',
|
|
138
138
|
docs: {
|
|
139
|
+
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-maintainability/docs/rules/no-unhandled-promise.md',
|
|
139
140
|
description: 'Detects unhandled Promise rejections',
|
|
141
|
+
cwe: 'CWE-1024',
|
|
142
|
+
cvss: 7.5,
|
|
140
143
|
},
|
|
141
144
|
hasSuggestions: true,
|
|
142
145
|
messages: {
|
|
@@ -201,12 +204,12 @@ exports.noUnhandledPromise = (0, eslint_devkit_2.createRule)({
|
|
|
201
204
|
],
|
|
202
205
|
create(context, [options = {}]) {
|
|
203
206
|
const { ignoreInTests = true, ignoreVoidExpressions = false } = options || {};
|
|
204
|
-
const filename = context.
|
|
207
|
+
const filename = context.filename;
|
|
205
208
|
const isTestFile = ignoreInTests && /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);
|
|
206
209
|
if (isTestFile) {
|
|
207
210
|
return {};
|
|
208
211
|
}
|
|
209
|
-
// const sourceCode = context.sourceCode
|
|
212
|
+
// const sourceCode = context.sourceCode; // Not used
|
|
210
213
|
/**
|
|
211
214
|
* Check call expressions for unhandled promises
|
|
212
215
|
*/
|
|
@@ -14,7 +14,10 @@ exports.cognitiveComplexity = (0, eslint_devkit_2.createRule)({
|
|
|
14
14
|
meta: {
|
|
15
15
|
type: 'suggestion',
|
|
16
16
|
docs: {
|
|
17
|
+
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-maintainability/docs/rules/cognitive-complexity.md',
|
|
17
18
|
description: 'Enforces a maximum cognitive complexity threshold with refactoring guidance',
|
|
19
|
+
cwe: 'CWE-1104',
|
|
20
|
+
cvss: 7.5,
|
|
18
21
|
},
|
|
19
22
|
messages: {
|
|
20
23
|
// 🎯 Token optimization: 40% reduction (60→36 tokens) - keeps complexity metrics inline
|
|
@@ -79,7 +82,7 @@ exports.cognitiveComplexity = (0, eslint_devkit_2.createRule)({
|
|
|
79
82
|
create(context) {
|
|
80
83
|
const options = context.options[0] || {};
|
|
81
84
|
const { maxComplexity = 15 } = options || {};
|
|
82
|
-
const filename = context.filename
|
|
85
|
+
const filename = context.filename;
|
|
83
86
|
/**
|
|
84
87
|
* Calculate cognitive complexity for a function
|
|
85
88
|
* Based on SonarQube's cognitive complexity algorithm
|
|
@@ -300,6 +303,7 @@ exports.cognitiveComplexity = (0, eslint_devkit_2.createRule)({
|
|
|
300
303
|
/**
|
|
301
304
|
* Suggest architectural patterns
|
|
302
305
|
*/
|
|
306
|
+
// oxlint-disable-next-line consistent-function-scoping
|
|
303
307
|
function suggestPattern(breakdown) {
|
|
304
308
|
if (breakdown.switches >= 2)
|
|
305
309
|
return 'Strategy Pattern';
|
|
@@ -13,6 +13,7 @@ exports.consistentFunctionScoping = (0, eslint_devkit_1.createRule)({
|
|
|
13
13
|
meta: {
|
|
14
14
|
type: 'suggestion',
|
|
15
15
|
docs: {
|
|
16
|
+
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-maintainability/docs/rules/consistent-function-scoping.md',
|
|
16
17
|
description: 'Move function definitions to the highest possible scope to improve readability and performance',
|
|
17
18
|
},
|
|
18
19
|
hasSuggestions: true,
|
|
@@ -80,21 +81,66 @@ exports.consistentFunctionScoping = (0, eslint_devkit_1.createRule)({
|
|
|
80
81
|
if (node.parent?.type === 'Program' || node.parent?.type === 'ExportNamedDeclaration' || node.parent?.type === 'ExportDefaultDeclaration') {
|
|
81
82
|
return;
|
|
82
83
|
}
|
|
84
|
+
// Class methods / class field initializers — these are bound to the
|
|
85
|
+
// instance and cannot be moved to module scope. The parent chain is
|
|
86
|
+
// `MethodDefinition` (regular methods) or `PropertyDefinition` (class
|
|
87
|
+
// fields). Without this exemption, every method that doesn't reference
|
|
88
|
+
// `this` is wrongly flagged.
|
|
89
|
+
const p = node.parent;
|
|
90
|
+
if (p?.type === 'MethodDefinition' || p?.type === 'PropertyDefinition') {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
// Inline callbacks passed to higher-order methods. These functions are
|
|
94
|
+
// inline BY DESIGN — moving `arr.reduce((a, b) => a + b)` to a
|
|
95
|
+
// top-level named function loses inline locality without improving
|
|
96
|
+
// anything. Common high-arity hosts:
|
|
97
|
+
// - Array methods: map, filter, reduce, forEach, find, some, every, sort, flatMap
|
|
98
|
+
// - Promise: .then, .catch, .finally
|
|
99
|
+
// - Event hosts: .on('event', ...), addEventListener('event', ...)
|
|
100
|
+
// - Scheduler: setTimeout, setInterval, requestAnimationFrame
|
|
101
|
+
// - Promise constructor: `new Promise((resolve, reject) => ...)`
|
|
102
|
+
if (p?.type === 'CallExpression') {
|
|
103
|
+
const callee = p.callee;
|
|
104
|
+
if (callee.type === 'MemberExpression' && callee.property.type === 'Identifier') {
|
|
105
|
+
const HOST_METHODS = new Set([
|
|
106
|
+
'map', 'filter', 'reduce', 'reduceRight', 'forEach', 'find', 'findIndex',
|
|
107
|
+
'some', 'every', 'sort', 'flatMap', 'flat',
|
|
108
|
+
'then', 'catch', 'finally',
|
|
109
|
+
'on', 'once', 'addEventListener', 'removeEventListener', 'subscribe',
|
|
110
|
+
]);
|
|
111
|
+
if (HOST_METHODS.has(callee.property.name))
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (callee.type === 'Identifier') {
|
|
115
|
+
const HOST_FNS = new Set([
|
|
116
|
+
'setTimeout', 'setInterval', 'setImmediate',
|
|
117
|
+
'requestAnimationFrame', 'requestIdleCallback', 'queueMicrotask',
|
|
118
|
+
]);
|
|
119
|
+
if (HOST_FNS.has(callee.name))
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// `new Promise((resolve, reject) => ...)` — executor must stay inline.
|
|
124
|
+
if (p?.type === 'NewExpression' &&
|
|
125
|
+
p.callee.type === 'Identifier' &&
|
|
126
|
+
p.callee.name === 'Promise') {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
83
129
|
// Get all variables referenced in the function body
|
|
84
130
|
const referencedVars = new Set();
|
|
85
|
-
function collectReferences(
|
|
131
|
+
function collectReferences(astNode, depth = 0, visited = new Set()) {
|
|
86
132
|
// Prevent infinite recursion
|
|
87
|
-
if (depth > 10 || visited.has(
|
|
133
|
+
if (depth > 10 || visited.has(astNode)) {
|
|
88
134
|
return;
|
|
89
135
|
}
|
|
90
|
-
visited.add(
|
|
91
|
-
if (
|
|
92
|
-
referencedVars.add(
|
|
136
|
+
visited.add(astNode);
|
|
137
|
+
if (astNode.type === 'Identifier') {
|
|
138
|
+
referencedVars.add(astNode.name);
|
|
93
139
|
}
|
|
94
140
|
// Recursively check all child nodes with depth limit
|
|
95
141
|
if (depth < 10) {
|
|
96
|
-
for (const key in
|
|
97
|
-
const child =
|
|
142
|
+
for (const key in astNode) {
|
|
143
|
+
const child = astNode[key];
|
|
98
144
|
if (child && typeof child === 'object') {
|
|
99
145
|
if (Array.isArray(child)) {
|
|
100
146
|
child.forEach(item => {
|
|
@@ -103,7 +149,9 @@ exports.consistentFunctionScoping = (0, eslint_devkit_1.createRule)({
|
|
|
103
149
|
}
|
|
104
150
|
});
|
|
105
151
|
}
|
|
106
|
-
else if ('type' in child
|
|
152
|
+
else if ('type' in child) {
|
|
153
|
+
// Outer `child && typeof child === 'object'` already guarantees
|
|
154
|
+
// a non-null object here (CodeQL: `js/comparison-between-incompatible-types`).
|
|
107
155
|
collectReferences(child, depth + 1, visited);
|
|
108
156
|
}
|
|
109
157
|
}
|
|
@@ -14,6 +14,7 @@ exports.identicalFunctions = (0, eslint_devkit_2.createRule)({
|
|
|
14
14
|
meta: {
|
|
15
15
|
type: 'suggestion',
|
|
16
16
|
docs: {
|
|
17
|
+
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-maintainability/docs/rules/identical-functions.md',
|
|
17
18
|
description: 'Detects duplicate function implementations with DRY refactoring suggestions',
|
|
18
19
|
},
|
|
19
20
|
messages: {
|
|
@@ -87,8 +88,8 @@ exports.identicalFunctions = (0, eslint_devkit_2.createRule)({
|
|
|
87
88
|
create(context) {
|
|
88
89
|
const options = context.options[0] || {};
|
|
89
90
|
const { minLines = 3, similarityThreshold = 0.9, ignoreTestFiles = true, } = options || {};
|
|
90
|
-
const sourceCode = context.sourceCode
|
|
91
|
-
const filename = context.filename
|
|
91
|
+
const sourceCode = context.sourceCode;
|
|
92
|
+
const filename = context.filename;
|
|
92
93
|
// Skip test files if configured
|
|
93
94
|
if (ignoreTestFiles && /\.(test|spec)\.[jt]sx?$/.test(filename)) {
|
|
94
95
|
return {};
|
|
@@ -98,6 +99,7 @@ exports.identicalFunctions = (0, eslint_devkit_2.createRule)({
|
|
|
98
99
|
* Normalize function body for comparison
|
|
99
100
|
* Remove variable names, keep structure
|
|
100
101
|
*/
|
|
102
|
+
// oxlint-disable-next-line consistent-function-scoping
|
|
101
103
|
function normalizeBody(body) {
|
|
102
104
|
return (body
|
|
103
105
|
// Remove whitespace
|
|
@@ -128,6 +130,7 @@ exports.identicalFunctions = (0, eslint_devkit_2.createRule)({
|
|
|
128
130
|
/**
|
|
129
131
|
* Levenshtein distance algorithm
|
|
130
132
|
*/
|
|
133
|
+
// oxlint-disable-next-line consistent-function-scoping
|
|
131
134
|
function levenshteinDistance(str1, str2) {
|
|
132
135
|
const matrix = [];
|
|
133
136
|
for (let i = 0; i <= str2.length; i++) {
|
|
@@ -188,6 +191,7 @@ exports.identicalFunctions = (0, eslint_devkit_2.createRule)({
|
|
|
188
191
|
/**
|
|
189
192
|
* Generate unified function suggestion
|
|
190
193
|
*/
|
|
194
|
+
// oxlint-disable-next-line consistent-function-scoping
|
|
191
195
|
function generateUnifiedFunction(group) {
|
|
192
196
|
const firstFunc = group.functions[0];
|
|
193
197
|
// Analyze parameter differences
|
|
@@ -206,6 +210,7 @@ exports.identicalFunctions = (0, eslint_devkit_2.createRule)({
|
|
|
206
210
|
/**
|
|
207
211
|
* Suggest refactoring approach
|
|
208
212
|
*/
|
|
213
|
+
// oxlint-disable-next-line consistent-function-scoping
|
|
209
214
|
function suggestRefactoringApproach(group) {
|
|
210
215
|
const funcNames = group.functions.map((f) => f.name);
|
|
211
216
|
const hasRolePattern = funcNames.some((name) => /user|admin|guest|customer/i.test(name));
|
|
@@ -20,6 +20,7 @@ exports.maxParameters = (0, eslint_devkit_2.createRule)({
|
|
|
20
20
|
meta: {
|
|
21
21
|
type: 'suggestion',
|
|
22
22
|
docs: {
|
|
23
|
+
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-maintainability/docs/rules/max-parameters.md',
|
|
23
24
|
description: 'Detects functions with too many parameters',
|
|
24
25
|
},
|
|
25
26
|
messages: {
|
|
@@ -52,6 +52,7 @@ exports.nestedComplexityHotspots = (0, eslint_devkit_2.createRule)({
|
|
|
52
52
|
meta: {
|
|
53
53
|
type: 'suggestion',
|
|
54
54
|
docs: {
|
|
55
|
+
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-maintainability/docs/rules/nested-complexity-hotspots.md',
|
|
55
56
|
description: 'Identifies nested control structures that harm readability',
|
|
56
57
|
},
|
|
57
58
|
messages: {
|
|
@@ -122,7 +123,7 @@ exports.nestedComplexityHotspots = (0, eslint_devkit_2.createRule)({
|
|
|
122
123
|
],
|
|
123
124
|
create(context, [options = {}]) {
|
|
124
125
|
const { maxDepth = 4, countConditionals = true, countLoops = true, } = options || {};
|
|
125
|
-
// const sourceCode = context.sourceCode
|
|
126
|
+
// const sourceCode = context.sourceCode; // Not used
|
|
126
127
|
/**
|
|
127
128
|
* Check control structures
|
|
128
129
|
*/
|
|
@@ -13,6 +13,7 @@ exports.noLonelyIf = (0, eslint_devkit_1.createRule)({
|
|
|
13
13
|
meta: {
|
|
14
14
|
type: 'suggestion',
|
|
15
15
|
docs: {
|
|
16
|
+
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-maintainability/docs/rules/no-lonely-if.md',
|
|
16
17
|
description: 'Prevent lone if statements inside else blocks - use else if instead',
|
|
17
18
|
},
|
|
18
19
|
hasSuggestions: true,
|
|
@@ -58,6 +59,7 @@ exports.noLonelyIf = (0, eslint_devkit_1.createRule)({
|
|
|
58
59
|
}
|
|
59
60
|
return false;
|
|
60
61
|
}
|
|
62
|
+
// oxlint-disable-next-line consistent-function-scoping
|
|
61
63
|
function isLonelyIf(node) {
|
|
62
64
|
// Check if this if statement is inside an else block (not a proper else if)
|
|
63
65
|
const parent = node.parent;
|
|
@@ -13,6 +13,7 @@ exports.noNestedTernary = (0, eslint_devkit_1.createRule)({
|
|
|
13
13
|
meta: {
|
|
14
14
|
type: 'suggestion',
|
|
15
15
|
docs: {
|
|
16
|
+
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-maintainability/docs/rules/no-nested-ternary.md',
|
|
16
17
|
description: 'Prevent nested ternary expressions for better readability',
|
|
17
18
|
},
|
|
18
19
|
hasSuggestions: true,
|
|
@@ -13,6 +13,7 @@ exports.noUnreadableIife = (0, eslint_devkit_1.createRule)({
|
|
|
13
13
|
meta: {
|
|
14
14
|
type: 'suggestion',
|
|
15
15
|
docs: {
|
|
16
|
+
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-maintainability/docs/rules/no-unreadable-iife.md',
|
|
16
17
|
description: 'Prevent unreadable Immediately Invoked Function Expressions that harm code clarity',
|
|
17
18
|
},
|
|
18
19
|
hasSuggestions: true,
|
|
@@ -77,6 +78,7 @@ exports.noUnreadableIife = (0, eslint_devkit_1.createRule)({
|
|
|
77
78
|
create(context) {
|
|
78
79
|
const [options] = context.options;
|
|
79
80
|
const { maxStatements = 3, maxDepth = 2, allowReturningIIFE = true } = options || {};
|
|
81
|
+
// oxlint-disable-next-line consistent-function-scoping
|
|
80
82
|
function isIIFE(node) {
|
|
81
83
|
// Check if this is a call expression with a function expression or arrow function
|
|
82
84
|
// Note: In typescript-eslint AST, parentheses don't create a separate node type,
|
|
@@ -93,47 +95,44 @@ exports.noUnreadableIife = (0, eslint_devkit_1.createRule)({
|
|
|
93
95
|
}
|
|
94
96
|
return false;
|
|
95
97
|
}
|
|
98
|
+
// oxlint-disable-next-line consistent-function-scoping
|
|
96
99
|
function countStatements(body) {
|
|
97
100
|
if (body.type === 'BlockStatement') {
|
|
98
101
|
return body.body.length;
|
|
99
102
|
}
|
|
100
103
|
return 1; // Single expression
|
|
101
104
|
}
|
|
102
|
-
function calculateDepth(node
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
for (const item of child) {
|
|
126
|
-
if (item && typeof item === 'object' && 'type' in item) {
|
|
127
|
-
maxDepthFound = Math.max(maxDepthFound, calculateDepth(item, currentDepth));
|
|
128
|
-
}
|
|
129
|
-
}
|
|
105
|
+
function calculateDepth(node) {
|
|
106
|
+
// AST-based nesting depth calculation — no regex needed
|
|
107
|
+
const CONTROL_FLOW_TYPES = new Set([
|
|
108
|
+
'IfStatement', 'ForStatement', 'ForInStatement', 'ForOfStatement',
|
|
109
|
+
'WhileStatement', 'DoWhileStatement', 'SwitchStatement', 'TryStatement',
|
|
110
|
+
]);
|
|
111
|
+
// Non-AST keys that should never be traversed
|
|
112
|
+
const SKIP_KEYS = new Set(['parent', 'loc', 'range', 'tokens', 'comments', 'leadingComments', 'trailingComments']);
|
|
113
|
+
// oxlint-disable-next-line consistent-function-scoping
|
|
114
|
+
function isASTNode(value) {
|
|
115
|
+
return !!value && typeof value === 'object' && 'type' in value && 'loc' in value;
|
|
116
|
+
}
|
|
117
|
+
let maxNestingDepth = 0;
|
|
118
|
+
function walk(current, depth) {
|
|
119
|
+
const isControlFlow = CONTROL_FLOW_TYPES.has(current.type);
|
|
120
|
+
const newDepth = isControlFlow ? depth + 1 : depth;
|
|
121
|
+
maxNestingDepth = Math.max(maxNestingDepth, newDepth);
|
|
122
|
+
Object.entries(current).forEach(([key, value]) => {
|
|
123
|
+
if (SKIP_KEYS.has(key))
|
|
124
|
+
return;
|
|
125
|
+
if (Array.isArray(value)) {
|
|
126
|
+
value.forEach(item => { if (isASTNode(item))
|
|
127
|
+
walk(item, newDepth); });
|
|
130
128
|
}
|
|
131
|
-
else if (
|
|
132
|
-
|
|
129
|
+
else if (isASTNode(value)) {
|
|
130
|
+
walk(value, newDepth);
|
|
133
131
|
}
|
|
134
|
-
}
|
|
132
|
+
});
|
|
135
133
|
}
|
|
136
|
-
|
|
134
|
+
walk(node, 0);
|
|
135
|
+
return maxNestingDepth;
|
|
137
136
|
}
|
|
138
137
|
function hasComplexLogic(body) {
|
|
139
138
|
if (body.type !== 'BlockStatement') {
|