eslint-plugin-maintainability 3.0.2 → 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 CHANGED
@@ -1,3 +1,60 @@
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
+
48
+ ## [3.0.2] - 2026-02-06
49
+
50
+ ### Bug Fixes
51
+
52
+ - align codecov component names and update docs components ([0a59a86c](https://github.com/ofri-peretz/eslint/commit/0a59a86c))
53
+
54
+ ### ❤️ Thank You
55
+
56
+ - Ofri Peretz
57
+
1
58
  ## [3.0.1] - 2026-02-02
2
59
 
3
60
  This was a version bump only for eslint-plugin-maintainability to align it with other projects, there were no code changes.
package/README.md CHANGED
@@ -1,9 +1,9 @@
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
- Security-focused ESLint plugin.
6
+ Maintainability rules — complexity ceilings, dead code, and readability guardrails.
7
7
  </p>
8
8
 
9
9
  <p align="center">
@@ -14,10 +14,11 @@
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 Security-focused ESLint plugin.
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 towarn in recommended preset. |
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
- | Rule | CWE | OWASP | CVSS | Description | 💼 | ⚠️ | 🔧 | 💡 | 🚫 |
57
- | :--- | :---: | :---: | :---: | :--- | :---: | :---: | :---: | :---: | :---: |
58
- | [complexity](https://eslint.interlace.tools/docs/quality/plugin-maintainability/rules/complexity) | | | | Enforce complexity | | | | | |
59
- | [development](https://eslint.interlace.tools/docs/quality/plugin-maintainability/rules/development) | | | | Enforce development | | | | | |
60
- | [duplication](https://eslint.interlace.tools/docs/quality/plugin-maintainability/rules/duplication) | | | | Enforce duplication | | | | | |
61
- | [error-handling](https://eslint.interlace.tools/docs/quality/plugin-maintainability/rules/error-handling) | | | | Enforce error handling | | | | | |
62
- | [maintainability](https://eslint.interlace.tools/docs/quality/plugin-maintainability/rules/maintainability) | | | | Enforce maintainability | | | | | |
63
- | [quality](https://eslint.interlace.tools/docs/quality/plugin-maintainability/rules/quality) | | | | Enforce quality | | | | | |
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) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-secure-coding.svg?style=flat-square)](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) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-pg.svg?style=flat-square)](https://www.npmjs.com/package/eslint-plugin-pg) | PostgreSQL security & best practices. |
73
- | [`eslint-plugin-crypto`](https://www.npmjs.com/package/eslint-plugin-crypto) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-crypto.svg?style=flat-square)](https://www.npmjs.com/package/eslint-plugin-crypto) | NodeJS Cryptography security rules. |
92
+ | [`eslint-plugin-node-security`](https://www.npmjs.com/package/eslint-plugin-node-security) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-node-security.svg?style=flat-square)](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) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-jwt.svg?style=flat-square)](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) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-browser-security.svg?style=flat-square)](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) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-express-security.svg?style=flat-square)](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) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-vercel-ai-security.svg?style=flat-square)](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) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-import-next.svg?style=flat-square)](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
+ [![GitHub stars](https://img.shields.io/github/stars/ofri-peretz/eslint?style=social)](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.2",
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://github.com/ofri-peretz/eslint/tree/main/packages/eslint-plugin-maintainability#readme",
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
- '@interlace/maintainability': {
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
- '@interlace/maintainability/maintainability/cognitive-complexity': "warn";
175
- '@interlace/maintainability/maintainability/identical-functions': "warn";
176
- '@interlace/maintainability/maintainability/max-parameters': "warn";
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: '@interlace/eslint-plugin-maintainability',
40
- version: '1.0.0',
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
- '@interlace/maintainability': exports.plugin,
68
+ 'maintainability': exports.plugin,
48
69
  },
49
70
  rules: {
50
- '@interlace/maintainability/maintainability/cognitive-complexity': 'warn',
51
- '@interlace/maintainability/maintainability/identical-functions': 'warn',
52
- '@interlace/maintainability/maintainability/max-parameters': 'warn',
71
+ 'maintainability/cognitive-complexity': 'warn',
72
+ 'maintainability/identical-functions': 'warn',
73
+ 'maintainability/max-parameters': 'warn',
53
74
  },
54
75
  },
55
76
  };
@@ -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.getFilename();
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.getFilename();
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 || 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.getFilename();
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 || context.sourceCode; // Not used
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 || context.getFilename();
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(node, depth = 0, visited = new Set()) {
131
+ function collectReferences(astNode, depth = 0, visited = new Set()) {
86
132
  // Prevent infinite recursion
87
- if (depth > 10 || visited.has(node)) {
133
+ if (depth > 10 || visited.has(astNode)) {
88
134
  return;
89
135
  }
90
- visited.add(node);
91
- if (node.type === 'Identifier') {
92
- referencedVars.add(node.name);
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 node) {
97
- const child = node[key];
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 && typeof child === 'object' && child !== null) {
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 || context.sourceCode;
91
- const filename = context.filename || context.getFilename();
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 || context.sourceCode; // Not used
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, currentDepth = 0) {
103
- let maxDepthFound = currentDepth;
104
- // Check for nested constructs that increase complexity
105
- if (node.type === 'IfStatement' ||
106
- node.type === 'ForStatement' ||
107
- node.type === 'WhileStatement' ||
108
- node.type === 'DoWhileStatement' ||
109
- node.type === 'SwitchStatement' ||
110
- node.type === 'TryStatement') {
111
- currentDepth++;
112
- maxDepthFound = Math.max(maxDepthFound, currentDepth);
113
- }
114
- // Properties to skip to avoid circular references
115
- const skipProperties = new Set(['parent', 'tokens', 'comments', 'loc', 'range']);
116
- // Recursively check children
117
- for (const key in node) {
118
- // Skip properties that cause circular references or aren't AST nodes
119
- if (skipProperties.has(key)) {
120
- continue;
121
- }
122
- const child = node[key];
123
- if (child && typeof child === 'object') {
124
- if (Array.isArray(child)) {
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 ('type' in child) {
132
- maxDepthFound = Math.max(maxDepthFound, calculateDepth(child, currentDepth));
129
+ else if (isASTNode(value)) {
130
+ walk(value, newDepth);
133
131
  }
134
- }
132
+ });
135
133
  }
136
- return maxDepthFound;
134
+ walk(node, 0);
135
+ return maxNestingDepth;
137
136
  }
138
137
  function hasComplexLogic(body) {
139
138
  if (body.type !== 'BlockStatement') {