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.
@@ -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; // This is an intermediate member expression
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; // Already reported
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; // Already reported by checkMemberExpression
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; // as unknown as T pattern
10
+ return true;
21
11
  }
22
- // Also check for 'any' type
23
12
  if (innerAssertion.typeAnnotation.type === 'TSAnyKeyword') {
24
- return true; // as any as T pattern
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, // Cannot auto-fix without context
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 {};