eslint-plugin-reliability 3.1.0 → 3.1.3

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,104 @@
1
+ ## [3.1.3] - 2026-05-03
2
+
3
+ ### Bug Fixes
4
+
5
+ - `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.
6
+ - `no-unhandled-promise`: refined detection (see source diff).
7
+
8
+ ## 3.1.2 (2026-02-09)
9
+
10
+ This was a version bump only for eslint-plugin-reliability to align it with other projects, there were no code changes.
11
+
12
+ ## [3.1.0] - 2026-02-08
13
+
14
+ This was a version bump only for eslint-plugin-reliability to align it with other projects, there were no code changes.
15
+
16
+ ## [3.0.4] - 2026-02-08
17
+
18
+ ### Bug Fixes
19
+
20
+ - align codecov component IDs with full package names ([2831b968](https://github.com/ofri-peretz/eslint/commit/2831b968))
21
+
22
+ ### Documentation
23
+
24
+ - fix changelog header format across all packages ([c3a15082](https://github.com/ofri-peretz/eslint/commit/c3a15082))
25
+
26
+ ### ❤️ Thank You
27
+
28
+ - Ofri Peretz
29
+
30
+ ## [3.0.3] - 2026-02-06
31
+
32
+ ### Bug Fixes
33
+
34
+ - align codecov component names and update docs components ([0a59a86c](https://github.com/ofri-peretz/eslint/commit/0a59a86c))
35
+
36
+ ### ❤️ Thank You
37
+
38
+ - Ofri Peretz
39
+
40
+ ## [3.0.2] - 2026-02-02
41
+
42
+ This was a version bump only for eslint-plugin-reliability to align it with other projects, there were no code changes.
43
+
44
+ # Changelog
45
+
46
+ All notable changes to `eslint-plugin-reliability` will be documented in this file.
47
+
48
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
49
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
50
+
51
+ ### Documentation
52
+
53
+ - 📘 Launched new documentation site: [eslint.interlace.tools](https://eslint.interlace.tools/)
54
+ - 📝 Achieved 100% documentation parity (both .md and .mdx files)
55
+
56
+ ## [3.0.1] - 2026-02-02
57
+
58
+ This was a version bump only for eslint-plugin-reliability to align it with other projects, there were no code changes.
59
+
60
+ ## [3.0.0] - 2026-02-02
61
+
62
+ This was a version bump only for eslint-plugin-reliability to align it with other projects, there were no code changes.
63
+
64
+ ## [1.0.0] - 2026-01-26
65
+
66
+ ### Added
67
+
68
+ - Initial stable release with 8 reliability rules
69
+ - LLM-optimized error messages for AI-assisted development
70
+ - 100% test coverage across all rules
71
+ - ESLint 9 flat config support
72
+ - TypeScript type definitions for all rule options
73
+
74
+ ### Rules
75
+
76
+ #### Error Handling (4 rules)
77
+
78
+ | Rule | Description | CWE |
79
+ | :------------------------- | :-------------------------------------------- | :------ |
80
+ | `no-unhandled-promise` | Detect unhandled promise rejections | CWE-392 |
81
+ | `no-silent-errors` | Detect empty catch blocks that swallow errors | CWE-390 |
82
+ | `no-missing-error-context` | Require error context when re-throwing | CWE-209 |
83
+ | `error-message` | Require meaningful error messages | CWE-209 |
84
+
85
+ #### Runtime Safety (4 rules)
86
+
87
+ | Rule | Description | CWE |
88
+ | :------------------------- | :-------------------------------------------- | :---------- |
89
+ | `no-missing-null-checks` | Detect potential null/undefined dereferences | CWE-476 |
90
+ | `no-unsafe-type-narrowing` | Detect unsafe type narrowing patterns | CWE-704 |
91
+ | `require-network-timeout` | Require timeouts on network requests | CWE-400 |
92
+ | `no-await-in-loop` | Detect sequential await in loops (N+1 issues) | Performance |
93
+
94
+ ### Presets
95
+
96
+ - `recommended` - Balanced reliability checks (4 rules as warnings)
97
+
98
+ ### Known Limitations
99
+
100
+ Documented in `docs/KNOWN-LIMITATIONS.md`:
101
+
102
+ - `no-unhandled-promises`: Promises in callbacks or async iterators may be missed
103
+ - `no-silent-errors`: Intentional suppression vs. actual swallowing is hard to distinguish
104
+ - `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
@@ -3,21 +3,20 @@
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=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
17
  ## Description
18
18
 
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.
19
+ This plugin provides Reliability rules for defensive programming, error handling, and async correctness.
21
20
 
22
21
  ## Philosophy
23
22
 
@@ -126,6 +125,14 @@ const results = await Promise.all(userIds.map((id) => fetchUser(id)));
126
125
 
127
126
  ---
128
127
 
