eslint-plugin-reliability 3.1.7 → 3.1.9
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 +4 -2
- package/package.json +5 -17
- package/src/index.d.ts +15 -196
- package/src/index.js +1 -9
- package/src/oxlint.d.ts +17 -20
- package/src/oxlint.js +0 -18
- package/src/rules/error-handling/error-message.js +0 -17
- package/src/rules/error-handling/no-missing-error-context.js +1 -34
- package/src/rules/error-handling/no-silent-errors.js +1 -21
- package/src/rules/error-handling/no-unhandled-promise.js +3 -147
- package/src/rules/reliability/no-await-in-loop.js +0 -18
- package/src/rules/reliability/no-jsdoc-terminator-in-example.js +2 -30
- package/src/rules/reliability/no-missing-null-checks.js +4 -137
- package/src/rules/reliability/no-unsafe-type-narrowing.js +3 -25
- package/src/rules/reliability/require-network-timeout.js +0 -14
- package/CHANGELOG.md +0 -164
- package/src/lib/eslint-plugin-reliability.d.ts +0 -1
- package/src/rules/error-handling/error-message.d.ts +0 -20
- package/src/rules/error-handling/no-missing-error-context.d.ts +0 -26
- package/src/rules/error-handling/no-silent-errors.d.ts +0 -24
- package/src/rules/error-handling/no-unhandled-promise.d.ts +0 -49
- package/src/rules/reliability/no-await-in-loop.d.ts +0 -24
- package/src/rules/reliability/no-jsdoc-terminator-in-example.d.ts +0 -35
- package/src/rules/reliability/no-missing-null-checks.d.ts +0 -35
- package/src/rules/reliability/no-unsafe-type-narrowing.d.ts +0 -24
- package/src/rules/reliability/require-network-timeout.d.ts +0 -12
|
@@ -1,75 +1,37 @@
|
|
|
1
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
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
3
|
exports.noMissingNullChecks = void 0;
|
|
9
4
|
exports.hasNullCheck = hasNullCheck;
|
|
10
5
|
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
11
6
|
const eslint_devkit_2 = require("@interlace/eslint-devkit");
|
|
12
|
-
/**
|
|
13
|
-
* Globals and built-ins that are never null/undefined. Property access on
|
|
14
|
-
* these doesn't need a null check.
|
|
15
|
-
*/
|
|
16
|
-
// Built-in namespaces that are never null AND aren't typically mutated. We
|
|
17
|
-
// deliberately exclude `globalThis`, `window`, `self`, `top`, `parent`, and
|
|
18
|
-
// `document` — those are mutable and `globalThis.appState = ...` is a real
|
|
19
|
-
// "global state mutation" antipattern that other rules want to catch.
|
|
20
|
-
// Read-only namespaces (Math, JSON, …) and library singletons are safe to
|
|
21
|
-
// exempt because their property access is idempotent.
|
|
22
7
|
const NEVER_NULL_GLOBALS = new Set([
|
|
23
|
-
// Read-only Node/V8 magics
|
|
24
8
|
'console', 'process', 'Buffer', '__dirname', '__filename', 'module', 'exports', 'require',
|
|
25
9
|
'navigator', 'location', 'history',
|
|
26
|
-
// Built-in objects (read-only namespaces)
|
|
27
10
|
'Math', 'JSON', 'Object', 'Array', 'Number', 'String', 'Boolean', 'Date',
|
|
28
11
|
'RegExp', 'Promise', 'Symbol', 'Map', 'Set', 'WeakMap', 'WeakSet',
|
|
29
12
|
'Proxy', 'Reflect', 'Intl', 'BigInt', 'WebAssembly', 'Atomics',
|
|
30
13
|
'URL', 'URLSearchParams', 'TextEncoder', 'TextDecoder',
|
|
31
14
|
'AbortController', 'AbortSignal', 'EventTarget', 'Event', 'CustomEvent',
|
|
32
15
|
'FormData', 'Blob', 'File', 'FileReader', 'Headers', 'Request', 'Response',
|
|
33
|
-
// Error classes
|
|
34
16
|
'Error', 'TypeError', 'RangeError', 'SyntaxError', 'URIError',
|
|
35
17
|
'EvalError', 'ReferenceError', 'AggregateError',
|
|
36
|
-
// Common library / framework names
|
|
37
18
|
'fetch', 'crypto', 'performance', 'queueMicrotask',
|
|
38
19
|
'setTimeout', 'clearTimeout', 'setInterval', 'clearInterval',
|
|
39
20
|
'setImmediate', 'clearImmediate', 'requestAnimationFrame', 'cancelAnimationFrame',
|
|
40
|
-
// Common loggers (used as singletons)
|
|
41
21
|
'logger', 'log', 'winston', 'pino', 'bunyan',
|
|
42
22
|
]);
|
|
43
|
-
/**
|
|
44
|
-
* Returns true if the identifier resolves to:
|
|
45
|
-
* - A NEVER_NULL_GLOBALS entry (built-in / known singleton)
|
|
46
|
-
* - A catch-clause parameter (`catch (e) { e.message }` — never null)
|
|
47
|
-
* - A variable initialized by `new X(...)` (constructor result is never null)
|
|
48
|
-
* - A top-level import (`import x from 'y'` — never null)
|
|
49
|
-
*
|
|
50
|
-
* For these, the rule should not demand a null check.
|
|
51
|
-
*/
|
|
52
23
|
function isProvablyNonNullableIdentifier(ident, scope) {
|
|
53
24
|
if (NEVER_NULL_GLOBALS.has(ident.name))
|
|
54
25
|
return true;
|
|
55
|
-
// Walk scope chain to find the resolved variable
|
|
56
26
|
let s = scope;
|
|
57
27
|
while (s) {
|
|
58
28
|
const variable = s.variables.find((v) => v.name === ident.name);
|
|
59
29
|
if (variable) {
|
|
60
30
|
for (const def of variable.defs) {
|
|
61
|
-
// catch (e) { ... }
|
|
62
31
|
if (def.type === 'CatchClause')
|
|
63
32
|
return true;
|
|
64
|
-
// import x from 'y' / import { x } from 'y'
|
|
65
33
|
if (def.type === 'ImportBinding')
|
|
66
34
|
return true;
|
|
67
|
-
// const x = new Foo(...) / array literal / object literal / primitive
|
|
68
|
-
// / class declaration / `await fetch(...)` / template literal — these
|
|
69
|
-
// initializers cannot produce null/undefined under normal control flow.
|
|
70
|
-
// Adding them closes the bulk of the ILB-Arena-Quality FPs without
|
|
71
|
-
// sacrificing real CWE-476 detection (genuine null-deref risks come
|
|
72
|
-
// from optional/maybe lookups, not from `const x = []`).
|
|
73
35
|
if (def.type === 'Variable' && def.node?.type === 'VariableDeclarator') {
|
|
74
36
|
const init = def.node.init;
|
|
75
37
|
if (!init)
|
|
@@ -84,21 +46,13 @@ function isProvablyNonNullableIdentifier(ident, scope) {
|
|
|
84
46
|
return true;
|
|
85
47
|
if (init.type === 'ClassExpression')
|
|
86
48
|
return true;
|
|
87
|
-
// Primitive literals (string / number / boolean / regex) — never null.
|
|
88
|
-
// Skip the `null` literal itself — that IS a null and the rule
|
|
89
|
-
// should let other rules complain about it.
|
|
90
49
|
if (init.type === 'Literal' && init.value !== null)
|
|
91
50
|
return true;
|
|
92
|
-
// `await fetch(url)` / `await fetch(url, opts)` — the WHATWG fetch
|
|
93
|
-
// contract guarantees a Response on resolution; null comes only
|
|
94
|
-
// through rejection, which throws past this assignment.
|
|
95
51
|
if (init.type === 'AwaitExpression' &&
|
|
96
52
|
init.argument.type === 'CallExpression' &&
|
|
97
53
|
init.argument.callee.type === 'Identifier' &&
|
|
98
54
|
init.argument.callee.name === 'fetch')
|
|
99
55
|
return true;
|
|
100
|
-
// `JSON.parse(...)` returns a value; typically non-null. Same for
|
|
101
|
-
// common Object/Array static methods.
|
|
102
56
|
if (init.type === 'CallExpression' &&
|
|
103
57
|
init.callee.type === 'MemberExpression' &&
|
|
104
58
|
init.callee.object.type === 'Identifier' &&
|
|
@@ -106,18 +60,10 @@ function isProvablyNonNullableIdentifier(ident, scope) {
|
|
|
106
60
|
init.callee.object.name === 'JSON'))
|
|
107
61
|
return true;
|
|
108
62
|
}
|
|
109
|
-
// function f(...) — function declaration name is never null
|
|
110
63
|
if (def.type === 'FunctionName')
|
|
111
64
|
return true;
|
|
112
|
-
// class C {} — class declaration name is never null
|
|
113
65
|
if (def.type === 'ClassName')
|
|
114
66
|
return true;
|
|
115
|
-
// Function/method parameters — without type information we cannot
|
|
116
|
-
// tell `(x: T)` from `(x: T | null)`, and firing on every param-deref
|
|
117
|
-
// is the dominant FP source on real codebases (53 → 44 → most-of-rest).
|
|
118
|
-
// The contract is the caller's responsibility; treat params as
|
|
119
|
-
// non-nullable by default. Type-aware analysis (TS) is the right
|
|
120
|
-
// tool for real param-nullability detection.
|
|
121
67
|
if (def.type === 'Parameter')
|
|
122
68
|
return true;
|
|
123
69
|
}
|
|
@@ -127,32 +73,18 @@ function isProvablyNonNullableIdentifier(ident, scope) {
|
|
|
127
73
|
}
|
|
128
74
|
return false;
|
|
129
75
|
}
|
|
130
|
-
/**
|
|
131
|
-
* Check if property access has null/undefined check
|
|
132
|
-
*
|
|
133
|
-
* Exported for direct Layer-2 unit testing: the `node.optional` and
|
|
134
|
-
* ChainExpression early returns are defensive duplicates of checks both
|
|
135
|
-
* callers perform before invoking this helper, so they are unreachable
|
|
136
|
-
* through the rule's listeners.
|
|
137
|
-
*/
|
|
138
76
|
function hasNullCheck(node, sourceCode) {
|
|
139
|
-
// Check if node itself uses optional chaining
|
|
140
77
|
if (node.optional) {
|
|
141
78
|
return true;
|
|
142
79
|
}
|
|
143
|
-
// Check if parent is optional chaining
|
|
144
80
|
const parent = node.parent;
|
|
145
81
|
if (parent && parent.type === 'ChainExpression') {
|
|
146
82
|
return true;
|
|
147
83
|
}
|
|
148
|
-
// Check if used with nullish coalescing
|
|
149
84
|
if (usesNullishCoalescing(node)) {
|
|
150
85
|
return true;
|
|
151
86
|
}
|
|
152
87
|
const objectText = sourceCode.getText(node.object);
|
|
153
|
-
// Short-circuit AND: `obj && obj.prop` — the right side of && runs only
|
|
154
|
-
// when the left side is truthy, so obj is guaranteed non-null here.
|
|
155
|
-
// Walk up one level (CallExpression wraps MemberExpression for `obj && obj.method()`)
|
|
156
88
|
const immediateParent = parent;
|
|
157
89
|
const nodeOrCall = immediateParent?.type === 'CallExpression' &&
|
|
158
90
|
immediateParent.callee === node
|
|
@@ -167,8 +99,6 @@ function hasNullCheck(node, sourceCode) {
|
|
|
167
99
|
if (leftText === objectText || leftText.endsWith(objectText))
|
|
168
100
|
return true;
|
|
169
101
|
}
|
|
170
|
-
// Ternary consequent: `obj ? obj.prop : fallback` — the test being truthy
|
|
171
|
-
// guarantees obj is non-null before the consequent evaluates.
|
|
172
102
|
let cur = node;
|
|
173
103
|
for (let depth = 0; depth < 8; depth++) {
|
|
174
104
|
const p = cur.parent;
|
|
@@ -182,17 +112,12 @@ function hasNullCheck(node, sourceCode) {
|
|
|
182
112
|
}
|
|
183
113
|
cur = p;
|
|
184
114
|
}
|
|
185
|
-
// Explicit null/truthy check in enclosing if statement
|
|
186
115
|
if (hasExplicitNullCheck(node, sourceCode)) {
|
|
187
116
|
return true;
|
|
188
117
|
}
|
|
189
118
|
return false;
|
|
190
119
|
}
|
|
191
|
-
/**
|
|
192
|
-
* Check for explicit null checks in if statements
|
|
193
|
-
*/
|
|
194
120
|
function hasExplicitNullCheck(node, sourceCode) {
|
|
195
|
-
// Walk up the AST to find if statements
|
|
196
121
|
let current = node;
|
|
197
122
|
let depth = 0;
|
|
198
123
|
const maxDepth = 10;
|
|
@@ -200,7 +125,6 @@ function hasExplicitNullCheck(node, sourceCode) {
|
|
|
200
125
|
const parent = current
|
|
201
126
|
.parent;
|
|
202
127
|
if (parent && parent.type === 'IfStatement') {
|
|
203
|
-
// Check if the test condition contains a null check for our object
|
|
204
128
|
const test = parent.test;
|
|
205
129
|
if (isNullCheckForObject(test, node.object, sourceCode)) {
|
|
206
130
|
return true;
|
|
@@ -211,21 +135,14 @@ function hasExplicitNullCheck(node, sourceCode) {
|
|
|
211
135
|
}
|
|
212
136
|
return false;
|
|
213
137
|
}
|
|
214
|
-
/**
|
|
215
|
-
* Check if a test expression is a null check for a specific object
|
|
216
|
-
*/
|
|
217
138
|
function isNullCheckForObject(test, object, sourceCode) {
|
|
218
139
|
const objectText = sourceCode.getText(object);
|
|
219
|
-
// Truthy check: `if (obj)` or `if (obj.prop)` — direct truthy guard proves
|
|
220
|
-
// non-null. Also covers nested chains: `if (response) { response.data.items }`
|
|
221
|
-
// because checking the root (response) implicitly protects the full chain.
|
|
222
140
|
if (test.type === 'Identifier' || test.type === 'MemberExpression') {
|
|
223
141
|
const testText = sourceCode.getText(test);
|
|
224
142
|
if (testText === objectText || objectText.startsWith(testText + '.')) {
|
|
225
143
|
return true;
|
|
226
144
|
}
|
|
227
145
|
}
|
|
228
|
-
// Handle binary expressions like obj !== null, obj != undefined
|
|
229
146
|
if (test.type === 'BinaryExpression') {
|
|
230
147
|
const { left, right, operator } = test;
|
|
231
148
|
if (operator === '!==' ||
|
|
@@ -242,18 +159,12 @@ function isNullCheckForObject(test, object, sourceCode) {
|
|
|
242
159
|
}
|
|
243
160
|
}
|
|
244
161
|
}
|
|
245
|
-
// Handle logical expressions like obj !== null && obj !== undefined
|
|
246
162
|
if (test.type === 'LogicalExpression') {
|
|
247
163
|
return (isNullCheckForObject(test.left, object, sourceCode) ||
|
|
248
164
|
isNullCheckForObject(test.right, object, sourceCode));
|
|
249
165
|
}
|
|
250
|
-
// Unary negation: `if (!obj)` is a FALSY guard — only safe when paired
|
|
251
|
-
// with early return, which requires control-flow analysis. Skip for now.
|
|
252
166
|
return false;
|
|
253
167
|
}
|
|
254
|
-
/**
|
|
255
|
-
* Check if expression uses nullish coalescing
|
|
256
|
-
*/
|
|
257
168
|
function usesNullishCoalescing(node) {
|
|
258
169
|
let current = node;
|
|
259
170
|
let depth = 0;
|
|
@@ -343,77 +254,48 @@ exports.noMissingNullChecks = (0, eslint_devkit_2.createRule)({
|
|
|
343
254
|
},
|
|
344
255
|
],
|
|
345
256
|
create(context, [options = {}]) {
|
|
346
|
-
const { ignoreInTests = true,
|
|
347
|
-
// requireExplicitChecks = false, // Not used
|
|
348
|
-
} = options || {};
|
|
257
|
+
const { ignoreInTests = true, } = options || {};
|
|
349
258
|
const filename = context.filename;
|
|
350
259
|
const isTestFile = ignoreInTests && /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);
|
|
351
260
|
if (isTestFile) {
|
|
352
261
|
return {};
|
|
353
262
|
}
|
|
354
263
|
const sourceCode = context.sourceCode;
|
|
355
|
-
// Track reported MemberExpression nodes to prevent duplicate reports
|
|
356
|
-
// Key format: "start-end" from node.range
|
|
357
264
|
const reportedMemberExpressions = new Set();
|
|
358
|
-
/**
|
|
359
|
-
* Get a unique key for a MemberExpression node to track if it's been reported
|
|
360
|
-
*/
|
|
361
265
|
function getMemberExpressionKey(node) {
|
|
362
|
-
// Use the node's range for a unique identifier
|
|
363
|
-
// Range is [start, end] character positions in the source
|
|
364
266
|
if (node.range && Array.isArray(node.range) && node.range.length >= 2) {
|
|
365
267
|
return `me-${node.range[0]}-${node.range[1]}`;
|
|
366
268
|
}
|
|
367
|
-
// Fallback: use location if range is not available
|
|
368
269
|
const loc = node
|
|
369
270
|
.loc;
|
|
370
271
|
if (loc && loc.start) {
|
|
371
272
|
return `me-${loc.start.line}-${loc.start.column}-${loc.end?.line || loc.start.line}-${loc.end?.column || loc.start.column}`;
|
|
372
273
|
}
|
|
373
|
-
// Last resort: use a hash of the node structure
|
|
374
274
|
return `me-${JSON.stringify(node).slice(0, 50)}`;
|
|
375
275
|
}
|
|
376
|
-
/**
|
|
377
|
-
* Check member expressions for null safety
|
|
378
|
-
*/
|
|
379
276
|
function checkMemberExpression(node) {
|
|
380
|
-
// Skip if already using optional chaining
|
|
381
277
|
if (node.optional) {
|
|
382
278
|
return;
|
|
383
279
|
}
|
|
384
|
-
// Skip if parent is optional chaining
|
|
385
280
|
const parent = node
|
|
386
281
|
.parent;
|
|
387
282
|
if (parent && parent.type === 'ChainExpression') {
|
|
388
283
|
return;
|
|
389
284
|
}
|
|
390
|
-
// Only report on the "deepest" member expression in a chain
|
|
391
|
-
// If this member expression is the object of another member expression,
|
|
392
|
-
// don't report it yet - let the deepest one be reported
|
|
393
285
|
if (parent &&
|
|
394
286
|
parent.type === 'MemberExpression' &&
|
|
395
287
|
parent.object === node) {
|
|
396
|
-
return;
|
|
288
|
+
return;
|
|
397
289
|
}
|
|
398
|
-
// Check if object might be null/undefined
|
|
399
|
-
// Check for Identifier or nested MemberExpression
|
|
400
290
|
const objectNode = node.object;
|
|
401
291
|
let shouldCheck = false;
|
|
402
292
|
if (objectNode.type === 'Identifier') {
|
|
403
|
-
// Skip identifiers that resolve to globals, catch params, imports,
|
|
404
|
-
// function declarations, or `new X()` results — these are never null.
|
|
405
293
|
if (isProvablyNonNullableIdentifier(objectNode, sourceCode.getScope(node))) {
|
|
406
294
|
return;
|
|
407
295
|
}
|
|
408
296
|
shouldCheck = true;
|
|
409
297
|
}
|
|
410
298
|
else if (objectNode.type === 'MemberExpression') {
|
|
411
|
-
// Nested member expressions like value.nested.deep — only fire on
|
|
412
|
-
// the deepest, where the leaf identifier matters most. Skip if the
|
|
413
|
-
// base of the chain is a known-non-null global (`console.log`,
|
|
414
|
-
// `JSON.stringify`, etc.) or is a `this`/`this.#field` chain — `this`
|
|
415
|
-
// inside a method is the class instance (never null), and private
|
|
416
|
-
// fields are always defined when accessed from inside the class.
|
|
417
299
|
let base = objectNode;
|
|
418
300
|
while (base.type === 'MemberExpression') {
|
|
419
301
|
base = base.object;
|
|
@@ -429,7 +311,7 @@ exports.noMissingNullChecks = (0, eslint_devkit_2.createRule)({
|
|
|
429
311
|
if (shouldCheck && !hasNullCheck(node, sourceCode)) {
|
|
430
312
|
const nodeKey = getMemberExpressionKey(node);
|
|
431
313
|
if (reportedMemberExpressions.has(nodeKey)) {
|
|
432
|
-
return;
|
|
314
|
+
return;
|
|
433
315
|
}
|
|
434
316
|
try {
|
|
435
317
|
reportedMemberExpressions.add(nodeKey);
|
|
@@ -453,37 +335,23 @@ exports.noMissingNullChecks = (0, eslint_devkit_2.createRule)({
|
|
|
453
335
|
});
|
|
454
336
|
}
|
|
455
337
|
catch {
|
|
456
|
-
// Silently skip if there's an error
|
|
457
338
|
return;
|
|
458
339
|
}
|
|
459
340
|
}
|
|
460
341
|
}
|
|
461
|
-
/**
|
|
462
|
-
* Check call expressions for null safety (e.g., obj.method())
|
|
463
|
-
* Only check if it's an actual method call, not just a property access
|
|
464
|
-
*/
|
|
465
342
|
function checkCallExpression(node) {
|
|
466
|
-
// Ensure this is actually a CallExpression (not just a MemberExpression)
|
|
467
343
|
if (node.type !== 'CallExpression') {
|
|
468
344
|
return;
|
|
469
345
|
}
|
|
470
|
-
// Only check if callee is a member expression (e.g., obj.method())
|
|
471
|
-
// This ensures we only check method calls, not property accesses
|
|
472
346
|
if (node.callee.type === 'MemberExpression') {
|
|
473
347
|
const memberExpr = node.callee;
|
|
474
|
-
// Skip if already using optional chaining
|
|
475
348
|
if (memberExpr.optional) {
|
|
476
349
|
return;
|
|
477
350
|
}
|
|
478
|
-
// Skip if parent is optional chaining
|
|
479
351
|
const parent = memberExpr.parent;
|
|
480
352
|
if (parent && parent.type === 'ChainExpression') {
|
|
481
353
|
return;
|
|
482
354
|
}
|
|
483
|
-
// Skip if this MemberExpression was already reported by checkMemberExpression
|
|
484
|
-
// We can't easily check this, so we'll rely on the fact that CallExpression
|
|
485
|
-
// is only triggered for actual method calls, not property accesses
|
|
486
|
-
// Check if object might be null/undefined
|
|
487
355
|
const objectNode = memberExpr.object;
|
|
488
356
|
let shouldCheck = false;
|
|
489
357
|
if (objectNode.type === 'Identifier') {
|
|
@@ -508,7 +376,7 @@ exports.noMissingNullChecks = (0, eslint_devkit_2.createRule)({
|
|
|
508
376
|
if (shouldCheck && !hasNullCheck(memberExpr, sourceCode)) {
|
|
509
377
|
const nodeKey = getMemberExpressionKey(memberExpr);
|
|
510
378
|
if (reportedMemberExpressions.has(nodeKey)) {
|
|
511
|
-
return;
|
|
379
|
+
return;
|
|
512
380
|
}
|
|
513
381
|
try {
|
|
514
382
|
reportedMemberExpressions.add(nodeKey);
|
|
@@ -532,7 +400,6 @@ exports.noMissingNullChecks = (0, eslint_devkit_2.createRule)({
|
|
|
532
400
|
});
|
|
533
401
|
}
|
|
534
402
|
catch {
|
|
535
|
-
// Silently skip if there's an error
|
|
536
403
|
return;
|
|
537
404
|
}
|
|
538
405
|
}
|
|
@@ -1,43 +1,26 @@
|
|
|
1
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
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
3
|
exports.noUnsafeTypeNarrowing = void 0;
|
|
9
4
|
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
10
5
|
const eslint_devkit_2 = require("@interlace/eslint-devkit");
|
|
11
|
-
/**
|
|
12
|
-
* Check if type assertion is unsafe (as unknown as T)
|
|
13
|
-
*/
|
|
14
6
|
function isUnsafeTypeAssertion(node) {
|
|
15
|
-
// Check for double assertion pattern: as unknown as T or as any as T
|
|
16
7
|
if (node.expression.type === 'TSAsExpression') {
|
|
17
8
|
const innerAssertion = node.expression;
|
|
18
|
-
// Check if inner assertion is to 'unknown'
|
|
19
9
|
if (innerAssertion.typeAnnotation.type === 'TSUnknownKeyword') {
|
|
20
|
-
return true;
|
|
10
|
+
return true;
|
|
21
11
|
}
|
|
22
|
-
// Also check for 'any' type
|
|
23
12
|
if (innerAssertion.typeAnnotation.type === 'TSAnyKeyword') {
|
|
24
|
-
return true;
|
|
13
|
+
return true;
|
|
25
14
|
}
|
|
26
15
|
}
|
|
27
|
-
// Don't flag direct assertions to unknown/any - those are handled by TSC
|
|
28
|
-
// Only flag the double assertion pattern which bypasses type safety
|
|
29
16
|
return false;
|
|
30
17
|
}
|
|
31
|
-
/**
|
|
32
|
-
* Check if type assertion has explanatory comment
|
|
33
|
-
*/
|
|
34
18
|
function hasExplanatoryComment(node, sourceCode) {
|
|
35
19
|
const comments = sourceCode.getAllComments();
|
|
36
20
|
const nodeStart = node.loc?.start;
|
|
37
21
|
if (!nodeStart || !comments.length) {
|
|
38
22
|
return false;
|
|
39
23
|
}
|
|
40
|
-
// Look for explanatory comments near the type assertion
|
|
41
24
|
const explanatoryPatterns = [
|
|
42
25
|
/type.?guard/i,
|
|
43
26
|
/validated/i,
|
|
@@ -53,7 +36,6 @@ function hasExplanatoryComment(node, sourceCode) {
|
|
|
53
36
|
/todo/i,
|
|
54
37
|
/fixme/i,
|
|
55
38
|
];
|
|
56
|
-
// Check comments before the assertion (within 1 line)
|
|
57
39
|
for (const comment of comments) {
|
|
58
40
|
if (comment.loc && nodeStart.line - comment.loc.end.line <= 1) {
|
|
59
41
|
const commentText = comment.value.toLowerCase();
|
|
@@ -140,14 +122,10 @@ exports.noUnsafeTypeNarrowing = (0, eslint_devkit_2.createRule)({
|
|
|
140
122
|
return {};
|
|
141
123
|
}
|
|
142
124
|
const sourceCode = context.sourceCode;
|
|
143
|
-
/**
|
|
144
|
-
* Check type assertions
|
|
145
|
-
*/
|
|
146
125
|
function checkTypeAssertion(node) {
|
|
147
126
|
if (!isUnsafeTypeAssertion(node)) {
|
|
148
127
|
return;
|
|
149
128
|
}
|
|
150
|
-
// Check if comment explains the unsafe assertion
|
|
151
129
|
if (allowWithComment && hasExplanatoryComment(node, sourceCode)) {
|
|
152
130
|
return;
|
|
153
131
|
}
|
|
@@ -157,7 +135,7 @@ exports.noUnsafeTypeNarrowing = (0, eslint_devkit_2.createRule)({
|
|
|
157
135
|
suggest: [
|
|
158
136
|
{
|
|
159
137
|
messageId: 'useTypeGuard',
|
|
160
|
-
fix: () => null,
|
|
138
|
+
fix: () => null,
|
|
161
139
|
},
|
|
162
140
|
{
|
|
163
141
|
messageId: 'useProperNarrowing',
|
|
@@ -1,16 +1,6 @@
|
|
|
1
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
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
3
|
exports.requireNetworkTimeout = void 0;
|
|
9
|
-
/**
|
|
10
|
-
* @fileoverview Require timeout limits for network requests
|
|
11
|
-
* @see https://owasp.org/www-project-mobile-top-10/
|
|
12
|
-
* @see https://cwe.mitre.org/data/definitions/770.html
|
|
13
|
-
*/
|
|
14
4
|
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
15
5
|
exports.requireNetworkTimeout = (0, eslint_devkit_1.createRule)({
|
|
16
6
|
name: 'require-network-timeout',
|
|
@@ -46,10 +36,6 @@ exports.requireNetworkTimeout = (0, eslint_devkit_1.createRule)({
|
|
|
46
36
|
callee.object.name === 'axios';
|
|
47
37
|
if (isFetch || isAxios) {
|
|
48
38
|
const optionsArg = node.arguments[1];
|
|
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
39
|
const hasBound = optionsArg?.type === eslint_devkit_1.AST_NODE_TYPES.ObjectExpression &&
|
|
54
40
|
optionsArg.properties.some((p) => p.type === eslint_devkit_1.AST_NODE_TYPES.Property &&
|
|
55
41
|
p.key.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
|
package/CHANGELOG.md
DELETED
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
## [3.1.3] - 2026-05-03
|
|
2
|
-
|
|
3
|
-
## 3.1.7
|
|
4
|
-
|
|
5
|
-
### Patch Changes
|
|
6
|
-
|
|
7
|
-
- [#294](https://github.com/ofri-peretz/eslint/pull/294) [`659f6dc`](https://github.com/ofri-peretz/eslint/commit/659f6dc0181b03b675f72b5949fcf123dd066358) Thanks [@ofri-peretz](https://github.com/ofri-peretz)! - Rewrite `description` and `keywords` on every published package for npm search discovery. npm ranks on name, description, and keywords, and the registry only picks up these fields at publish — so this is metadata-only and takes effect for each package on its next release.
|
|
8
|
-
|
|
9
|
-
**Descriptions now lead with the search phrase.** Every one starts `ESLint plugin for <the thing you'd search>` instead of a brand-first or category-first framing, and names the concrete vulnerabilities the plugin actually detects. Three were corrected while doing so:
|
|
10
|
-
|
|
11
|
-
- `eslint-plugin-import-next` claimed "100x faster no-cycle detection". No 100x measurement exists: `CLAIMS.md` records **3.1x end-to-end** (8x in pure rule execution) on a 5,483-file React codebase, and the highest number in any benchmark result is 54.9x on the synthetic corpus. The description now states the real-codebase figure.
|
|
12
|
-
- `eslint-plugin-secure-coding` claimed SQL injection, XSS and CSRF coverage — none of which are its rules. It now names what it does detect: LDAP, XPath, XXE, GraphQL and template injection, unsafe deserialization, ReDoS, missing authentication, and PII in logs.
|
|
13
|
-
- `eslint-plugin-secure-coding` ("89 rules") and `eslint-plugin-react-a11y` ("37 rules") hard-coded rule counts that had drifted from reality. Counts are generated into `interlace-numbers.json`; hand-typed copies are removed rather than corrected.
|
|
14
|
-
|
|
15
|
-
**Keywords now match the vocabulary of the plugins that rank.** `eslint-plugin-security`, `eslint-plugin-jsx-a11y`, `eslint-plugin-n` and `eslint-plugin-import` all carry the `eslint` / `eslintplugin` / `eslint-plugin` trio — six of our packages were missing `eslintplugin`, and every one now carries all three plus `static-analysis`, `linting` and `code-quality`. Security plugins add `sast`, `appsec` and `vulnerability`; `node-security` and `secure-coding` also carry `nodesecurity`, the exact keyword `eslint-plugin-security` ranks on. Each plugin gained the CWE identifiers and attack names for what it detects (`cwe-78` command injection, `cwe-22` path traversal, `cwe-89` SQL injection, `cwe-79` XSS, `cwe-347` JWT algorithm confusion, `cwe-352` CSRF, `cwe-943` NoSQL injection), and `node-security` gained the crypto vocabulary it had been missing entirely despite absorbing the crypto rule set (`crypto`, `cryptography`, `weak-hash`, `md5`, `sha1`, `timing-attack`).
|
|
16
|
-
|
|
17
|
-
No rule behavior, exports, or configuration changes.
|
|
18
|
-
|
|
19
|
-
- Updated dependencies [[`e1cdf83`](https://github.com/ofri-peretz/eslint/commit/e1cdf83e3db761907f0ab06f7fc6c1f1da7513a5), [`659f6dc`](https://github.com/ofri-peretz/eslint/commit/659f6dc0181b03b675f72b5949fcf123dd066358)]:
|
|
20
|
-
- @interlace/eslint-devkit@1.4.3
|
|
21
|
-
|
|
22
|
-
## 3.1.6
|
|
23
|
-
|
|
24
|
-
### Patch Changes
|
|
25
|
-
|
|
26
|
-
- [#269](https://github.com/ofri-peretz/eslint/pull/269) [`7028fe2`](https://github.com/ofri-peretz/eslint/commit/7028fe2668a42266d831014184dcef70e73101ad) Thanks [@ofri-peretz](https://github.com/ofri-peretz)! - docs: dual-logo README header (Interlace mark + ESLint mark side by side) and closing Interlace footer — refreshes the README rendered on npmjs.com. No runtime changes.
|
|
27
|
-
|
|
28
|
-
- Updated dependencies [[`7028fe2`](https://github.com/ofri-peretz/eslint/commit/7028fe2668a42266d831014184dcef70e73101ad)]:
|
|
29
|
-
- @interlace/eslint-devkit@1.4.2
|
|
30
|
-
|
|
31
|
-
## 3.1.5
|
|
32
|
-
|
|
33
|
-
### Patch Changes
|
|
34
|
-
|
|
35
|
-
- [#252](https://github.com/ofri-peretz/eslint/pull/252) [`d67e395`](https://github.com/ofri-peretz/eslint/commit/d67e3953c2748ad36e6aebe0f24b1d04e518b4d0) Thanks [@ofri-peretz](https://github.com/ofri-peretz)! - Fix Codecov badge showing "unknown" — switch from flag to component URL format
|
|
36
|
-
|
|
37
|
-
## 3.1.4
|
|
38
|
-
|
|
39
|
-
### Patch Changes
|
|
40
|
-
|
|
41
|
-
- [#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
|
|
42
|
-
|
|
43
|
-
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:
|
|
44
|
-
- `browser-security/no-clickjacking`
|
|
45
|
-
- `import-next/first`, `named`, `no-barrel-import`, `no-import-module-exports`, `no-namespace`
|
|
46
|
-
- `node-security/no-buffer-overread`, `no-unsafe-dynamic-require`, `no-zip-slip`
|
|
47
|
-
- `react-features/react-no-inline-functions`
|
|
48
|
-
- `reliability/no-jsdoc-terminator-in-example` (uses `suggest`, not auto-fix; corrected to `hasSuggestions: true` only)
|
|
49
|
-
- `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`
|
|
50
|
-
|
|
51
|
-
- [#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
|
|
52
|
-
|
|
53
|
-
Rules that were recognized as null guards are now correctly identified as safe:
|
|
54
|
-
1. **Truthy if guard** — `if (obj) { obj.prop }` — direct truthy check proves non-null. Also covers chains: `if (response)` protects `response.data.items`.
|
|
55
|
-
2. **Short-circuit AND** — `obj && obj.prop` — right side of `&&` only runs when left is truthy.
|
|
56
|
-
3. **Ternary consequent** — `obj ? obj.prop : fallback` — truthy test guards the consequent.
|
|
57
|
-
|
|
58
|
-
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.
|
|
59
|
-
|
|
60
|
-
- Updated dependencies [[`736a5fe`](https://github.com/ofri-peretz/eslint/commit/736a5fed47e673f6157ea900b29fe2a54e4bc7df)]:
|
|
61
|
-
- @interlace/eslint-devkit@1.4.1
|
|
62
|
-
|
|
63
|
-
### Bug Fixes
|
|
64
|
-
|
|
65
|
-
- `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.
|
|
66
|
-
- `no-unhandled-promise`: refined detection (see source diff).
|
|
67
|
-
|
|
68
|
-
## 3.1.2 (2026-02-09)
|
|
69
|
-
|
|
70
|
-
This was a version bump only for eslint-plugin-reliability to align it with other projects, there were no code changes.
|
|
71
|
-
|
|
72
|
-
## [3.1.0] - 2026-02-08
|
|
73
|
-
|
|
74
|
-
This was a version bump only for eslint-plugin-reliability to align it with other projects, there were no code changes.
|
|
75
|
-
|
|
76
|
-
## [3.0.4] - 2026-02-08
|
|
77
|
-
|
|
78
|
-
### Bug Fixes
|
|
79
|
-
|
|
80
|
-
- align codecov component IDs with full package names ([2831b968](https://github.com/ofri-peretz/eslint/commit/2831b968))
|
|
81
|
-
|
|
82
|
-
### Documentation
|
|
83
|
-
|
|
84
|
-
- fix changelog header format across all packages ([c3a15082](https://github.com/ofri-peretz/eslint/commit/c3a15082))
|
|
85
|
-
|
|
86
|
-
### ❤️ Thank You
|
|
87
|
-
|
|
88
|
-
- Ofri Peretz
|
|
89
|
-
|
|
90
|
-
## [3.0.3] - 2026-02-06
|
|
91
|
-
|
|
92
|
-
### Bug Fixes
|
|
93
|
-
|
|
94
|
-
- align codecov component names and update docs components ([0a59a86c](https://github.com/ofri-peretz/eslint/commit/0a59a86c))
|
|
95
|
-
|
|
96
|
-
### ❤️ Thank You
|
|
97
|
-
|
|
98
|
-
- Ofri Peretz
|
|
99
|
-
|
|
100
|
-
## [3.0.2] - 2026-02-02
|
|
101
|
-
|
|
102
|
-
This was a version bump only for eslint-plugin-reliability to align it with other projects, there were no code changes.
|
|
103
|
-
|
|
104
|
-
# Changelog
|
|
105
|
-
|
|
106
|
-
All notable changes to `eslint-plugin-reliability` will be documented in this file.
|
|
107
|
-
|
|
108
|
-
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
109
|
-
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
110
|
-
|
|
111
|
-
### Documentation
|
|
112
|
-
|
|
113
|
-
- 📘 Launched new documentation site: [eslint.interlace.tools](https://eslint.interlace.tools/)
|
|
114
|
-
- 📝 Achieved 100% documentation parity (both .md and .mdx files)
|
|
115
|
-
|
|
116
|
-
## [3.0.1] - 2026-02-02
|
|
117
|
-
|
|
118
|
-
This was a version bump only for eslint-plugin-reliability to align it with other projects, there were no code changes.
|
|
119
|
-
|
|
120
|
-
## [3.0.0] - 2026-02-02
|
|
121
|
-
|
|
122
|
-
This was a version bump only for eslint-plugin-reliability to align it with other projects, there were no code changes.
|
|
123
|
-
|
|
124
|
-
## [1.0.0] - 2026-01-26
|
|
125
|
-
|
|
126
|
-
### Added
|
|
127
|
-
|
|
128
|
-
- Initial stable release with 8 reliability rules
|
|
129
|
-
- LLM-optimized error messages for AI-assisted development
|
|
130
|
-
- 100% test coverage across all rules
|
|
131
|
-
- ESLint 9 flat config support
|
|
132
|
-
- TypeScript type definitions for all rule options
|
|
133
|
-
|
|
134
|
-
### Rules
|
|
135
|
-
|
|
136
|
-
#### Error Handling (4 rules)
|
|
137
|
-
|
|
138
|
-
| Rule | Description | CWE |
|
|
139
|
-
| :------------------------- | :-------------------------------------------- | :------ |
|
|
140
|
-
| `no-unhandled-promise` | Detect unhandled promise rejections | CWE-392 |
|
|
141
|
-
| `no-silent-errors` | Detect empty catch blocks that swallow errors | CWE-390 |
|
|
142
|
-
| `no-missing-error-context` | Require error context when re-throwing | CWE-209 |
|
|
143
|
-
| `error-message` | Require meaningful error messages | CWE-209 |
|
|
144
|
-
|
|
145
|
-
#### Runtime Safety (4 rules)
|
|
146
|
-
|
|
147
|
-
| Rule | Description | CWE |
|
|
148
|
-
| :------------------------- | :-------------------------------------------- | :---------- |
|
|
149
|
-
| `no-missing-null-checks` | Detect potential null/undefined dereferences | CWE-476 |
|
|
150
|
-
| `no-unsafe-type-narrowing` | Detect unsafe type narrowing patterns | CWE-704 |
|
|
151
|
-
| `require-network-timeout` | Require timeouts on network requests | CWE-400 |
|
|
152
|
-
| `no-await-in-loop` | Detect sequential await in loops (N+1 issues) | Performance |
|
|
153
|
-
|
|
154
|
-
### Presets
|
|
155
|
-
|
|
156
|
-
- `recommended` - Balanced reliability checks (4 rules as warnings)
|
|
157
|
-
|
|
158
|
-
### Known Limitations
|
|
159
|
-
|
|
160
|
-
Documented in `docs/KNOWN-LIMITATIONS.md`:
|
|
161
|
-
|
|
162
|
-
- `no-unhandled-promises`: Promises in callbacks or async iterators may be missed
|
|
163
|
-
- `no-silent-errors`: Intentional suppression vs. actual swallowing is hard to distinguish
|
|
164
|
-
- `no-missing-null-checks`: Business-logic guarantees not in types can trigger false positives
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function eslintPluginReliability(): string;
|
|
@@ -1,20 +0,0 @@
|
|
|
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 {};
|