eslint-plugin-reliability 3.1.2 → 3.1.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 ADDED
@@ -0,0 +1,130 @@
1
+ ## [3.1.3] - 2026-05-03
2
+
3
+ ## 3.1.4
4
+
5
+ ### Patch Changes
6
+
7
+ - [#141](https://github.com/ofri-peretz/eslint/pull/141) [`38ab670`](https://github.com/ofri-peretz/eslint/commit/38ab670a0221684f4fd3d5dc3c05ddec7458ca2b) Thanks [@ofri-peretz](https://github.com/ofri-peretz)! - fix: remove false `meta.fixable: 'code'` declarations from 21 rules that had no `fix()` function
8
+
9
+ Rules that declared `fixable: 'code'` in their ESLint meta without an actual `fix()` implementation would show the ⚡ auto-fix icon in editors and CI formatters but apply no change when `--fix` was run. This patch removes the misleading declaration from:
10
+ - `browser-security/no-clickjacking`
11
+ - `import-next/first`, `named`, `no-barrel-import`, `no-import-module-exports`, `no-namespace`
12
+ - `node-security/no-buffer-overread`, `no-unsafe-dynamic-require`, `no-zip-slip`
13
+ - `react-features/react-no-inline-functions`
14
+ - `reliability/no-jsdoc-terminator-in-example` (uses `suggest`, not auto-fix; corrected to `hasSuggestions: true` only)
15
+ - `secure-coding/no-directive-injection`, `no-electron-security-issues`, `no-graphql-injection`, `no-improper-sanitization`, `no-improper-type-validation`, `no-ldap-injection`, `no-unchecked-loop-condition`, `no-unlimited-resource-allocation`, `no-weak-password-recovery`, `no-xpath-injection`
16
+
17
+ - [#143](https://github.com/ofri-peretz/eslint/pull/143) [`213cde1`](https://github.com/ofri-peretz/eslint/commit/213cde190ff2aea49ca7c1b533170940f879d9b4) Thanks [@ofri-peretz](https://github.com/ofri-peretz)! - fix(no-missing-null-checks): eliminate 53 false positives via three new narrowing patterns
18
+
19
+ Rules that were recognized as null guards are now correctly identified as safe:
20
+ 1. **Truthy if guard** — `if (obj) { obj.prop }` — direct truthy check proves non-null. Also covers chains: `if (response)` protects `response.data.items`.
21
+ 2. **Short-circuit AND** — `obj && obj.prop` — right side of `&&` only runs when left is truthy.
22
+ 3. **Ternary consequent** — `obj ? obj.prop : fallback` — truthy test guards the consequent.
23
+
24
+ Also: bumped `beforeAll` timeout to 30 seconds in 7 compatibility test files (`__compatibility__/*.spec.ts`). Native-addon packages routinely exceed the previous 10-second default on a cold ESM load.
25
+
26
+ - Updated dependencies [[`736a5fe`](https://github.com/ofri-peretz/eslint/commit/736a5fed47e673f6157ea900b29fe2a54e4bc7df)]:
27
+ - @interlace/eslint-devkit@1.4.1
28
+
29
+ ### Bug Fixes
30
+
31
+ - `no-missing-null-checks`: exempt provably-non-null identifiers (built-in singletons like `Math`, `JSON`, `console`, error classes; catch-clause params; constructor results; top-level imports) from the null-check requirement. Eliminates a large class of false positives without weakening real coverage.
32
+ - `no-unhandled-promise`: refined detection (see source diff).
33
+
34
+ ## 3.1.2 (2026-02-09)
35
+
36
+ This was a version bump only for eslint-plugin-reliability to align it with other projects, there were no code changes.
37
+
38
+ ## [3.1.0] - 2026-02-08
39
+
40
+ This was a version bump only for eslint-plugin-reliability to align it with other projects, there were no code changes.
41
+
42
+ ## [3.0.4] - 2026-02-08
43
+
44
+ ### Bug Fixes
45
+
46
+ - align codecov component IDs with full package names ([2831b968](https://github.com/ofri-peretz/eslint/commit/2831b968))
47
+
48
+ ### Documentation
49
+
50
+ - fix changelog header format across all packages ([c3a15082](https://github.com/ofri-peretz/eslint/commit/c3a15082))
51
+
52
+ ### ❤️ Thank You
53
+
54
+ - Ofri Peretz
55
+
56
+ ## [3.0.3] - 2026-02-06
57
+
58
+ ### Bug Fixes
59
+
60
+ - align codecov component names and update docs components ([0a59a86c](https://github.com/ofri-peretz/eslint/commit/0a59a86c))
61
+
62
+ ### ❤️ Thank You
63
+
64
+ - Ofri Peretz
65
+
66
+ ## [3.0.2] - 2026-02-02
67
+
68
+ This was a version bump only for eslint-plugin-reliability to align it with other projects, there were no code changes.
69
+
70
+ # Changelog
71
+
72
+ All notable changes to `eslint-plugin-reliability` will be documented in this file.
73
+
74
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
75
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
76
+
77
+ ### Documentation
78
+
79
+ - 📘 Launched new documentation site: [eslint.interlace.tools](https://eslint.interlace.tools/)
80
+ - 📝 Achieved 100% documentation parity (both .md and .mdx files)
81
+
82
+ ## [3.0.1] - 2026-02-02
83
+
84
+ This was a version bump only for eslint-plugin-reliability to align it with other projects, there were no code changes.
85
+
86
+ ## [3.0.0] - 2026-02-02
87
+
88
+ This was a version bump only for eslint-plugin-reliability to align it with other projects, there were no code changes.
89
+
90
+ ## [1.0.0] - 2026-01-26
91
+
92
+ ### Added
93
+
94
+ - Initial stable release with 8 reliability rules
95
+ - LLM-optimized error messages for AI-assisted development
96
+ - 100% test coverage across all rules
97
+ - ESLint 9 flat config support
98
+ - TypeScript type definitions for all rule options
99
+
100
+ ### Rules
101
+
102
+ #### Error Handling (4 rules)
103
+
104
+ | Rule | Description | CWE |
105
+ | :------------------------- | :-------------------------------------------- | :------ |
106
+ | `no-unhandled-promise` | Detect unhandled promise rejections | CWE-392 |
107
+ | `no-silent-errors` | Detect empty catch blocks that swallow errors | CWE-390 |
108
+ | `no-missing-error-context` | Require error context when re-throwing | CWE-209 |
109
+ | `error-message` | Require meaningful error messages | CWE-209 |
110
+
111
+ #### Runtime Safety (4 rules)
112
+
113
+ | Rule | Description | CWE |
114
+ | :------------------------- | :-------------------------------------------- | :---------- |
115
+ | `no-missing-null-checks` | Detect potential null/undefined dereferences | CWE-476 |
116
+ | `no-unsafe-type-narrowing` | Detect unsafe type narrowing patterns | CWE-704 |
117
+ | `require-network-timeout` | Require timeouts on network requests | CWE-400 |
118
+ | `no-await-in-loop` | Detect sequential await in loops (N+1 issues) | Performance |
119
+
120
+ ### Presets
121
+
122
+ - `recommended` - Balanced reliability checks (4 rules as warnings)
123
+
124
+ ### Known Limitations
125
+
126
+ Documented in `docs/KNOWN-LIMITATIONS.md`:
127
+
128
+ - `no-unhandled-promises`: Promises in callbacks or async iterators may be missed
129
+ - `no-silent-errors`: Intentional suppression vs. actual swallowing is hard to distinguish
130
+ - `no-missing-null-checks`: Business-logic guarantees not in types can trigger false positives
package/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Ofri Peretz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+
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-reliability" 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
+ Reliability rules for defensive programming, error handling, and async correctness.
7
7
  </p>
8
8
 
9
9
  <p align="center">
10
10
  <a href="https://www.npmjs.com/package/eslint-plugin-reliability" target="_blank"><img src="https://img.shields.io/npm/v/eslint-plugin-reliability.svg" alt="NPM Version" /></a>
11
11
  <a href="https://www.npmjs.com/package/eslint-plugin-reliability" target="_blank"><img src="https://img.shields.io/npm/dm/eslint-plugin-reliability.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=eslint-plugin-reliability" target="_blank"><img src="https://codecov.io/gh/ofri-peretz/eslint/graph/badge.svg?component=eslint-plugin-reliability" alt="Codecov" /></a>
13
+ <a href="https://app.codecov.io/gh/ofri-peretz/eslint/components?components%5B0%5D=reliability" target="_blank"><img src="https://codecov.io/gh/ofri-peretz/eslint/graph/badge.svg?component=reliability" 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 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 Reliability rules for defensive programming, error handling, and async correctness.
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-reliability), visit [eslint.interlace.tools](https://eslint.interlace.tools). 📚
29
- - 要查看中文 [指南](https://eslint.interlace.tools/docs/quality/plugin-reliability), 请访问 [eslint.interlace.tools](https://eslint.interlace.tools). 📚
30
- - [가이드](https://eslint.interlace.tools/docs/quality/plugin-reliability) 문서는 [eslint.interlace.tools](https://eslint.interlace.tools)에서 확인하실 수 있습니다. 📚
31
- - [ガイド](https://eslint.interlace.tools/docs/quality/plugin-reliability)は [eslint.interlace.tools](https://eslint.interlace.tools)でご確認ください。 📚
32
- - Para ver la [guía](https://eslint.interlace.tools/docs/quality/plugin-reliability), visita [eslint.interlace.tools](https://eslint.interlace.tools). 📚
33
- - للاطلاع على [الدليل](https://eslint.interlace.tools/docs/quality/plugin-reliability)، قم بزيارة [eslint.interlace.tools](https://eslint.interlace.tools). 📚
29
+ - To check out the [guide](https://eslint.interlace.tools/docs/quality/plugin-reliability?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-reliability), visit [eslint.interlace.tools](https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-reliability). 📚
30
+ - 要查看中文 [指南](https://eslint.interlace.tools/docs/quality/plugin-reliability?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-reliability), 请访问 [eslint.interlace.tools](https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-reliability). 📚
31
+ - [가이드](https://eslint.interlace.tools/docs/quality/plugin-reliability?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-reliability) 문서는 [eslint.interlace.tools](https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-reliability)에서 확인하실 수 있습니다. 📚
32
+ - [ガイド](https://eslint.interlace.tools/docs/quality/plugin-reliability?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-reliability)は [eslint.interlace.tools](https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-reliability)でご確認ください。 📚
33
+ - Para ver la [guía](https://eslint.interlace.tools/docs/quality/plugin-reliability?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-reliability), visita [eslint.interlace.tools](https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-reliability). 📚
34
+ - للاطلاع على [الدليل](https://eslint.interlace.tools/docs/quality/plugin-reliability?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-reliability)، قم بزيارة [eslint.interlace.tools](https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-reliability). 📚
34
35
 
35
36
  ```bash
36
37
  npm install eslint-plugin-reliability --save-dev
@@ -126,6 +127,14 @@ const results = await Promise.all(userIds.map((id) => fetchUser(id)));
126
127
 
127
128
  ---
128
129
 
130
+ ## 📦 Compatibility
131
+ | Package | Version |
132
+ | :--- | :--- |
133
+ | ESLint | `^8.0.0 \|\| ^9.0.0 \|\| ^10.0.0` |
134
+ | Node.js | `>=18.0.0` |
135
+
136
+ 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.
137
+
129
138
  ## Rules
130
139
 
131
140
  **Legend**
@@ -133,16 +142,27 @@ const results = await Promise.all(userIds.map((id) => fetchUser(id)));
133
142
  | Icon | Description |
134
143
  | :---: | :--- |
135
144
  | 💼 | **Recommended**: Included in the recommended preset. |
136
- | ⚠️ | **Warns**: Set towarn in recommended preset. |
145
+ | ⚠️ | **Warns**: Set to warn in recommended preset. |
137
146
  | 🔧 | **Auto-fixable**: Automatically fixable by the `--fix` CLI option. |
138
147
  | 💡 | **Suggestions**: Providing code suggestions in IDE. |
139
148
  | 🚫 | **Deprecated**: This rule is deprecated. |
140
-
141
- | Rule | CWE | OWASP | CVSS | Description | 💼 | ⚠️ | 🔧 | 💡 | 🚫 |
142
- | :--- | :---: | :---: | :---: | :--- | :---: | :---: | :---: | :---: | :---: |
143
- | [error-handling](https://eslint.interlace.tools/docs/quality/plugin-reliability/rules/error-handling) | | | | Enforce error handling | | | | | |
144
- | [reliability](https://eslint.interlace.tools/docs/quality/plugin-reliability/rules/reliability) | | | | Enforce reliability | | | | | |
145
-
149
+ | 🟢 | **Type-unaware**: AST-only, runs in oxlint JS-plugin tier. |
150
+ | 🟡 | **Type-aware (refining)**: pure-AST primary path; types refine precision. |
151
+ | 🟠 | **Type-aware (graceful)**: requires TS program; silent without it. |
152
+
153
+ <!-- AUTO-GENERATED:RULES_TABLE:START - Do not edit manually -->
154
+ | Rule | CWE | OWASP | CVSS | Description | 🧠 | 💼 | ⚠️ | 🔧 | 💡 | 🚫 |
155
+ | :--- | :---: | :---: | :---: | :--- | :---: | :---: | :---: | :---: | :---: | :---: |
156
+ | [error-message](https://eslint.interlace.tools/docs/quality/plugin-reliability/rules/error-message?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-reliability) | | | | Enforce providing a message when creating built-in Error objects for better debugging. This rule is part of… | 🟢 | | | | 💡 | |
157
+ | [no-await-in-loop](https://eslint.interlace.tools/docs/quality/plugin-reliability/rules/no-await-in-loop?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-reliability) | | | | Disallow await inside loops without considering concurrency implications | 🟢 | | | | 💡 | |
158
+ | [no-jsdoc-terminator-in-example](https://eslint.interlace.tools/docs/quality/plugin-reliability/rules/no-jsdoc-terminator-in-example?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-reliability) | | | | Detect `*/` sequences inside JSDoc `@example` blocks that prematurely close the JSDoc comment. | 🟢 | | | | 💡 | |
159
+ | [no-missing-error-context](https://eslint.interlace.tools/docs/quality/plugin-reliability/rules/no-missing-error-context?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-reliability) | | | | ESLint Rule: no-missing-error-context with LLM-optimized suggestions and auto-fix capabilities. | 🟢 | | | | 💡 | |
160
+ | [no-missing-null-checks](https://eslint.interlace.tools/docs/quality/plugin-reliability/rules/no-missing-null-checks?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-reliability) | CWE-476 | | | ESLint Rule: no-missing-null-checks with LLM-optimized suggestions and auto-fix capabilities. | 🟢 | | ⚠️ | | 💡 | |
161
+ | [no-silent-errors](https://eslint.interlace.tools/docs/quality/plugin-reliability/rules/no-silent-errors?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-reliability) | | | | ESLint Rule: no-silent-errors with LLM-optimized suggestions and auto-fix capabilities. | 🟢 | | ⚠️ | | 💡 | |
162
+ | [no-unhandled-promise](https://eslint.interlace.tools/docs/quality/plugin-reliability/rules/no-unhandled-promise?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-reliability) | CWE-1024 | | | Disallow unhandled Promise rejections with LLM-optimized suggestions for proper async error handling. This… | 🟢 | | | | 💡 | |
163
+ | [no-unsafe-type-narrowing](https://eslint.interlace.tools/docs/quality/plugin-reliability/rules/no-unsafe-type-narrowing?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-reliability) | | | | ESLint Rule: no-unsafe-type-narrowing with LLM-optimized suggestions and auto-fix capabilities. | 🟢 | | | | 💡 | |
164
+ | [require-network-timeout](https://eslint.interlace.tools/docs/quality/plugin-reliability/rules/require-network-timeout?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-reliability) | | | | Require timeout configuration for network requests. This rule is part of eslint-plugin-reliability and prov… | 🟢 | 💼 | | | 💡 | |
165
+ <!-- AUTO-GENERATED:RULES_TABLE:END -->
146
166
  ## 🔗 Related ESLint Plugins
147
167
 
148
168
  Part of the **Interlace ESLint Ecosystem** — AI-native security plugins with LLM-optimized error messages:
@@ -151,7 +171,7 @@ Part of the **Interlace ESLint Ecosystem** — AI-native security plugins with L
151
171
  | :--- | :---: | :--- |
152
172
  | [`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. |
153
173
  | [`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. |
154
- | [`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. |
174
+ | [`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). |
155
175
  | [`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. |
156
176
  | [`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. |
157
177
  | [`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. |
@@ -161,10 +181,19 @@ Part of the **Interlace ESLint Ecosystem** — AI-native security plugins with L
161
181
  | [`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. |
162
182
  | [`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. |
163
183
 
184
+ <!-- INTERLACE:STAR_CTA:START -->
185
+ ## ⭐ Support & follow
186
+
187
+ 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.
188
+
189
+ [![GitHub stars](https://img.shields.io/github/stars/ofri-peretz/eslint?style=social)](https://github.com/ofri-peretz/eslint)
190
+
191
+ <!-- INTERLACE:STAR_CTA:END -->
192
+
164
193
  ## 📄 License
165
194
 
166
195
  MIT © [Ofri Peretz](https://github.com/ofri-peretz)
167
196
 
168
197
  <p align="center">
169
- <a href="https://eslint.interlace.tools/docs/quality/plugin-reliability"><img src="https://eslint.interlace.tools/images/og-reliability.png" alt="ESLint Interlace Plugin" width="100%" /></a>
198
+ <a href="https://eslint.interlace.tools/docs/quality/plugin-reliability?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-reliability"><img src="https://eslint.interlace.tools/images/og-reliability.png" alt="ESLint Interlace Plugin" width="100%" /></a>
170
199
  </p>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-reliability",
3
- "version": "3.1.2",
3
+ "version": "3.1.4",
4
4
  "description": "ESLint rules for runtime stability, fault tolerance, and type safety.",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -9,6 +9,10 @@
9
9
  ".": {
10
10
  "types": "./src/index.d.ts",
11
11
  "default": "./src/index.js"
12
+ },
13
+ "./oxlint": {
14
+ "types": "./src/oxlint.d.ts",
15
+ "default": "./src/oxlint.js"
12
16
  }
13
17
  },
14
18
  "author": "Ofri Peretz <ofriperetzdev@gmail.com>",
@@ -48,8 +52,21 @@
48
52
  "url": "https://github.com/ofri-peretz/eslint",
49
53
  "directory": "packages/eslint-plugin-reliability"
50
54
  },
51
- "homepage": "https://github.com/ofri-peretz/eslint/tree/main/packages/eslint-plugin-reliability#readme",
55
+ "homepage": "https://eslint.interlace.tools/docs/quality/plugin-reliability?utm_source=npm&utm_medium=referral&utm_campaign=eslint-plugin-reliability",
52
56
  "bugs": {
53
57
  "url": "https://github.com/ofri-peretz/eslint/issues"
58
+ },
59
+ "scripts": {
60
+ "build": "tsx ../../scripts/build-package.ts",
61
+ "test": "vitest run",
62
+ "test:coverage": "vitest run --coverage",
63
+ "typecheck": "tsc -p tsconfig.lib.json --noEmit"
64
+ },
65
+ "peerDependencies": {
66
+ "eslint": "^8.0.0 || ^9.0.0 || ^10.0.0"
67
+ },
68
+ "funding": {
69
+ "type": "github",
70
+ "url": "https://github.com/ofri-peretz/eslint"
54
71
  }
55
- }
72
+ }
package/src/index.d.ts CHANGED
@@ -189,7 +189,7 @@ export declare const configs: {
189
189
  };
190
190
  };
191
191
  rules: {
192
- 'reliability/no-silent-errors': "warn";
192
+ 'reliability/no-silent-errors': "error";
193
193
  'reliability/no-missing-null-checks': "warn";
194
194
  'reliability/require-network-timeout': "error";
195
195
  };
package/src/index.js CHANGED
@@ -41,7 +41,7 @@ exports.rules = {
41
41
  exports.plugin = {
42
42
  meta: {
43
43
  name: 'eslint-plugin-reliability',
44
- version: '1.0.0',
44
+ version: '3.1.4',
45
45
  },
46
46
  rules: exports.rules,
47
47
  };
@@ -51,7 +51,7 @@ exports.configs = {
51
51
  reliability: exports.plugin,
52
52
  },
53
53
  rules: {
54
- 'reliability/no-silent-errors': 'warn',
54
+ 'reliability/no-silent-errors': 'error',
55
55
  'reliability/no-missing-null-checks': 'warn',
56
56
  'reliability/require-network-timeout': 'error',
57
57
  },
@@ -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-reliability/oxlint"] }
11
+ *
12
+ * oxlint's JS plugin loader does `require('eslint-plugin-reliability/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-reliability/oxlint"] }
12
+ *
13
+ * oxlint's JS plugin loader does `require('eslint-plugin-reliability/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-reliability/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) {
@@ -15,12 +15,25 @@ function hasErrorMessage(node) {
15
15
  if (!node.argument) {
16
16
  return false;
17
17
  }
18
+ // `throw error;` — re-throwing a caught/named identifier. The original
19
+ // error already carries its own message + stack; demanding context on the
20
+ // re-throw is an FP. Same for `throw err`, `throw cause`, etc. Excludes
21
+ // the literal-like `undefined` / `NaN` / `Infinity` identifiers, which
22
+ // are typed as global constants but carry no diagnostic value.
23
+ if (node.argument.type === 'Identifier' &&
24
+ node.argument.name !== 'undefined' &&
25
+ node.argument.name !== 'NaN' &&
26
+ node.argument.name !== 'Infinity') {
27
+ return true;
28
+ }
18
29
  // Check if it's a new Error() with message (includes TypeError, ReferenceError, etc.)
19
30
  if (node.argument.type === 'NewExpression' &&
20
31
  node.argument.callee.type === 'Identifier' &&
21
32
  (node.argument.callee.name === 'Error' ||
22
33
  node.argument.callee.name.endsWith('Error'))) {
23
- // Check if first argument is a string (message)
34
+ // Check if first argument is a string (message) OR ANY expression — a
35
+ // custom error class like `new UserNotFoundError(userId)` builds its
36
+ // own message internally; the constructor argument IS the context.
24
37
  if (node.argument.arguments.length > 0) {
25
38
  const firstArg = node.argument.arguments[0];
26
39
  if (firstArg.type === 'Literal' && typeof firstArg.value === 'string') {
@@ -29,6 +42,13 @@ function hasErrorMessage(node) {
29
42
  if (firstArg.type === 'TemplateLiteral') {
30
43
  return true;
31
44
  }
45
+ // Non-string argument to a custom *Error class — accept as context.
46
+ // The rule's purpose is "throws should carry information"; passing an
47
+ // identifier/object to the error constructor carries information.
48
+ if (node.argument.callee.name !== 'Error' &&
49
+ node.argument.callee.name.endsWith('Error')) {
50
+ return true;
51
+ }
32
52
  }
33
53
  }
34
54
  // Check if it's a string literal
@@ -49,6 +69,15 @@ function hasErrorStack(node) {
49
69
  if (!node.argument) {
50
70
  return false;
51
71
  }
72
+ // `throw error;` — caught/named identifier already has a stack from the
73
+ // place where it was first thrown. Re-throws preserve the stack. Excludes
74
+ // `undefined` / `NaN` / `Infinity` global-constant identifiers.
75
+ if (node.argument.type === 'Identifier' &&
76
+ node.argument.name !== 'undefined' &&
77
+ node.argument.name !== 'NaN' &&
78
+ node.argument.name !== 'Infinity') {
79
+ return true;
80
+ }
52
81
  // Check if it's a new Error() instance
53
82
  if (node.argument.type === 'NewExpression' &&
54
83
  node.argument.callee.type === 'Identifier') {
@@ -65,6 +94,7 @@ exports.noMissingErrorContext = (0, eslint_devkit_2.createRule)({
65
94
  meta: {
66
95
  type: 'suggestion',
67
96
  docs: {
97
+ url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-reliability/docs/rules/no-missing-error-context.md',
68
98
  description: 'Detects thrown errors without context',
69
99
  },
70
100
  hasSuggestions: true,
@@ -135,7 +165,7 @@ exports.noMissingErrorContext = (0, eslint_devkit_2.createRule)({
135
165
  ],
136
166
  create(context, [options = {}]) {
137
167
  const { requireMessage = true, requireStackTrace = false, ignoreInTests = true, } = options || {};
138
- const filename = context.getFilename();
168
+ const filename = context.filename;
139
169
  const isTestFile = ignoreInTests && /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);
140
170
  if (isTestFile) {
141
171
  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-reliability/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
  */
@@ -9,20 +9,113 @@ exports.noUnhandledPromise = void 0;
9
9
  const eslint_devkit_1 = require("@interlace/eslint-devkit");
10
10
  const eslint_devkit_2 = require("@interlace/eslint-devkit");
11
11
  /**
12
- * Check if a node is a Promise-like expression
13
- * For now, we check all CallExpressions since we can't statically determine
14
- * which functions return promises. The isPromiseHandled function will filter out
15
- * non-promise calls that are inside handled promise chains.
12
+ * Built-in / library calls that are KNOWN to NOT return a promise. Firing
13
+ * on these produces FPs (e.g., `setTimeout(...)`, `console.log(...)`,
14
+ * `Math.floor(...)` are not unhandled promises).
16
15
  */
17
- function isPromiseExpression(node) {
18
- // Function calls that might return promises
19
- if (node.type === 'CallExpression') {
16
+ const NEVER_RETURNS_PROMISE_FUNCTIONS = new Set([
17
+ 'setTimeout', 'clearTimeout', 'setInterval', 'clearInterval',
18
+ 'setImmediate', 'clearImmediate',
19
+ 'requestAnimationFrame', 'cancelAnimationFrame',
20
+ 'queueMicrotask',
21
+ 'String', 'Number', 'Boolean', 'Symbol', 'BigInt',
22
+ 'parseInt', 'parseFloat', 'isNaN', 'isFinite',
23
+ 'Array', 'Object',
24
+ ]);
25
+ const NEVER_RETURNS_PROMISE_METHODS = new Set([
26
+ // console / logger
27
+ 'log', 'error', 'warn', 'info', 'debug', 'trace', 'group', 'groupEnd',
28
+ 'time', 'timeEnd', 'assert',
29
+ // Math
30
+ 'floor', 'ceil', 'round', 'abs', 'min', 'max', 'pow', 'sqrt', 'random',
31
+ 'sin', 'cos', 'tan', 'log2', 'log10',
32
+ // String / Array helpers
33
+ 'slice', 'split', 'join', 'concat', 'includes', 'indexOf', 'lastIndexOf',
34
+ 'startsWith', 'endsWith', 'replace', 'replaceAll', 'trim', 'toLowerCase', 'toUpperCase',
35
+ 'repeat', 'padStart', 'padEnd', 'charAt', 'charCodeAt', 'codePointAt',
36
+ 'push', 'pop', 'shift', 'unshift', 'splice', 'reverse', 'sort',
37
+ 'map', 'filter', 'reduce', 'reduceRight', 'forEach', 'every', 'some', 'find', 'findIndex',
38
+ 'flat', 'flatMap', 'fill', 'copyWithin', 'entries', 'keys', 'values',
39
+ // JSON
40
+ 'parse', 'stringify',
41
+ // AbortController/AbortSignal
42
+ 'abort', 'addEventListener', 'removeEventListener', 'dispatchEvent',
43
+ // Date / Buffer / Number / Array static helpers (sync)
44
+ 'now', 'parse', 'UTC', 'from', 'of', 'isArray', 'isBuffer',
45
+ 'isInteger', 'isFinite', 'isNaN', 'isSafeInteger',
46
+ 'fromCharCode', 'fromCodePoint', 'raw',
47
+ // Object helpers (sync)
48
+ 'assign', 'freeze', 'isFrozen', 'create', 'defineProperty', 'defineProperties',
49
+ 'getOwnPropertyDescriptor', 'getOwnPropertyNames', 'getPrototypeOf', 'setPrototypeOf',
50
+ 'preventExtensions', 'isExtensible', 'seal', 'isSealed', 'fromEntries',
51
+ // Promise constructors that are themselves a promise but the callee is OK
52
+ ]);
53
+ /**
54
+ * Globals whose methods are conventionally synchronous (no method on these
55
+ * namespaces returns a Promise in the standard library). Used in addition
56
+ * to `NEVER_RETURNS_PROMISE_METHODS` because matching by method name alone
57
+ * is too coarse: `from` is sync on `Array`/`Buffer`/`Date` but could be
58
+ * async on a user-defined object.
59
+ */
60
+ const SYNC_NAMESPACE_OBJECTS = new Set([
61
+ 'Math', 'JSON', 'Date', 'Buffer', 'Array', 'Object', 'Number', 'String',
62
+ 'Boolean', 'Symbol', 'BigInt', 'Reflect', 'console', 'process',
63
+ ]);
64
+ /**
65
+ * Returns true if the call MIGHT return a Promise (default — we want to
66
+ * preserve detection for unknown calls). Returns false only when the
67
+ * callee is a known synchronous built-in (`setTimeout`, `console.log`,
68
+ * `Math.floor`, etc.) — those structurally never return promises and
69
+ * firing on them produces FPs. Keeping the default as "could be a
70
+ * promise" preserves recall on user-defined async functions.
71
+ */
72
+ function isLikelyPromiseExpression(node) {
73
+ if (node.type !== 'CallExpression')
74
+ return false;
75
+ const callee = node.callee;
76
+ // Direct calls — skip known synchronous built-ins
77
+ if (callee.type === 'Identifier') {
78
+ const name = callee.name;
79
+ if (NEVER_RETURNS_PROMISE_FUNCTIONS.has(name))
80
+ return false;
20
81
  return true;
21
82
  }
22
- // Await expressions (already handled)
23
- if (node.type === 'AwaitExpression') {
24
- return false; // Already handled
83
+ // Method calls — skip known synchronous methods (Math.*, Array.*,
84
+ // String.*, console.*, JSON.*)
85
+ if (callee.type === 'MemberExpression') {
86
+ const prop = callee.property;
87
+ if (prop.type === 'Identifier') {
88
+ if (NEVER_RETURNS_PROMISE_METHODS.has(prop.name))
89
+ return false;
90
+ }
91
+ // Static helpers on known sync namespaces — `Buffer.from`, `Date.now`,
92
+ // `Array.isArray`, `Object.keys`, etc. The standard library never
93
+ // returns a Promise from any method on these globals.
94
+ const obj = callee.object;
95
+ if (obj.type === 'Identifier' && SYNC_NAMESPACE_OBJECTS.has(obj.name)) {
96
+ return false;
97
+ }
98
+ return true;
25
99
  }
100
+ return true;
101
+ }
102
+ /**
103
+ * Returns true when the call's parent indicates the promise is delegated
104
+ * to a caller and therefore not "unhandled" at this site. This covers:
105
+ * - `return fn()` — the enclosing function returns the promise; its
106
+ * caller takes responsibility.
107
+ * - `() => fn()` — concise-body arrow returns the promise.
108
+ * - `.then(() => fn())` — already inside a promise chain (handled by
109
+ * `isInsidePromiseCallback`, but the arrow-body case is the same).
110
+ */
111
+ function isPromiseDelegatedToCaller(node) {
112
+ const parent = node.parent;
113
+ if (!parent)
114
+ return false;
115
+ if (parent.type === 'ReturnStatement')
116
+ return true;
117
+ if (parent.type === 'ArrowFunctionExpression' && parent.body === node)
118
+ return true;
26
119
  return false;
27
120
  }
28
121
  /**
@@ -136,7 +229,10 @@ exports.noUnhandledPromise = (0, eslint_devkit_2.createRule)({
136
229
  meta: {
137
230
  type: 'problem',
138
231
  docs: {
232
+ url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-reliability/docs/rules/no-unhandled-promise.md',
139
233
  description: 'Detects unhandled Promise rejections',
234
+ cwe: 'CWE-1024',
235
+ cvss: 7.5,
140
236
  },
141
237
  hasSuggestions: true,
142
238
  messages: {
@@ -201,12 +297,12 @@ exports.noUnhandledPromise = (0, eslint_devkit_2.createRule)({
201
297
  ],
202
298
  create(context, [options = {}]) {
203
299
  const { ignoreInTests = true, ignoreVoidExpressions = false } = options || {};
204
- const filename = context.getFilename();
300
+ const filename = context.filename;
205
301
  const isTestFile = ignoreInTests && /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);
206
302
  if (isTestFile) {
207
303
  return {};
208
304
  }
209
- // const sourceCode = context.sourceCode || context.sourceCode; // Not used
305
+ // const sourceCode = context.sourceCode; // Not used
210
306
  /**
211
307
  * Check call expressions for unhandled promises
212
308
  */
@@ -215,6 +311,12 @@ exports.noUnhandledPromise = (0, eslint_devkit_2.createRule)({
215
311
  if (isInsidePromiseCallback(node)) {
216
312
  return;
217
313
  }
314
+ // `return fn()` / `() => fn()` — the promise is delegated to the
315
+ // caller. Flagging here would force `await` everywhere a promise
316
+ // is forwarded, which is wrong: forwarding IS handling.
317
+ if (isPromiseDelegatedToCaller(node)) {
318
+ return;
319
+ }
218
320
  // Skip calls to promise methods (.then, .catch, .finally) as they are handled by definition
219
321
  // But only if they have meaningful callbacks
220
322
  if (node.callee.type === 'MemberExpression' &&
@@ -241,7 +343,7 @@ exports.noUnhandledPromise = (0, eslint_devkit_2.createRule)({
241
343
  }
242
344
  }
243
345
  // Check if it's a promise-returning function
244
- if (!isPromiseExpression(node)) {
346
+ if (!isLikelyPromiseExpression(node)) {
245
347
  return;
246
348
  }
247
349
  // Check if it's already handled
@@ -307,7 +409,7 @@ exports.noUnhandledPromise = (0, eslint_devkit_2.createRule)({
307
409
  }
308
410
 
309
411
  // Check if it's a promise-like identifier
310
- if (!isPromiseExpression(node)) {
412
+ if (!isLikelyPromiseExpression(node)) {
311
413
  return;
312
414
  }
313
415
 
@@ -13,6 +13,7 @@ exports.noAwaitInLoop = (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-reliability/docs/rules/no-await-in-loop.md',
16
17
  description: 'Disallow await inside loops and suggest appropriate concurrency patterns',
17
18
  },
18
19
  hasSuggestions: false,
@@ -137,7 +138,7 @@ exports.noAwaitInLoop = (0, eslint_devkit_1.createRule)({
137
138
  }
138
139
  }
139
140
  function analyzeLoopContext(node, loopType) {
140
- const context = {
141
+ const loopCtx = {
141
142
  loopType,
142
143
  operationCount: 1,
143
144
  hasDependencies: false,
@@ -153,28 +154,28 @@ exports.noAwaitInLoop = (0, eslint_devkit_1.createRule)({
153
154
  function analyzeOperations(currentNode) {
154
155
  if (currentNode.type === 'CallExpression') {
155
156
  if (currentNode.callee.type === 'Identifier') {
156
- context.operations.push(currentNode.callee.name);
157
+ loopCtx.operations.push(currentNode.callee.name);
157
158
  }
158
159
  else if (currentNode.callee.type === 'MemberExpression' &&
159
160
  currentNode.callee.property.type === 'Identifier') {
160
- context.operations.push(currentNode.callee.property.name);
161
+ loopCtx.operations.push(currentNode.callee.property.name);
161
162
  }
162
163
  }
163
164
  // Check for dependencies between iterations
164
165
  if (currentNode.type === 'AssignmentExpression' ||
165
166
  currentNode.type === 'UpdateExpression') {
166
- context.hasDependencies = true;
167
+ loopCtx.hasDependencies = true;
167
168
  }
168
169
  // Check for side effects that might require sequential execution
169
170
  if (currentNode.type === 'CallExpression' &&
170
- (context.operations.includes('push') ||
171
- context.operations.includes('splice') ||
172
- context.operations.includes('delete'))) {
173
- context.hasSideEffects = true;
171
+ (loopCtx.operations.includes('push') ||
172
+ loopCtx.operations.includes('splice') ||
173
+ loopCtx.operations.includes('delete'))) {
174
+ loopCtx.hasSideEffects = true;
174
175
  }
175
176
  // Count operations
176
177
  if (currentNode.type === 'AwaitExpression') {
177
- context.operationCount++;
178
+ loopCtx.operationCount++;
178
179
  }
179
180
  // Recursively analyze
180
181
  for (const key in currentNode) {
@@ -195,18 +196,19 @@ exports.noAwaitInLoop = (0, eslint_devkit_1.createRule)({
195
196
  }
196
197
  analyzeOperations(node);
197
198
  // Determine if operations can be concurrent
198
- if (context.hasDependencies || context.hasSideEffects) {
199
- context.isSequential = true;
200
- context.estimatedPerformance = `sequential (${context.operationCount} operations)`;
199
+ if (loopCtx.hasDependencies || loopCtx.hasSideEffects) {
200
+ loopCtx.isSequential = true;
201
+ loopCtx.estimatedPerformance = `sequential (${loopCtx.operationCount} operations)`;
201
202
  }
202
203
  else {
203
- context.isSequential = false;
204
- context.estimatedPerformance = `potential ${context.operationCount}x speedup with concurrency`;
204
+ loopCtx.isSequential = false;
205
+ loopCtx.estimatedPerformance = `potential ${loopCtx.operationCount}x speedup with concurrency`;
205
206
  }
206
- return context;
207
+ return loopCtx;
207
208
  }
208
- function getSuggestion(context) {
209
- if (context.isSequential) {
209
+ // oxlint-disable-next-line consistent-function-scoping
210
+ function getSuggestion(loopCtx) {
211
+ if (loopCtx.isSequential) {
210
212
  return 'operations may need to be sequential - consider if concurrency is safe';
211
213
  }
212
214
  else {
@@ -60,9 +60,9 @@ exports.noJsdocTerminatorInExample = (0, eslint_devkit_1.createRule)({
60
60
  meta: {
61
61
  type: 'problem',
62
62
  docs: {
63
+ url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-reliability/docs/rules/no-jsdoc-terminator-in-example.md',
63
64
  description: 'Detects `*/` sequences inside JSDoc @example blocks that prematurely close the comment',
64
65
  },
65
- fixable: 'code',
66
66
  hasSuggestions: true,
67
67
  messages: {
68
68
  jsdocTerminatorInExample: (0, eslint_devkit_1.formatLLMMessage)({
@@ -86,7 +86,7 @@ exports.noJsdocTerminatorInExample = (0, eslint_devkit_1.createRule)({
86
86
  },
87
87
  defaultOptions: [],
88
88
  create(context) {
89
- const sourceCode = context.sourceCode || context.sourceCode;
89
+ const sourceCode = context.sourceCode;
90
90
  return {
91
91
  Program() {
92
92
  const comments = sourceCode.getAllComments();
@@ -8,6 +8,124 @@ Object.defineProperty(exports, "__esModule", { value: true });
8
8
  exports.noMissingNullChecks = void 0;
9
9
  const eslint_devkit_1 = require("@interlace/eslint-devkit");
10
10
  const eslint_devkit_2 = require("@interlace/eslint-devkit");
11
+ /**
12
+ * Globals and built-ins that are never null/undefined. Property access on
13
+ * these doesn't need a null check.
14
+ */
15
+ // Built-in namespaces that are never null AND aren't typically mutated. We
16
+ // deliberately exclude `globalThis`, `window`, `self`, `top`, `parent`, and
17
+ // `document` — those are mutable and `globalThis.appState = ...` is a real
18
+ // "global state mutation" antipattern that other rules want to catch.
19
+ // Read-only namespaces (Math, JSON, …) and library singletons are safe to
20
+ // exempt because their property access is idempotent.
21
+ const NEVER_NULL_GLOBALS = new Set([
22
+ // Read-only Node/V8 magics
23
+ 'console', 'process', 'Buffer', '__dirname', '__filename', 'module', 'exports', 'require',
24
+ 'navigator', 'location', 'history',
25
+ // Built-in objects (read-only namespaces)
26
+ 'Math', 'JSON', 'Object', 'Array', 'Number', 'String', 'Boolean', 'Date',
27
+ 'RegExp', 'Promise', 'Symbol', 'Map', 'Set', 'WeakMap', 'WeakSet',
28
+ 'Proxy', 'Reflect', 'Intl', 'BigInt', 'WebAssembly', 'Atomics',
29
+ 'URL', 'URLSearchParams', 'TextEncoder', 'TextDecoder',
30
+ 'AbortController', 'AbortSignal', 'EventTarget', 'Event', 'CustomEvent',
31
+ 'FormData', 'Blob', 'File', 'FileReader', 'Headers', 'Request', 'Response',
32
+ // Error classes
33
+ 'Error', 'TypeError', 'RangeError', 'SyntaxError', 'URIError',
34
+ 'EvalError', 'ReferenceError', 'AggregateError',
35
+ // Common library / framework names
36
+ 'fetch', 'crypto', 'performance', 'queueMicrotask',
37
+ 'setTimeout', 'clearTimeout', 'setInterval', 'clearInterval',
38
+ 'setImmediate', 'clearImmediate', 'requestAnimationFrame', 'cancelAnimationFrame',
39
+ // Common loggers (used as singletons)
40
+ 'logger', 'log', 'winston', 'pino', 'bunyan',
41
+ ]);
42
+ /**
43
+ * Returns true if the identifier resolves to:
44
+ * - A NEVER_NULL_GLOBALS entry (built-in / known singleton)
45
+ * - A catch-clause parameter (`catch (e) { e.message }` — never null)
46
+ * - A variable initialized by `new X(...)` (constructor result is never null)
47
+ * - A top-level import (`import x from 'y'` — never null)
48
+ *
49
+ * For these, the rule should not demand a null check.
50
+ */
51
+ function isProvablyNonNullableIdentifier(ident, scope) {
52
+ if (NEVER_NULL_GLOBALS.has(ident.name))
53
+ return true;
54
+ // Walk scope chain to find the resolved variable
55
+ let s = scope;
56
+ while (s) {
57
+ const variable = s.variables.find((v) => v.name === ident.name);
58
+ if (variable) {
59
+ for (const def of variable.defs) {
60
+ // catch (e) { ... }
61
+ if (def.type === 'CatchClause')
62
+ return true;
63
+ // import x from 'y' / import { x } from 'y'
64
+ if (def.type === 'ImportBinding')
65
+ return true;
66
+ // const x = new Foo(...) / array literal / object literal / primitive
67
+ // / class declaration / `await fetch(...)` / template literal — these
68
+ // initializers cannot produce null/undefined under normal control flow.
69
+ // Adding them closes the bulk of the ILB-Arena-Quality FPs without
70
+ // sacrificing real CWE-476 detection (genuine null-deref risks come
71
+ // from optional/maybe lookups, not from `const x = []`).
72
+ if (def.type === 'Variable' && def.node?.type === 'VariableDeclarator') {
73
+ const init = def.node.init;
74
+ if (!init)
75
+ continue;
76
+ if (init.type === 'NewExpression')
77
+ return true;
78
+ if (init.type === 'ArrayExpression')
79
+ return true;
80
+ if (init.type === 'ObjectExpression')
81
+ return true;
82
+ if (init.type === 'TemplateLiteral')
83
+ return true;
84
+ if (init.type === 'ClassExpression')
85
+ return true;
86
+ // Primitive literals (string / number / boolean / regex) — never null.
87
+ // Skip the `null` literal itself — that IS a null and the rule
88
+ // should let other rules complain about it.
89
+ if (init.type === 'Literal' && init.value !== null)
90
+ return true;
91
+ // `await fetch(url)` / `await fetch(url, opts)` — the WHATWG fetch
92
+ // contract guarantees a Response on resolution; null comes only
93
+ // through rejection, which throws past this assignment.
94
+ if (init.type === 'AwaitExpression' &&
95
+ init.argument.type === 'CallExpression' &&
96
+ init.argument.callee.type === 'Identifier' &&
97
+ init.argument.callee.name === 'fetch')
98
+ return true;
99
+ // `JSON.parse(...)` returns a value; typically non-null. Same for
100
+ // common Object/Array static methods.
101
+ if (init.type === 'CallExpression' &&
102
+ init.callee.type === 'MemberExpression' &&
103
+ init.callee.object.type === 'Identifier' &&
104
+ (init.callee.object.name === 'Object' || init.callee.object.name === 'Array' ||
105
+ init.callee.object.name === 'JSON'))
106
+ return true;
107
+ }
108
+ // function f(...) — function declaration name is never null
109
+ if (def.type === 'FunctionName')
110
+ return true;
111
+ // class C {} — class declaration name is never null
112
+ if (def.type === 'ClassName')
113
+ return true;
114
+ // Function/method parameters — without type information we cannot
115
+ // tell `(x: T)` from `(x: T | null)`, and firing on every param-deref
116
+ // is the dominant FP source on real codebases (53 → 44 → most-of-rest).
117
+ // The contract is the caller's responsibility; treat params as
118
+ // non-nullable by default. Type-aware analysis (TS) is the right
119
+ // tool for real param-nullability detection.
120
+ if (def.type === 'Parameter')
121
+ return true;
122
+ }
123
+ return false;
124
+ }
125
+ s = s.upper;
126
+ }
127
+ return false;
128
+ }
11
129
  /**
12
130
  * Check if property access has null/undefined check
13
131
  */
@@ -19,20 +137,49 @@ function hasNullCheck(node, sourceCode) {
19
137
  // Check if parent is optional chaining
20
138
  const parent = node.parent;
21
139
  if (parent && parent.type === 'ChainExpression') {
22
- return true; // Optional chaining handles null/undefined
140
+ return true;
23
141
  }
24
142
  // Check if used with nullish coalescing
25
143
  if (usesNullishCoalescing(node)) {
26
144
  return true;
27
145
  }
28
- // Check for basic explicit null checks in if statements
29
- // This is a simplified check that looks for patterns like:
30
- // if (obj !== null) { obj.property; }
146
+ const objectText = sourceCode.getText(node.object);
147
+ // Short-circuit AND: `obj && obj.prop` the right side of && runs only
148
+ // when the left side is truthy, so obj is guaranteed non-null here.
149
+ // Walk up one level (CallExpression wraps MemberExpression for `obj && obj.method()`)
150
+ const immediateParent = parent;
151
+ const nodeOrCall = immediateParent?.type === 'CallExpression' &&
152
+ immediateParent.callee === node
153
+ ? immediateParent
154
+ : node;
155
+ const andParent = nodeOrCall
156
+ .parent;
157
+ if (andParent?.type === 'LogicalExpression' &&
158
+ andParent.operator === '&&' &&
159
+ andParent.right === nodeOrCall) {
160
+ const leftText = sourceCode.getText(andParent.left);
161
+ if (leftText === objectText || leftText.endsWith(objectText))
162
+ return true;
163
+ }
164
+ // Ternary consequent: `obj ? obj.prop : fallback` — the test being truthy
165
+ // guarantees obj is non-null before the consequent evaluates.
166
+ let cur = node;
167
+ for (let depth = 0; depth < 8; depth++) {
168
+ const p = cur.parent;
169
+ if (!p)
170
+ break;
171
+ if (p.type === 'ConditionalExpression' &&
172
+ p.consequent === cur) {
173
+ if (sourceCode.getText(p.test) === objectText) {
174
+ return true;
175
+ }
176
+ }
177
+ cur = p;
178
+ }
179
+ // Explicit null/truthy check in enclosing if statement
31
180
  if (hasExplicitNullCheck(node, sourceCode)) {
32
181
  return true;
33
182
  }
34
- // For more sophisticated null checking, we'd need control flow analysis
35
- // This is a simplified implementation that checks for basic patterns
36
183
  return false;
37
184
  }
38
185
  /**
@@ -62,7 +209,17 @@ function hasExplicitNullCheck(node, sourceCode) {
62
209
  * Check if a test expression is a null check for a specific object
63
210
  */
64
211
  function isNullCheckForObject(test, object, sourceCode) {
65
- // Handle binary expressions like obj !== null
212
+ const objectText = sourceCode.getText(object);
213
+ // Truthy check: `if (obj)` or `if (obj.prop)` — direct truthy guard proves
214
+ // non-null. Also covers nested chains: `if (response) { response.data.items }`
215
+ // because checking the root (response) implicitly protects the full chain.
216
+ if (test.type === 'Identifier' || test.type === 'MemberExpression') {
217
+ const testText = sourceCode.getText(test);
218
+ if (testText === objectText || objectText.startsWith(testText + '.')) {
219
+ return true;
220
+ }
221
+ }
222
+ // Handle binary expressions like obj !== null, obj != undefined
66
223
  if (test.type === 'BinaryExpression') {
67
224
  const { left, right, operator } = test;
68
225
  if (operator === '!==' ||
@@ -71,8 +228,6 @@ function isNullCheckForObject(test, object, sourceCode) {
71
228
  operator === '==') {
72
229
  const leftText = sourceCode.getText(left);
73
230
  const rightText = sourceCode.getText(right);
74
- const objectText = sourceCode.getText(object);
75
- // Check if one side matches our object and the other is null/undefined
76
231
  if ((leftText === objectText &&
77
232
  (rightText === 'null' || rightText === 'undefined')) ||
78
233
  (rightText === objectText &&
@@ -86,6 +241,8 @@ function isNullCheckForObject(test, object, sourceCode) {
86
241
  return (isNullCheckForObject(test.left, object, sourceCode) ||
87
242
  isNullCheckForObject(test.right, object, sourceCode));
88
243
  }
244
+ // Unary negation: `if (!obj)` is a FALSY guard — only safe when paired
245
+ // with early return, which requires control-flow analysis. Skip for now.
89
246
  return false;
90
247
  }
91
248
  /**
@@ -113,7 +270,10 @@ exports.noMissingNullChecks = (0, eslint_devkit_2.createRule)({
113
270
  meta: {
114
271
  type: 'problem',
115
272
  docs: {
273
+ url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-reliability/docs/rules/no-missing-null-checks.md',
116
274
  description: 'Detects potential null pointer dereferences',
275
+ cwe: 'CWE-476',
276
+ cvss: 7.5,
117
277
  },
118
278
  hasSuggestions: true,
119
279
  messages: {
@@ -180,12 +340,12 @@ exports.noMissingNullChecks = (0, eslint_devkit_2.createRule)({
180
340
  const { ignoreInTests = true,
181
341
  // requireExplicitChecks = false, // Not used
182
342
  } = options || {};
183
- const filename = context.getFilename();
343
+ const filename = context.filename;
184
344
  const isTestFile = ignoreInTests && /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);
185
345
  if (isTestFile) {
186
346
  return {};
187
347
  }
188
- const sourceCode = context.sourceCode || context.sourceCode;
348
+ const sourceCode = context.sourceCode;
189
349
  // Track reported MemberExpression nodes to prevent duplicate reports
190
350
  // Key format: "start-end" from node.range
191
351
  const reportedMemberExpressions = new Set();
@@ -234,10 +394,30 @@ exports.noMissingNullChecks = (0, eslint_devkit_2.createRule)({
234
394
  const objectNode = node.object;
235
395
  let shouldCheck = false;
236
396
  if (objectNode.type === 'Identifier') {
397
+ // Skip identifiers that resolve to globals, catch params, imports,
398
+ // function declarations, or `new X()` results — these are never null.
399
+ if (isProvablyNonNullableIdentifier(objectNode, sourceCode.getScope(node))) {
400
+ return;
401
+ }
237
402
  shouldCheck = true;
238
403
  }
239
404
  else if (objectNode.type === 'MemberExpression') {
240
- // Nested member expressions like value.nested.deep
405
+ // Nested member expressions like value.nested.deep — only fire on
406
+ // the deepest, where the leaf identifier matters most. Skip if the
407
+ // base of the chain is a known-non-null global (`console.log`,
408
+ // `JSON.stringify`, etc.) or is a `this`/`this.#field` chain — `this`
409
+ // inside a method is the class instance (never null), and private
410
+ // fields are always defined when accessed from inside the class.
411
+ let base = objectNode;
412
+ while (base.type === 'MemberExpression') {
413
+ base = base.object;
414
+ }
415
+ if (base.type === 'ThisExpression')
416
+ return;
417
+ if (base.type === 'Identifier' &&
418
+ isProvablyNonNullableIdentifier(base, sourceCode.getScope(node))) {
419
+ return;
420
+ }
241
421
  shouldCheck = true;
242
422
  }
243
423
  if (shouldCheck && !hasNullCheck(node, sourceCode)) {
@@ -303,10 +483,22 @@ exports.noMissingNullChecks = (0, eslint_devkit_2.createRule)({
303
483
  const objectNode = memberExpr.object;
304
484
  let shouldCheck = false;
305
485
  if (objectNode.type === 'Identifier') {
486
+ if (isProvablyNonNullableIdentifier(objectNode, sourceCode.getScope(memberExpr))) {
487
+ return;
488
+ }
306
489
  shouldCheck = true;
307
490
  }
308
491
  else if (objectNode.type === 'MemberExpression') {
309
- // Nested member expressions
492
+ let base = objectNode;
493
+ while (base.type === 'MemberExpression') {
494
+ base = base.object;
495
+ }
496
+ if (base.type === 'ThisExpression')
497
+ return;
498
+ if (base.type === 'Identifier' &&
499
+ isProvablyNonNullableIdentifier(base, sourceCode.getScope(memberExpr))) {
500
+ return;
501
+ }
310
502
  shouldCheck = true;
311
503
  }
312
504
  if (shouldCheck && !hasNullCheck(memberExpr, sourceCode)) {
@@ -69,6 +69,7 @@ exports.noUnsafeTypeNarrowing = (0, eslint_devkit_2.createRule)({
69
69
  meta: {
70
70
  type: 'problem',
71
71
  docs: {
72
+ url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-reliability/docs/rules/no-unsafe-type-narrowing.md',
72
73
  description: 'Detects unsafe type narrowing patterns',
73
74
  },
74
75
  hasSuggestions: true,
@@ -133,12 +134,12 @@ exports.noUnsafeTypeNarrowing = (0, eslint_devkit_2.createRule)({
133
134
  ],
134
135
  create(context, [options = {}]) {
135
136
  const { ignoreInTests = true, allowWithComment = false } = options || {};
136
- const filename = context.getFilename();
137
+ const filename = context.filename;
137
138
  const isTestFile = ignoreInTests && /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);
138
139
  if (isTestFile) {
139
140
  return {};
140
141
  }
141
- const sourceCode = context.sourceCode || context.sourceCode;
142
+ const sourceCode = context.sourceCode;
142
143
  /**
143
144
  * Check type assertions
144
145
  */
@@ -17,7 +17,10 @@ exports.requireNetworkTimeout = (0, eslint_devkit_1.createRule)({
17
17
  meta: {
18
18
  type: 'problem',
19
19
  docs: {
20
+ url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-reliability/docs/rules/require-network-timeout.md',
20
21
  description: 'Require timeout limits for network requests',
22
+ cwe: 'CWE-400',
23
+ cvss: 5,
21
24
  },
22
25
  messages: {
23
26
  violationDetected: (0, eslint_devkit_1.formatLLMMessage)({
@@ -43,11 +46,15 @@ exports.requireNetworkTimeout = (0, eslint_devkit_1.createRule)({
43
46
  callee.object.name === 'axios';
44
47
  if (isFetch || isAxios) {
45
48
  const optionsArg = node.arguments[1];
46
- const hasTimeout = optionsArg?.type === eslint_devkit_1.AST_NODE_TYPES.ObjectExpression &&
49
+ // Either an explicit `timeout` option, or an AbortSignal — both
50
+ // bound the request. `{ signal: controller.signal }` is the
51
+ // standard timeout-via-AbortController pattern; flagging it as
52
+ // "missing timeout" is a false positive.
53
+ const hasBound = optionsArg?.type === eslint_devkit_1.AST_NODE_TYPES.ObjectExpression &&
47
54
  optionsArg.properties.some((p) => p.type === eslint_devkit_1.AST_NODE_TYPES.Property &&
48
55
  p.key.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
49
- p.key.name === 'timeout');
50
- if (!hasTimeout) {
56
+ (p.key.name === 'timeout' || p.key.name === 'signal'));
57
+ if (!hasBound) {
51
58
  context.report({ node, messageId: 'violationDetected' });
52
59
  }
53
60
  }