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 +130 -0
- package/LICENSE +23 -0
- package/README.md +49 -20
- package/package.json +20 -3
- package/src/index.d.ts +1 -1
- package/src/index.js +2 -2
- package/src/oxlint.d.ts +20 -0
- package/src/oxlint.js +21 -0
- package/src/rules/error-handling/error-message.js +2 -0
- package/src/rules/error-handling/no-missing-error-context.js +32 -2
- package/src/rules/error-handling/no-silent-errors.js +3 -2
- package/src/rules/error-handling/no-unhandled-promise.js +116 -14
- package/src/rules/reliability/no-await-in-loop.js +19 -17
- package/src/rules/reliability/no-jsdoc-terminator-in-example.js +2 -2
- package/src/rules/reliability/no-missing-null-checks.js +205 -13
- package/src/rules/reliability/no-unsafe-type-narrowing.js +3 -2
- package/src/rules/reliability/require-network-timeout.js +10 -3
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
|
-
|
|
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=
|
|
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
|
|
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
|
|
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
|
-
|
|
|
142
|
-
|
|
|
143
|
-
|
|
144
|
-
|
|
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) | [](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) | [](https://www.npmjs.com/package/eslint-plugin-pg) | PostgreSQL security & best practices. |
|
|
154
|
-
| [`eslint-plugin-
|
|
174
|
+
| [`eslint-plugin-node-security`](https://www.npmjs.com/package/eslint-plugin-node-security) | [](https://www.npmjs.com/package/eslint-plugin-node-security) | Node.js core-module security (fs, child_process, vm, crypto, Buffer). |
|
|
155
175
|
| [`eslint-plugin-jwt`](https://www.npmjs.com/package/eslint-plugin-jwt) | [](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) | [](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) | [](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) | [](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) | [](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
|
+
[](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.
|
|
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://
|
|
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': "
|
|
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.
|
|
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': '
|
|
54
|
+
'reliability/no-silent-errors': 'error',
|
|
55
55
|
'reliability/no-missing-null-checks': 'warn',
|
|
56
56
|
'reliability/require-network-timeout': 'error',
|
|
57
57
|
},
|
package/src/oxlint.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Ofri Peretz
|
|
3
|
+
* Licensed under the MIT License. Use of this source code is governed by the
|
|
4
|
+
* MIT license that can be found in the LICENSE file.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* oxlint sub-export.
|
|
8
|
+
*
|
|
9
|
+
* Consumers wire oxlint to this entry via:
|
|
10
|
+
* { "jsPlugins": ["eslint-plugin-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.
|
|
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.
|
|
139
|
+
const filename = context.filename;
|
|
139
140
|
const isTestFile = ignoreInTests && /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);
|
|
140
141
|
if (isTestFile) {
|
|
141
142
|
return {};
|
|
142
143
|
}
|
|
143
|
-
const sourceCode = context.sourceCode
|
|
144
|
+
const sourceCode = context.sourceCode;
|
|
144
145
|
/**
|
|
145
146
|
* Check catch clauses
|
|
146
147
|
*/
|
|
@@ -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
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
//
|
|
23
|
-
|
|
24
|
-
|
|
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.
|
|
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
|
|
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 (!
|
|
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 (!
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
167
|
+
loopCtx.hasDependencies = true;
|
|
167
168
|
}
|
|
168
169
|
// Check for side effects that might require sequential execution
|
|
169
170
|
if (currentNode.type === 'CallExpression' &&
|
|
170
|
-
(
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
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 (
|
|
199
|
-
|
|
200
|
-
|
|
199
|
+
if (loopCtx.hasDependencies || loopCtx.hasSideEffects) {
|
|
200
|
+
loopCtx.isSequential = true;
|
|
201
|
+
loopCtx.estimatedPerformance = `sequential (${loopCtx.operationCount} operations)`;
|
|
201
202
|
}
|
|
202
203
|
else {
|
|
203
|
-
|
|
204
|
-
|
|
204
|
+
loopCtx.isSequential = false;
|
|
205
|
+
loopCtx.estimatedPerformance = `potential ${loopCtx.operationCount}x speedup with concurrency`;
|
|
205
206
|
}
|
|
206
|
-
return
|
|
207
|
+
return loopCtx;
|
|
207
208
|
}
|
|
208
|
-
|
|
209
|
-
|
|
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
|
|
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;
|
|
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
|
-
|
|
29
|
-
//
|
|
30
|
-
//
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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 (!
|
|
56
|
+
(p.key.name === 'timeout' || p.key.name === 'signal'));
|
|
57
|
+
if (!hasBound) {
|
|
51
58
|
context.report({ node, messageId: 'violationDetected' });
|
|
52
59
|
}
|
|
53
60
|
}
|