eslint-plugin-reliability 3.0.0
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/README.md +159 -0
- package/package.json +55 -0
- package/src/index.d.ts +180 -0
- package/src/index.js +57 -0
- package/src/lib/eslint-plugin-reliability.d.ts +1 -0
- package/src/lib/eslint-plugin-reliability.js +6 -0
- package/src/rules/error-handling/error-message.d.ts +20 -0
- package/src/rules/error-handling/error-message.js +146 -0
- package/src/rules/error-handling/no-missing-error-context.d.ts +26 -0
- package/src/rules/error-handling/no-missing-error-context.js +183 -0
- package/src/rules/error-handling/no-silent-errors.d.ts +24 -0
- package/src/rules/error-handling/no-silent-errors.js +178 -0
- package/src/rules/error-handling/no-unhandled-promise.d.ts +26 -0
- package/src/rules/error-handling/no-unhandled-promise.js +351 -0
- package/src/rules/reliability/no-await-in-loop.d.ts +24 -0
- package/src/rules/reliability/no-await-in-loop.js +234 -0
- package/src/rules/reliability/no-missing-null-checks.d.ts +26 -0
- package/src/rules/reliability/no-missing-null-checks.js +351 -0
- package/src/rules/reliability/no-unsafe-type-narrowing.d.ts +24 -0
- package/src/rules/reliability/no-unsafe-type-narrowing.js +176 -0
- package/src/rules/reliability/require-network-timeout.d.ts +12 -0
- package/src/rules/reliability/require-network-timeout.js +57 -0
package/README.md
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
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>
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
Error handling, null safety, and runtime reliability rules for robust applications.
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
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
|
+
<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
|
+
<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
|
+
</p>
|
|
14
|
+
|
|
15
|
+
## Description
|
|
16
|
+
|
|
17
|
+
This plugin enforces reliable code patterns by detecting unhandled promises, silent error swallowing, missing null checks, and network timeouts. It helps teams build applications that fail gracefully and recover predictably.
|
|
18
|
+
|
|
19
|
+
## Philosophy
|
|
20
|
+
|
|
21
|
+
**Interlace** fosters **strength through integration**. Reliability isn't optional — it's foundational. These rules catch common runtime failure patterns before they cause incidents in production.
|
|
22
|
+
|
|
23
|
+
## Getting Started
|
|
24
|
+
|
|
25
|
+
- To check out the [guide](https://eslint.interlace.tools/docs/reliability), visit [eslint.interlace.tools](https://eslint.interlace.tools). 📚
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install eslint-plugin-reliability --save-dev
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## ⚙️ Configuration Presets
|
|
32
|
+
|
|
33
|
+
| Preset | Description |
|
|
34
|
+
| :------------ | :-------------------------------------------- |
|
|
35
|
+
| `recommended` | Balanced reliability checks for most projects |
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 🏢 Usage Example
|
|
40
|
+
|
|
41
|
+
```js
|
|
42
|
+
// eslint.config.js
|
|
43
|
+
import reliability from 'eslint-plugin-reliability';
|
|
44
|
+
|
|
45
|
+
export default [reliability.configs.recommended];
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Rules
|
|
51
|
+
|
|
52
|
+
### Error Handling
|
|
53
|
+
|
|
54
|
+
| Rule | Description | 💼 | ⚠️ |
|
|
55
|
+
| :------------------------------------------------------------------- | :-------------------------------------------- | :-: | :-: |
|
|
56
|
+
| [no-unhandled-promise](./docs/rules/no-unhandled-promise.md) | Detect unhandled promise rejections | | |
|
|
57
|
+
| [no-silent-errors](./docs/rules/no-silent-errors.md) | Detect empty catch blocks that swallow errors | 💼 | ⚠️ |
|
|
58
|
+
| [no-missing-error-context](./docs/rules/no-missing-error-context.md) | Require error context when re-throwing | | |
|
|
59
|
+
| [error-message](./docs/rules/error-message.md) | Require meaningful error messages | | |
|
|
60
|
+
|
|
61
|
+
### Runtime Safety
|
|
62
|
+
|
|
63
|
+
| Rule | Description | 💼 | ⚠️ |
|
|
64
|
+
| :------------------------------------------------------------------- | :--------------------------------------------- | :-: | :-: |
|
|
65
|
+
| [no-missing-null-checks](./docs/rules/no-missing-null-checks.md) | Detect potential null/undefined dereferences | 💼 | ⚠️ |
|
|
66
|
+
| [no-unsafe-type-narrowing](./docs/rules/no-unsafe-type-narrowing.md) | Detect unsafe type narrowing patterns | | |
|
|
67
|
+
| [require-network-timeout](./docs/rules/require-network-timeout.md) | Require timeouts on network requests | 💼 | |
|
|
68
|
+
| [no-await-in-loop](./docs/rules/no-await-in-loop.md) | Detect sequential await in loops (performance) | | |
|
|
69
|
+
|
|
70
|
+
**Legend**: 💼 Recommended | ⚠️ Warns (not error)
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Why These Rules?
|
|
75
|
+
|
|
76
|
+
### `no-silent-errors`
|
|
77
|
+
|
|
78
|
+
Empty catch blocks hide errors, making debugging impossible.
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
// ❌ Bad: Silent error swallowing
|
|
82
|
+
try {
|
|
83
|
+
await processPayment(order);
|
|
84
|
+
} catch (e) {
|
|
85
|
+
// Error is silently ignored!
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ✅ Good: Handle or log the error
|
|
89
|
+
try {
|
|
90
|
+
await processPayment(order);
|
|
91
|
+
} catch (e) {
|
|
92
|
+
logger.error('Payment processing failed', { orderId: order.id, error: e });
|
|
93
|
+
throw new PaymentError('Payment failed', { cause: e });
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### `no-missing-null-checks`
|
|
98
|
+
|
|
99
|
+
Catches potential `null` or `undefined` dereferences.
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
// ❌ Bad: Potential runtime error
|
|
103
|
+
function greet(user: User | null) {
|
|
104
|
+
return `Hello, ${user.name}`; // TypeError if user is null!
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ✅ Good: Null-safe access
|
|
108
|
+
function greet(user: User | null) {
|
|
109
|
+
return user ? `Hello, ${user.name}` : 'Hello, guest';
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### `require-network-timeout`
|
|
114
|
+
|
|
115
|
+
Network requests without timeouts can hang indefinitely.
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
// ❌ Bad: No timeout, can hang forever
|
|
119
|
+
const response = await fetch('/api/data');
|
|
120
|
+
|
|
121
|
+
// ✅ Good: Request with timeout
|
|
122
|
+
const controller = new AbortController();
|
|
123
|
+
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
|
124
|
+
|
|
125
|
+
const response = await fetch('/api/data', {
|
|
126
|
+
signal: controller.signal,
|
|
127
|
+
});
|
|
128
|
+
clearTimeout(timeoutId);
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### `no-await-in-loop`
|
|
132
|
+
|
|
133
|
+
Sequential awaits in loops cause N+1 performance issues.
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
// ❌ Bad: Sequential requests (slow)
|
|
137
|
+
for (const id of userIds) {
|
|
138
|
+
const user = await fetchUser(id); // N sequential requests
|
|
139
|
+
results.push(user);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ✅ Good: Parallel requests (fast)
|
|
143
|
+
const results = await Promise.all(userIds.map((id) => fetchUser(id)));
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## 🔗 Related ESLint Plugins
|
|
149
|
+
|
|
150
|
+
Part of the **Interlace ESLint Ecosystem** — AI-native quality plugins with LLM-optimized error messages:
|
|
151
|
+
|
|
152
|
+
| Plugin | Description |
|
|
153
|
+
| :--------------------------------------------------------------------------------------------- | :---------------------------------- |
|
|
154
|
+
| [`eslint-plugin-operability`](https://www.npmjs.com/package/eslint-plugin-operability) | Production readiness & debug code |
|
|
155
|
+
| [`eslint-plugin-maintainability`](https://www.npmjs.com/package/eslint-plugin-maintainability) | Cognitive complexity & code quality |
|
|
156
|
+
|
|
157
|
+
## 📄 License
|
|
158
|
+
|
|
159
|
+
MIT © [Ofri Peretz](https://github.com/ofri-peretz)
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "eslint-plugin-reliability",
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"description": "ESLint rules for runtime stability, fault tolerance, and type safety.",
|
|
5
|
+
"type": "commonjs",
|
|
6
|
+
"main": "./src/index.js",
|
|
7
|
+
"types": "./src/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./src/index.d.ts",
|
|
11
|
+
"default": "./src/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"author": "Ofri Peretz <ofriperetzdev@gmail.com>",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"src/",
|
|
21
|
+
"dist/",
|
|
22
|
+
"README.md",
|
|
23
|
+
"LICENSE",
|
|
24
|
+
"CHANGELOG.md"
|
|
25
|
+
],
|
|
26
|
+
"keywords": [
|
|
27
|
+
"eslint",
|
|
28
|
+
"eslint-plugin",
|
|
29
|
+
"interlace-quality",
|
|
30
|
+
"reliability",
|
|
31
|
+
"error-handling",
|
|
32
|
+
"type-safety",
|
|
33
|
+
"llm-optimized"
|
|
34
|
+
],
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18.0.0"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"tslib": "^2.3.0",
|
|
40
|
+
"@interlace/eslint-devkit": "*"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@typescript-eslint/parser": "^8.46.2",
|
|
44
|
+
"@typescript-eslint/rule-tester": "^8.46.2"
|
|
45
|
+
},
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "https://github.com/ofri-peretz/eslint",
|
|
49
|
+
"directory": "packages/eslint-plugin-reliability"
|
|
50
|
+
},
|
|
51
|
+
"homepage": "https://github.com/ofri-peretz/eslint/tree/main/packages/eslint-plugin-reliability#readme",
|
|
52
|
+
"bugs": {
|
|
53
|
+
"url": "https://github.com/ofri-peretz/eslint/issues"
|
|
54
|
+
}
|
|
55
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
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
|
+
import type { TSESLint } from '@interlace/eslint-devkit';
|
|
7
|
+
export declare const rules: {
|
|
8
|
+
'no-unhandled-promise': TSESLint.RuleModule<"unhandledPromise" | "addCatch" | "useTryCatch" | "useAwait", [(import("./rules/error-handling/no-unhandled-promise").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
9
|
+
name: string;
|
|
10
|
+
};
|
|
11
|
+
'no-silent-errors': TSESLint.RuleModule<"silentError" | "addErrorLogging" | "addErrorHandling" | "rethrowError", [(import("./rules/error-handling/no-silent-errors").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
12
|
+
name: string;
|
|
13
|
+
};
|
|
14
|
+
'no-missing-error-context': TSESLint.RuleModule<"missingErrorContext" | "addErrorMessage" | "addErrorStack" | "useErrorClass", [(import("./rules/error-handling/no-missing-error-context").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
15
|
+
name: string;
|
|
16
|
+
};
|
|
17
|
+
'error-message': TSESLint.RuleModule<"addErrorMessage" | "missingErrorMessage", [(import("./rules/error-handling/error-message").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
18
|
+
name: string;
|
|
19
|
+
};
|
|
20
|
+
'no-missing-null-checks': TSESLint.RuleModule<"missingNullCheck" | "useOptionalChaining" | "useNullishCoalescing" | "addExplicitCheck", [(import("./rules/reliability/no-missing-null-checks").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
21
|
+
name: string;
|
|
22
|
+
};
|
|
23
|
+
'no-unsafe-type-narrowing': TSESLint.RuleModule<"unsafeTypeNarrowing" | "useTypeGuard" | "useProperNarrowing" | "validateBeforeAssert", [(import("./rules/reliability/no-unsafe-type-narrowing").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
24
|
+
name: string;
|
|
25
|
+
};
|
|
26
|
+
'require-network-timeout': TSESLint.RuleModule<"violationDetected", [(import("./rules/reliability/require-network-timeout").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
27
|
+
name: string;
|
|
28
|
+
};
|
|
29
|
+
'no-await-in-loop': TSESLint.RuleModule<"awaitInLoop" | "suggestPromiseAll" | "suggestConcurrent" | "considerSequential" | "asyncLoopPattern", [(import("./rules/reliability/no-await-in-loop").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
30
|
+
name: string;
|
|
31
|
+
};
|
|
32
|
+
'error-handling/no-unhandled-promise': TSESLint.RuleModule<"unhandledPromise" | "addCatch" | "useTryCatch" | "useAwait", [(import("./rules/error-handling/no-unhandled-promise").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
33
|
+
name: string;
|
|
34
|
+
};
|
|
35
|
+
'error-handling/no-silent-errors': TSESLint.RuleModule<"silentError" | "addErrorLogging" | "addErrorHandling" | "rethrowError", [(import("./rules/error-handling/no-silent-errors").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
36
|
+
name: string;
|
|
37
|
+
};
|
|
38
|
+
'error-handling/no-missing-error-context': TSESLint.RuleModule<"missingErrorContext" | "addErrorMessage" | "addErrorStack" | "useErrorClass", [(import("./rules/error-handling/no-missing-error-context").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
39
|
+
name: string;
|
|
40
|
+
};
|
|
41
|
+
'error-handling/error-message': TSESLint.RuleModule<"addErrorMessage" | "missingErrorMessage", [(import("./rules/error-handling/error-message").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
42
|
+
name: string;
|
|
43
|
+
};
|
|
44
|
+
'reliability/no-missing-null-checks': TSESLint.RuleModule<"missingNullCheck" | "useOptionalChaining" | "useNullishCoalescing" | "addExplicitCheck", [(import("./rules/reliability/no-missing-null-checks").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
45
|
+
name: string;
|
|
46
|
+
};
|
|
47
|
+
'reliability/no-unsafe-type-narrowing': TSESLint.RuleModule<"unsafeTypeNarrowing" | "useTypeGuard" | "useProperNarrowing" | "validateBeforeAssert", [(import("./rules/reliability/no-unsafe-type-narrowing").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
48
|
+
name: string;
|
|
49
|
+
};
|
|
50
|
+
'reliability/require-network-timeout': TSESLint.RuleModule<"violationDetected", [(import("./rules/reliability/require-network-timeout").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
51
|
+
name: string;
|
|
52
|
+
};
|
|
53
|
+
'reliability/no-await-in-loop': TSESLint.RuleModule<"awaitInLoop" | "suggestPromiseAll" | "suggestConcurrent" | "considerSequential" | "asyncLoopPattern", [(import("./rules/reliability/no-await-in-loop").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
54
|
+
name: string;
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
export declare const plugin: {
|
|
58
|
+
meta: {
|
|
59
|
+
name: string;
|
|
60
|
+
version: string;
|
|
61
|
+
};
|
|
62
|
+
rules: {
|
|
63
|
+
'no-unhandled-promise': TSESLint.RuleModule<"unhandledPromise" | "addCatch" | "useTryCatch" | "useAwait", [(import("./rules/error-handling/no-unhandled-promise").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
64
|
+
name: string;
|
|
65
|
+
};
|
|
66
|
+
'no-silent-errors': TSESLint.RuleModule<"silentError" | "addErrorLogging" | "addErrorHandling" | "rethrowError", [(import("./rules/error-handling/no-silent-errors").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
67
|
+
name: string;
|
|
68
|
+
};
|
|
69
|
+
'no-missing-error-context': TSESLint.RuleModule<"missingErrorContext" | "addErrorMessage" | "addErrorStack" | "useErrorClass", [(import("./rules/error-handling/no-missing-error-context").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
70
|
+
name: string;
|
|
71
|
+
};
|
|
72
|
+
'error-message': TSESLint.RuleModule<"addErrorMessage" | "missingErrorMessage", [(import("./rules/error-handling/error-message").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
73
|
+
name: string;
|
|
74
|
+
};
|
|
75
|
+
'no-missing-null-checks': TSESLint.RuleModule<"missingNullCheck" | "useOptionalChaining" | "useNullishCoalescing" | "addExplicitCheck", [(import("./rules/reliability/no-missing-null-checks").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
76
|
+
name: string;
|
|
77
|
+
};
|
|
78
|
+
'no-unsafe-type-narrowing': TSESLint.RuleModule<"unsafeTypeNarrowing" | "useTypeGuard" | "useProperNarrowing" | "validateBeforeAssert", [(import("./rules/reliability/no-unsafe-type-narrowing").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
79
|
+
name: string;
|
|
80
|
+
};
|
|
81
|
+
'require-network-timeout': TSESLint.RuleModule<"violationDetected", [(import("./rules/reliability/require-network-timeout").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
82
|
+
name: string;
|
|
83
|
+
};
|
|
84
|
+
'no-await-in-loop': TSESLint.RuleModule<"awaitInLoop" | "suggestPromiseAll" | "suggestConcurrent" | "considerSequential" | "asyncLoopPattern", [(import("./rules/reliability/no-await-in-loop").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
85
|
+
name: string;
|
|
86
|
+
};
|
|
87
|
+
'error-handling/no-unhandled-promise': TSESLint.RuleModule<"unhandledPromise" | "addCatch" | "useTryCatch" | "useAwait", [(import("./rules/error-handling/no-unhandled-promise").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
88
|
+
name: string;
|
|
89
|
+
};
|
|
90
|
+
'error-handling/no-silent-errors': TSESLint.RuleModule<"silentError" | "addErrorLogging" | "addErrorHandling" | "rethrowError", [(import("./rules/error-handling/no-silent-errors").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
91
|
+
name: string;
|
|
92
|
+
};
|
|
93
|
+
'error-handling/no-missing-error-context': TSESLint.RuleModule<"missingErrorContext" | "addErrorMessage" | "addErrorStack" | "useErrorClass", [(import("./rules/error-handling/no-missing-error-context").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
94
|
+
name: string;
|
|
95
|
+
};
|
|
96
|
+
'error-handling/error-message': TSESLint.RuleModule<"addErrorMessage" | "missingErrorMessage", [(import("./rules/error-handling/error-message").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
97
|
+
name: string;
|
|
98
|
+
};
|
|
99
|
+
'reliability/no-missing-null-checks': TSESLint.RuleModule<"missingNullCheck" | "useOptionalChaining" | "useNullishCoalescing" | "addExplicitCheck", [(import("./rules/reliability/no-missing-null-checks").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
100
|
+
name: string;
|
|
101
|
+
};
|
|
102
|
+
'reliability/no-unsafe-type-narrowing': TSESLint.RuleModule<"unsafeTypeNarrowing" | "useTypeGuard" | "useProperNarrowing" | "validateBeforeAssert", [(import("./rules/reliability/no-unsafe-type-narrowing").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
103
|
+
name: string;
|
|
104
|
+
};
|
|
105
|
+
'reliability/require-network-timeout': TSESLint.RuleModule<"violationDetected", [(import("./rules/reliability/require-network-timeout").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
106
|
+
name: string;
|
|
107
|
+
};
|
|
108
|
+
'reliability/no-await-in-loop': TSESLint.RuleModule<"awaitInLoop" | "suggestPromiseAll" | "suggestConcurrent" | "considerSequential" | "asyncLoopPattern", [(import("./rules/reliability/no-await-in-loop").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
109
|
+
name: string;
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
export declare const configs: {
|
|
114
|
+
recommended: {
|
|
115
|
+
plugins: {
|
|
116
|
+
'@interlace/reliability': {
|
|
117
|
+
meta: {
|
|
118
|
+
name: string;
|
|
119
|
+
version: string;
|
|
120
|
+
};
|
|
121
|
+
rules: {
|
|
122
|
+
'no-unhandled-promise': TSESLint.RuleModule<"unhandledPromise" | "addCatch" | "useTryCatch" | "useAwait", [(import("./rules/error-handling/no-unhandled-promise").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
123
|
+
name: string;
|
|
124
|
+
};
|
|
125
|
+
'no-silent-errors': TSESLint.RuleModule<"silentError" | "addErrorLogging" | "addErrorHandling" | "rethrowError", [(import("./rules/error-handling/no-silent-errors").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
126
|
+
name: string;
|
|
127
|
+
};
|
|
128
|
+
'no-missing-error-context': TSESLint.RuleModule<"missingErrorContext" | "addErrorMessage" | "addErrorStack" | "useErrorClass", [(import("./rules/error-handling/no-missing-error-context").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
129
|
+
name: string;
|
|
130
|
+
};
|
|
131
|
+
'error-message': TSESLint.RuleModule<"addErrorMessage" | "missingErrorMessage", [(import("./rules/error-handling/error-message").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
132
|
+
name: string;
|
|
133
|
+
};
|
|
134
|
+
'no-missing-null-checks': TSESLint.RuleModule<"missingNullCheck" | "useOptionalChaining" | "useNullishCoalescing" | "addExplicitCheck", [(import("./rules/reliability/no-missing-null-checks").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
135
|
+
name: string;
|
|
136
|
+
};
|
|
137
|
+
'no-unsafe-type-narrowing': TSESLint.RuleModule<"unsafeTypeNarrowing" | "useTypeGuard" | "useProperNarrowing" | "validateBeforeAssert", [(import("./rules/reliability/no-unsafe-type-narrowing").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
138
|
+
name: string;
|
|
139
|
+
};
|
|
140
|
+
'require-network-timeout': TSESLint.RuleModule<"violationDetected", [(import("./rules/reliability/require-network-timeout").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
141
|
+
name: string;
|
|
142
|
+
};
|
|
143
|
+
'no-await-in-loop': TSESLint.RuleModule<"awaitInLoop" | "suggestPromiseAll" | "suggestConcurrent" | "considerSequential" | "asyncLoopPattern", [(import("./rules/reliability/no-await-in-loop").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
144
|
+
name: string;
|
|
145
|
+
};
|
|
146
|
+
'error-handling/no-unhandled-promise': TSESLint.RuleModule<"unhandledPromise" | "addCatch" | "useTryCatch" | "useAwait", [(import("./rules/error-handling/no-unhandled-promise").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
147
|
+
name: string;
|
|
148
|
+
};
|
|
149
|
+
'error-handling/no-silent-errors': TSESLint.RuleModule<"silentError" | "addErrorLogging" | "addErrorHandling" | "rethrowError", [(import("./rules/error-handling/no-silent-errors").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
150
|
+
name: string;
|
|
151
|
+
};
|
|
152
|
+
'error-handling/no-missing-error-context': TSESLint.RuleModule<"missingErrorContext" | "addErrorMessage" | "addErrorStack" | "useErrorClass", [(import("./rules/error-handling/no-missing-error-context").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
153
|
+
name: string;
|
|
154
|
+
};
|
|
155
|
+
'error-handling/error-message': TSESLint.RuleModule<"addErrorMessage" | "missingErrorMessage", [(import("./rules/error-handling/error-message").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
156
|
+
name: string;
|
|
157
|
+
};
|
|
158
|
+
'reliability/no-missing-null-checks': TSESLint.RuleModule<"missingNullCheck" | "useOptionalChaining" | "useNullishCoalescing" | "addExplicitCheck", [(import("./rules/reliability/no-missing-null-checks").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
159
|
+
name: string;
|
|
160
|
+
};
|
|
161
|
+
'reliability/no-unsafe-type-narrowing': TSESLint.RuleModule<"unsafeTypeNarrowing" | "useTypeGuard" | "useProperNarrowing" | "validateBeforeAssert", [(import("./rules/reliability/no-unsafe-type-narrowing").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
162
|
+
name: string;
|
|
163
|
+
};
|
|
164
|
+
'reliability/require-network-timeout': TSESLint.RuleModule<"violationDetected", [(import("./rules/reliability/require-network-timeout").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
165
|
+
name: string;
|
|
166
|
+
};
|
|
167
|
+
'reliability/no-await-in-loop': TSESLint.RuleModule<"awaitInLoop" | "suggestPromiseAll" | "suggestConcurrent" | "considerSequential" | "asyncLoopPattern", [(import("./rules/reliability/no-await-in-loop").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
168
|
+
name: string;
|
|
169
|
+
};
|
|
170
|
+
};
|
|
171
|
+
};
|
|
172
|
+
};
|
|
173
|
+
rules: {
|
|
174
|
+
'@interlace/reliability/error-handling/no-silent-errors': "warn";
|
|
175
|
+
'@interlace/reliability/reliability/no-missing-null-checks': "warn";
|
|
176
|
+
'@interlace/reliability/reliability/require-network-timeout': "error";
|
|
177
|
+
};
|
|
178
|
+
};
|
|
179
|
+
};
|
|
180
|
+
export default plugin;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.configs = exports.plugin = exports.rules = void 0;
|
|
9
|
+
// Error handling rules
|
|
10
|
+
const no_unhandled_promise_1 = require("./rules/error-handling/no-unhandled-promise");
|
|
11
|
+
const no_silent_errors_1 = require("./rules/error-handling/no-silent-errors");
|
|
12
|
+
const no_missing_error_context_1 = require("./rules/error-handling/no-missing-error-context");
|
|
13
|
+
const error_message_1 = require("./rules/error-handling/error-message");
|
|
14
|
+
// Reliability rules
|
|
15
|
+
const no_missing_null_checks_1 = require("./rules/reliability/no-missing-null-checks");
|
|
16
|
+
const no_unsafe_type_narrowing_1 = require("./rules/reliability/no-unsafe-type-narrowing");
|
|
17
|
+
const require_network_timeout_1 = require("./rules/reliability/require-network-timeout");
|
|
18
|
+
const no_await_in_loop_1 = require("./rules/reliability/no-await-in-loop");
|
|
19
|
+
exports.rules = {
|
|
20
|
+
'no-unhandled-promise': no_unhandled_promise_1.noUnhandledPromise,
|
|
21
|
+
'no-silent-errors': no_silent_errors_1.noSilentErrors,
|
|
22
|
+
'no-missing-error-context': no_missing_error_context_1.noMissingErrorContext,
|
|
23
|
+
'error-message': error_message_1.errorMessage,
|
|
24
|
+
'no-missing-null-checks': no_missing_null_checks_1.noMissingNullChecks,
|
|
25
|
+
'no-unsafe-type-narrowing': no_unsafe_type_narrowing_1.noUnsafeTypeNarrowing,
|
|
26
|
+
'require-network-timeout': require_network_timeout_1.requireNetworkTimeout,
|
|
27
|
+
'no-await-in-loop': no_await_in_loop_1.noAwaitInLoop,
|
|
28
|
+
// Categorized names
|
|
29
|
+
'error-handling/no-unhandled-promise': no_unhandled_promise_1.noUnhandledPromise,
|
|
30
|
+
'error-handling/no-silent-errors': no_silent_errors_1.noSilentErrors,
|
|
31
|
+
'error-handling/no-missing-error-context': no_missing_error_context_1.noMissingErrorContext,
|
|
32
|
+
'error-handling/error-message': error_message_1.errorMessage,
|
|
33
|
+
'reliability/no-missing-null-checks': no_missing_null_checks_1.noMissingNullChecks,
|
|
34
|
+
'reliability/no-unsafe-type-narrowing': no_unsafe_type_narrowing_1.noUnsafeTypeNarrowing,
|
|
35
|
+
'reliability/require-network-timeout': require_network_timeout_1.requireNetworkTimeout,
|
|
36
|
+
'reliability/no-await-in-loop': no_await_in_loop_1.noAwaitInLoop,
|
|
37
|
+
};
|
|
38
|
+
exports.plugin = {
|
|
39
|
+
meta: {
|
|
40
|
+
name: 'eslint-plugin-reliability',
|
|
41
|
+
version: '1.0.0',
|
|
42
|
+
},
|
|
43
|
+
rules: exports.rules,
|
|
44
|
+
};
|
|
45
|
+
exports.configs = {
|
|
46
|
+
recommended: {
|
|
47
|
+
plugins: {
|
|
48
|
+
'@interlace/reliability': exports.plugin,
|
|
49
|
+
},
|
|
50
|
+
rules: {
|
|
51
|
+
'@interlace/reliability/error-handling/no-silent-errors': 'warn',
|
|
52
|
+
'@interlace/reliability/reliability/no-missing-null-checks': 'warn',
|
|
53
|
+
'@interlace/reliability/reliability/require-network-timeout': 'error',
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
exports.default = exports.plugin;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function eslintPluginReliability(): string;
|
|
@@ -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
|
+
* ESLint Rule: error-message
|
|
8
|
+
* Enforces providing a message when creating built-in Error objects
|
|
9
|
+
*/
|
|
10
|
+
import type { TSESLint } from '@interlace/eslint-devkit';
|
|
11
|
+
type MessageIds = 'missingErrorMessage' | 'addErrorMessage';
|
|
12
|
+
export interface Options {
|
|
13
|
+
/** Allow Error() without message (not recommended) */
|
|
14
|
+
allowEmptyCatch?: boolean;
|
|
15
|
+
}
|
|
16
|
+
type RuleOptions = [Options?];
|
|
17
|
+
export declare const errorMessage: TSESLint.RuleModule<MessageIds, RuleOptions, unknown, TSESLint.RuleListener> & {
|
|
18
|
+
name: string;
|
|
19
|
+
};
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,146 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.errorMessage = void 0;
|
|
9
|
+
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
10
|
+
const eslint_devkit_2 = require("@interlace/eslint-devkit");
|
|
11
|
+
exports.errorMessage = (0, eslint_devkit_1.createRule)({
|
|
12
|
+
name: 'error-message',
|
|
13
|
+
meta: {
|
|
14
|
+
type: 'problem',
|
|
15
|
+
docs: {
|
|
16
|
+
description: 'Enforce providing a message when creating built-in Error objects for better debugging',
|
|
17
|
+
},
|
|
18
|
+
hasSuggestions: true,
|
|
19
|
+
messages: {
|
|
20
|
+
missingErrorMessage: (0, eslint_devkit_2.formatLLMMessage)({
|
|
21
|
+
icon: eslint_devkit_2.MessageIcons.INFO,
|
|
22
|
+
issueName: 'Missing Error Message',
|
|
23
|
+
description: 'Error constructor called without message parameter',
|
|
24
|
+
severity: 'HIGH',
|
|
25
|
+
fix: 'Add descriptive error message: new Error("description")',
|
|
26
|
+
documentationLink: 'https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/error-message.md',
|
|
27
|
+
}),
|
|
28
|
+
addErrorMessage: (0, eslint_devkit_2.formatLLMMessage)({
|
|
29
|
+
icon: eslint_devkit_2.MessageIcons.WARNING,
|
|
30
|
+
issueName: 'Add Error Message',
|
|
31
|
+
description: 'Add descriptive error message to constructor',
|
|
32
|
+
severity: 'HIGH',
|
|
33
|
+
fix: 'Add descriptive error message: new Error("description")',
|
|
34
|
+
documentationLink: 'https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/error-message.md',
|
|
35
|
+
}),
|
|
36
|
+
},
|
|
37
|
+
schema: [
|
|
38
|
+
{
|
|
39
|
+
type: 'object',
|
|
40
|
+
properties: {
|
|
41
|
+
allowEmptyCatch: {
|
|
42
|
+
type: 'boolean',
|
|
43
|
+
default: false,
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
additionalProperties: false,
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
defaultOptions: [{ allowEmptyCatch: false }],
|
|
51
|
+
create(context) {
|
|
52
|
+
const [options] = context.options;
|
|
53
|
+
const { allowEmptyCatch = false } = options || {};
|
|
54
|
+
// Built-in Error constructors that should have messages
|
|
55
|
+
const errorConstructors = new Set([
|
|
56
|
+
'Error',
|
|
57
|
+
'TypeError',
|
|
58
|
+
'ReferenceError',
|
|
59
|
+
'SyntaxError',
|
|
60
|
+
'RangeError',
|
|
61
|
+
'EvalError',
|
|
62
|
+
'URIError',
|
|
63
|
+
]);
|
|
64
|
+
function isErrorConstructor(name) {
|
|
65
|
+
return errorConstructors.has(name);
|
|
66
|
+
}
|
|
67
|
+
function hasMessageArgument(node) {
|
|
68
|
+
// Check if there are arguments
|
|
69
|
+
if (!node.arguments || node.arguments.length === 0) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
// Check if first argument is a non-empty string or expression
|
|
73
|
+
const firstArg = node.arguments[0];
|
|
74
|
+
if (firstArg.type === 'Literal') {
|
|
75
|
+
// Allow non-empty strings
|
|
76
|
+
return (typeof firstArg.value === 'string' && firstArg.value.trim().length > 0);
|
|
77
|
+
}
|
|
78
|
+
// Allow any expression (variable, function call, etc.) as it might be dynamic
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
function isInCatchClause(node) {
|
|
82
|
+
let current = node;
|
|
83
|
+
while (current) {
|
|
84
|
+
if (current.type === 'CatchClause') {
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
current = current.parent;
|
|
88
|
+
}
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
function checkErrorCreation(node) {
|
|
92
|
+
let constructorName = null;
|
|
93
|
+
if (node.type === 'NewExpression') {
|
|
94
|
+
// new Error(...)
|
|
95
|
+
if (node.callee.type === 'Identifier') {
|
|
96
|
+
constructorName = node.callee.name;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
else if (node.type === 'CallExpression') {
|
|
100
|
+
// Error(...) - function call style
|
|
101
|
+
if (node.callee.type === 'Identifier') {
|
|
102
|
+
constructorName = node.callee.name;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (!constructorName || !isErrorConstructor(constructorName)) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
// Allow empty catch if option is enabled
|
|
109
|
+
if (allowEmptyCatch && isInCatchClause(node)) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
// Check if message is provided
|
|
113
|
+
if (!hasMessageArgument(node)) {
|
|
114
|
+
context.report({
|
|
115
|
+
node,
|
|
116
|
+
messageId: 'missingErrorMessage',
|
|
117
|
+
data: {
|
|
118
|
+
constructor: constructorName,
|
|
119
|
+
},
|
|
120
|
+
suggest: [
|
|
121
|
+
{
|
|
122
|
+
messageId: 'addErrorMessage',
|
|
123
|
+
data: { constructor: constructorName },
|
|
124
|
+
fix(fixer) {
|
|
125
|
+
if (node.arguments.length === 0) {
|
|
126
|
+
// No arguments: replace the empty parentheses with ("Error message")
|
|
127
|
+
const parenStart = node.callee.range[1];
|
|
128
|
+
const parenEnd = node.range[1];
|
|
129
|
+
return fixer.replaceTextRange([parenStart, parenEnd], '("Error message")');
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
// Has arguments: replace the first argument with "Error message"
|
|
133
|
+
return fixer.replaceText(node.arguments[0], '"Error message"');
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
NewExpression: checkErrorCreation,
|
|
143
|
+
CallExpression: checkErrorCreation,
|
|
144
|
+
};
|
|
145
|
+
},
|
|
146
|
+
});
|