128
+ ## 📦 Compatibility
129
+ | Package | Version |
130
+ | :--- | :--- |
131
+ | ESLint | `^8.0.0 \|\| ^9.0.0 \|\| ^10.0.0` |
132
+ | Node.js | `>=18.0.0` |
133
+
134
+ 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.
135
+
129
136
  ## Rules
130
137
 
131
138
  **Legend**
@@ -133,16 +140,27 @@ const results = await Promise.all(userIds.map((id) => fetchUser(id)));
133
140
  | Icon | Description |
134
141
  | :---: | :--- |
135
142
  | 💼 | **Recommended**: Included in the recommended preset. |
136
- | ⚠️ | **Warns**: Set towarn in recommended preset. |
143
+ | ⚠️ | **Warns**: Set to warn in recommended preset. |
137
144
  | 🔧 | **Auto-fixable**: Automatically fixable by the `--fix` CLI option. |
138
145
  | 💡 | **Suggestions**: Providing code suggestions in IDE. |
139
146
  | 🚫 | **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
-
147
+ | 🟢 | **Type-unaware**: AST-only, runs in oxlint JS-plugin tier. |
148
+ | 🟡 | **Type-aware (refining)**: pure-AST primary path; types refine precision. |
149
+ | 🟠 | **Type-aware (graceful)**: requires TS program; silent without it. |
150
+
151
+ <!-- AUTO-GENERATED:RULES_TABLE:START - Do not edit manually -->
152
+ | Rule | CWE | OWASP | CVSS | Description | 🧠 | 💼 | ⚠️ | 🔧 | 💡 | 🚫 |
153
+ | :--- | :---: | :---: | :---: | :--- | :---: | :---: | :---: | :---: | :---: | :---: |
154
+ | [error-message](https://eslint.interlace.tools/docs/quality/plugin-reliability/rules/error-message) | | | | Enforce providing a message when creating built-in Error objects for better debugging. This rule is part of… | 🟢 | | | | 💡 | |
155
+ | [no-await-in-loop](https://eslint.interlace.tools/docs/quality/plugin-reliability/rules/no-await-in-loop) | | | | Disallow await inside loops without considering concurrency implications | 🟢 | | | | 💡 | |
156
+ | [no-jsdoc-terminator-in-example](https://eslint.interlace.tools/docs/quality/plugin-reliability/rules/no-jsdoc-terminator-in-example) | | | | Detect `*/` sequences inside JSDoc `@example` blocks that prematurely close the JSDoc comment. | 🟢 | | | | 💡 | |
157
+ | [no-missing-error-context](https://eslint.interlace.tools/docs/quality/plugin-reliability/rules/no-missing-error-context) | | | | ESLint Rule: no-missing-error-context with LLM-optimized suggestions and auto-fix capabilities. | 🟢 | | | | 💡 | |
158
+ | [no-missing-null-checks](https://eslint.interlace.tools/docs/quality/plugin-reliability/rules/no-missing-null-checks) | CWE-476 | | | ESLint Rule: no-missing-null-checks with LLM-optimized suggestions and auto-fix capabilities. | 🟢 | | ⚠️ | | 💡 | |
159
+ | [no-silent-errors](https://eslint.interlace.tools/docs/quality/plugin-reliability/rules/no-silent-errors) | | | | ESLint Rule: no-silent-errors with LLM-optimized suggestions and auto-fix capabilities. | 🟢 | | ⚠️ | | 💡 | |
160
+ | [no-unhandled-promise](https://eslint.interlace.tools/docs/quality/plugin-reliability/rules/no-unhandled-promise) | CWE-1024 | | | Disallow unhandled Promise rejections with LLM-optimized suggestions for proper async error handling. This… | 🟢 | | | | 💡 | |
161
+ | [no-unsafe-type-narrowing](https://eslint.interlace.tools/docs/quality/plugin-reliability/rules/no-unsafe-type-narrowing) | | | | ESLint Rule: no-unsafe-type-narrowing with LLM-optimized suggestions and auto-fix capabilities. | 🟢 | | | | 💡 | |
162
+ | [require-network-timeout](https://eslint.interlace.tools/docs/quality/plugin-reliability/rules/require-network-timeout) | | | | Require timeout configuration for network requests. This rule is part of eslint-plugin-reliability and prov… | 🟢 | 💼 | | | 💡 | |
163
+ <!-- AUTO-GENERATED:RULES_TABLE:END -->
146
164
  ## 🔗 Related ESLint Plugins
147
165
 
148
166
  Part of the **Interlace ESLint Ecosystem** — AI-native security plugins with LLM-optimized error messages:
@@ -151,7 +169,7 @@ Part of the **Interlace ESLint Ecosystem** — AI-native security plugins with L
151
169
  | :--- | :---: | :--- |
152
170
  | [`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
171
  | [`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. |
172
+ | [`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
173
  | [`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
174
  | [`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
175
  | [`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. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-reliability",
3
- "version": "3.1.0",
3
+ "version": "3.1.3",
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>",
@@ -51,5 +55,14 @@
51
55
  "homepage": "https://github.com/ofri-peretz/eslint/tree/main/packages/eslint-plugin-reliability#readme",
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"
54
67
  }
55
- }
68
+ }
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.3',
45
45
  },
46
46
  rules: exports.rules,
47
47
  };
@@ -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,6 +60,7 @@ 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
66
  fixable: 'code',
@@ -86,7 +87,7 @@ exports.noJsdocTerminatorInExample = (0, eslint_devkit_1.createRule)({
86
87
  },
87
88
  defaultOptions: [],
88
89
  create(context) {
89
- const sourceCode = context.sourceCode || context.sourceCode;
90
+ const sourceCode = context.sourceCode;
90
91
  return {
91
92
  Program() {
92
93
  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
  */
@@ -113,7 +231,10 @@ exports.noMissingNullChecks = (0, eslint_devkit_2.createRule)({
113
231
  meta: {
114
232
  type: 'problem',
115
233
  docs: {
234
+ url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-reliability/docs/rules/no-missing-null-checks.md',
116
235
  description: 'Detects potential null pointer dereferences',
236
+ cwe: 'CWE-476',
237
+ cvss: 7.5,
117
238
  },
118
239
  hasSuggestions: true,
119
240
  messages: {
@@ -180,12 +301,12 @@ exports.noMissingNullChecks = (0, eslint_devkit_2.createRule)({
180
301
  const { ignoreInTests = true,
181
302
  // requireExplicitChecks = false, // Not used
182
303
  } = options || {};
183
- const filename = context.getFilename();
304
+ const filename = context.filename;
184
305
  const isTestFile = ignoreInTests && /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);
185
306
  if (isTestFile) {
186
307
  return {};
187
308
  }
188
- const sourceCode = context.sourceCode || context.sourceCode;
309
+ const sourceCode = context.sourceCode;
189
310
  // Track reported MemberExpression nodes to prevent duplicate reports
190
311
  // Key format: "start-end" from node.range
191
312
  const reportedMemberExpressions = new Set();
@@ -234,10 +355,30 @@ exports.noMissingNullChecks = (0, eslint_devkit_2.createRule)({
234
355
  const objectNode = node.object;
235
356
  let shouldCheck = false;
236
357
  if (objectNode.type === 'Identifier') {
358
+ // Skip identifiers that resolve to globals, catch params, imports,
359
+ // function declarations, or `new X()` results — these are never null.
360
+ if (isProvablyNonNullableIdentifier(objectNode, sourceCode.getScope(node))) {
361
+ return;
362
+ }
237
363
  shouldCheck = true;
238
364
  }
239
365
  else if (objectNode.type === 'MemberExpression') {
240
- // Nested member expressions like value.nested.deep
366
+ // Nested member expressions like value.nested.deep — only fire on
367
+ // the deepest, where the leaf identifier matters most. Skip if the
368
+ // base of the chain is a known-non-null global (`console.log`,
369
+ // `JSON.stringify`, etc.) or is a `this`/`this.#field` chain — `this`
370
+ // inside a method is the class instance (never null), and private
371
+ // fields are always defined when accessed from inside the class.
372
+ let base = objectNode;
373
+ while (base.type === 'MemberExpression') {
374
+ base = base.object;
375
+ }
376
+ if (base.type === 'ThisExpression')
377
+ return;
378
+ if (base.type === 'Identifier' &&
379
+ isProvablyNonNullableIdentifier(base, sourceCode.getScope(node))) {
380
+ return;
381
+ }
241
382
  shouldCheck = true;
242
383
  }
243
384
  if (shouldCheck && !hasNullCheck(node, sourceCode)) {
@@ -303,10 +444,22 @@ exports.noMissingNullChecks = (0, eslint_devkit_2.createRule)({
303
444
  const objectNode = memberExpr.object;
304
445
  let shouldCheck = false;
305
446
  if (objectNode.type === 'Identifier') {
447
+ if (isProvablyNonNullableIdentifier(objectNode, sourceCode.getScope(memberExpr))) {
448
+ return;
449
+ }
306
450
  shouldCheck = true;
307
451
  }
308
452
  else if (objectNode.type === 'MemberExpression') {
309
- // Nested member expressions
453
+ let base = objectNode;
454
+ while (base.type === 'MemberExpression') {
455
+ base = base.object;
456
+ }
457
+ if (base.type === 'ThisExpression')
458
+ return;
459
+ if (base.type === 'Identifier' &&
460
+ isProvablyNonNullableIdentifier(base, sourceCode.getScope(memberExpr))) {
461
+ return;
462
+ }
310
463
  shouldCheck = true;
311
464
  }
312
465
  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
  }