eslint-plugin-reliability 3.0.3 → 3.1.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
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
<a href="https://www.npmjs.com/package/eslint-plugin-reliability" target="_blank"><img src="https://img.shields.io/npm/v/eslint-plugin-reliability.svg" alt="NPM Version" /></a>
|
|
11
11
|
<a href="https://www.npmjs.com/package/eslint-plugin-reliability" target="_blank"><img src="https://img.shields.io/npm/dm/eslint-plugin-reliability.svg" alt="NPM Downloads" /></a>
|
|
12
12
|
<a href="https://opensource.org/licenses/MIT" target="_blank"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="Package License" /></a>
|
|
13
|
-
<a href="https://app.codecov.io/gh/ofri-peretz/eslint/components?components%5B0%5D=reliability" target="_blank"><img src="https://codecov.io/gh/ofri-peretz/eslint/graph/badge.svg?component=reliability" alt="Codecov" /></a>
|
|
13
|
+
<a href="https://app.codecov.io/gh/ofri-peretz/eslint/components?components%5B0%5D=reliability" target="_blank"><img src="https://codecov.io/gh/ofri-peretz/eslint/graph/badge.svg?component=eslint-plugin-reliability" alt="Codecov" /></a>
|
|
14
14
|
<a href="https://github.com/ofri-peretz/eslint" target="_blank"><img src="https://img.shields.io/badge/Since-Dec_2025-blue?logo=rocket&logoColor=white" alt="Since Dec 2025" /></a>
|
|
15
15
|
</p>
|
|
16
16
|
|
package/package.json
CHANGED
package/src/index.d.ts
CHANGED
|
@@ -29,6 +29,9 @@ export declare const rules: {
|
|
|
29
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
30
|
name: string;
|
|
31
31
|
};
|
|
32
|
+
'no-jsdoc-terminator-in-example': TSESLint.RuleModule<"jsdocTerminatorInExample" | "wrapInQuotes", [(import("./rules/reliability/no-jsdoc-terminator-in-example").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
33
|
+
name: string;
|
|
34
|
+
};
|
|
32
35
|
'error-handling/no-unhandled-promise': TSESLint.RuleModule<"unhandledPromise" | "addCatch" | "useTryCatch" | "useAwait", [(import("./rules/error-handling/no-unhandled-promise").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
33
36
|
name: string;
|
|
34
37
|
};
|
|
@@ -53,6 +56,9 @@ export declare const rules: {
|
|
|
53
56
|
'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
57
|
name: string;
|
|
55
58
|
};
|
|
59
|
+
'reliability/no-jsdoc-terminator-in-example': TSESLint.RuleModule<"jsdocTerminatorInExample" | "wrapInQuotes", [(import("./rules/reliability/no-jsdoc-terminator-in-example").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
60
|
+
name: string;
|
|
61
|
+
};
|
|
56
62
|
};
|
|
57
63
|
export declare const plugin: {
|
|
58
64
|
meta: {
|
|
@@ -84,6 +90,9 @@ export declare const plugin: {
|
|
|
84
90
|
'no-await-in-loop': TSESLint.RuleModule<"awaitInLoop" | "suggestPromiseAll" | "suggestConcurrent" | "considerSequential" | "asyncLoopPattern", [(import("./rules/reliability/no-await-in-loop").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
85
91
|
name: string;
|
|
86
92
|
};
|
|
93
|
+
'no-jsdoc-terminator-in-example': TSESLint.RuleModule<"jsdocTerminatorInExample" | "wrapInQuotes", [(import("./rules/reliability/no-jsdoc-terminator-in-example").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
94
|
+
name: string;
|
|
95
|
+
};
|
|
87
96
|
'error-handling/no-unhandled-promise': TSESLint.RuleModule<"unhandledPromise" | "addCatch" | "useTryCatch" | "useAwait", [(import("./rules/error-handling/no-unhandled-promise").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
88
97
|
name: string;
|
|
89
98
|
};
|
|
@@ -108,12 +117,15 @@ export declare const plugin: {
|
|
|
108
117
|
'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
118
|
name: string;
|
|
110
119
|
};
|
|
120
|
+
'reliability/no-jsdoc-terminator-in-example': TSESLint.RuleModule<"jsdocTerminatorInExample" | "wrapInQuotes", [(import("./rules/reliability/no-jsdoc-terminator-in-example").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
121
|
+
name: string;
|
|
122
|
+
};
|
|
111
123
|
};
|
|
112
124
|
};
|
|
113
125
|
export declare const configs: {
|
|
114
126
|
recommended: {
|
|
115
127
|
plugins: {
|
|
116
|
-
|
|
128
|
+
reliability: {
|
|
117
129
|
meta: {
|
|
118
130
|
name: string;
|
|
119
131
|
version: string;
|
|
@@ -143,6 +155,9 @@ export declare const configs: {
|
|
|
143
155
|
'no-await-in-loop': TSESLint.RuleModule<"awaitInLoop" | "suggestPromiseAll" | "suggestConcurrent" | "considerSequential" | "asyncLoopPattern", [(import("./rules/reliability/no-await-in-loop").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
144
156
|
name: string;
|
|
145
157
|
};
|
|
158
|
+
'no-jsdoc-terminator-in-example': TSESLint.RuleModule<"jsdocTerminatorInExample" | "wrapInQuotes", [(import("./rules/reliability/no-jsdoc-terminator-in-example").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
159
|
+
name: string;
|
|
160
|
+
};
|
|
146
161
|
'error-handling/no-unhandled-promise': TSESLint.RuleModule<"unhandledPromise" | "addCatch" | "useTryCatch" | "useAwait", [(import("./rules/error-handling/no-unhandled-promise").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
147
162
|
name: string;
|
|
148
163
|
};
|
|
@@ -167,13 +182,16 @@ export declare const configs: {
|
|
|
167
182
|
'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
183
|
name: string;
|
|
169
184
|
};
|
|
185
|
+
'reliability/no-jsdoc-terminator-in-example': TSESLint.RuleModule<"jsdocTerminatorInExample" | "wrapInQuotes", [(import("./rules/reliability/no-jsdoc-terminator-in-example").Options | undefined)?], unknown, TSESLint.RuleListener> & {
|
|
186
|
+
name: string;
|
|
187
|
+
};
|
|
170
188
|
};
|
|
171
189
|
};
|
|
172
190
|
};
|
|
173
191
|
rules: {
|
|
174
|
-
'
|
|
175
|
-
'
|
|
176
|
-
'
|
|
192
|
+
'reliability/no-silent-errors': "warn";
|
|
193
|
+
'reliability/no-missing-null-checks': "warn";
|
|
194
|
+
'reliability/require-network-timeout': "error";
|
|
177
195
|
};
|
|
178
196
|
};
|
|
179
197
|
};
|
package/src/index.js
CHANGED
|
@@ -16,6 +16,7 @@ const no_missing_null_checks_1 = require("./rules/reliability/no-missing-null-ch
|
|
|
16
16
|
const no_unsafe_type_narrowing_1 = require("./rules/reliability/no-unsafe-type-narrowing");
|
|
17
17
|
const require_network_timeout_1 = require("./rules/reliability/require-network-timeout");
|
|
18
18
|
const no_await_in_loop_1 = require("./rules/reliability/no-await-in-loop");
|
|
19
|
+
const no_jsdoc_terminator_in_example_1 = require("./rules/reliability/no-jsdoc-terminator-in-example");
|
|
19
20
|
exports.rules = {
|
|
20
21
|
'no-unhandled-promise': no_unhandled_promise_1.noUnhandledPromise,
|
|
21
22
|
'no-silent-errors': no_silent_errors_1.noSilentErrors,
|
|
@@ -25,6 +26,7 @@ exports.rules = {
|
|
|
25
26
|
'no-unsafe-type-narrowing': no_unsafe_type_narrowing_1.noUnsafeTypeNarrowing,
|
|
26
27
|
'require-network-timeout': require_network_timeout_1.requireNetworkTimeout,
|
|
27
28
|
'no-await-in-loop': no_await_in_loop_1.noAwaitInLoop,
|
|
29
|
+
'no-jsdoc-terminator-in-example': no_jsdoc_terminator_in_example_1.noJsdocTerminatorInExample,
|
|
28
30
|
// Categorized names
|
|
29
31
|
'error-handling/no-unhandled-promise': no_unhandled_promise_1.noUnhandledPromise,
|
|
30
32
|
'error-handling/no-silent-errors': no_silent_errors_1.noSilentErrors,
|
|
@@ -34,6 +36,7 @@ exports.rules = {
|
|
|
34
36
|
'reliability/no-unsafe-type-narrowing': no_unsafe_type_narrowing_1.noUnsafeTypeNarrowing,
|
|
35
37
|
'reliability/require-network-timeout': require_network_timeout_1.requireNetworkTimeout,
|
|
36
38
|
'reliability/no-await-in-loop': no_await_in_loop_1.noAwaitInLoop,
|
|
39
|
+
'reliability/no-jsdoc-terminator-in-example': no_jsdoc_terminator_in_example_1.noJsdocTerminatorInExample,
|
|
37
40
|
};
|
|
38
41
|
exports.plugin = {
|
|
39
42
|
meta: {
|
|
@@ -45,12 +48,12 @@ exports.plugin = {
|
|
|
45
48
|
exports.configs = {
|
|
46
49
|
recommended: {
|
|
47
50
|
plugins: {
|
|
48
|
-
|
|
51
|
+
reliability: exports.plugin,
|
|
49
52
|
},
|
|
50
53
|
rules: {
|
|
51
|
-
'
|
|
52
|
-
'
|
|
53
|
-
'
|
|
54
|
+
'reliability/no-silent-errors': 'warn',
|
|
55
|
+
'reliability/no-missing-null-checks': 'warn',
|
|
56
|
+
'reliability/require-network-timeout': 'error',
|
|
54
57
|
},
|
|
55
58
|
},
|
|
56
59
|
};
|
|
@@ -0,0 +1,25 @@
|
|
|
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: no-jsdoc-terminator-in-example
|
|
8
|
+
* Detects `* /` sequences inside JSDoc @example blocks that can
|
|
9
|
+
* prematurely close the comment and cause TypeScript compilation errors.
|
|
10
|
+
*
|
|
11
|
+
* Inspired by real DefinitelyTyped CI failures where patterns like
|
|
12
|
+
* `*\/*` (MIME types, glob patterns) inside @example terminated the
|
|
13
|
+
* JSDoc block early, producing broken type definitions.
|
|
14
|
+
*
|
|
15
|
+
* @see https://github.com/microsoft/dtslint
|
|
16
|
+
*/
|
|
17
|
+
import type { TSESLint } from '@interlace/eslint-devkit';
|
|
18
|
+
type MessageIds = 'jsdocTerminatorInExample' | 'wrapInQuotes';
|
|
19
|
+
export interface Options {
|
|
20
|
+
}
|
|
21
|
+
type RuleOptions = [Options?];
|
|
22
|
+
export declare const noJsdocTerminatorInExample: TSESLint.RuleModule<MessageIds, RuleOptions, unknown, TSESLint.RuleListener> & {
|
|
23
|
+
name: string;
|
|
24
|
+
};
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,131 @@
|
|
|
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.noJsdocTerminatorInExample = void 0;
|
|
9
|
+
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
10
|
+
/**
|
|
11
|
+
* Check whether a comment line is inside an @example block.
|
|
12
|
+
* We track this by scanning line-by-line for @example / next-tag boundaries.
|
|
13
|
+
*/
|
|
14
|
+
function findTerminatorsInExamples(commentText) {
|
|
15
|
+
const lines = commentText.split('\n');
|
|
16
|
+
let inExample = false;
|
|
17
|
+
const offsets = [];
|
|
18
|
+
let currentOffset = 0;
|
|
19
|
+
for (const line of lines) {
|
|
20
|
+
// Strip leading whitespace and optional `*` prefix (block comment lines)
|
|
21
|
+
const stripped = line.replace(/^\s*\*?\s?/, '');
|
|
22
|
+
// Detect @example start
|
|
23
|
+
if (/^\s*@example\b/i.test(stripped)) {
|
|
24
|
+
inExample = true;
|
|
25
|
+
currentOffset += line.length + 1; // +1 for the newline
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
// Detect any other JSDoc tag → exits @example scope
|
|
29
|
+
if (/^\s*@\w+/.test(stripped) && !stripped.startsWith('@example')) {
|
|
30
|
+
inExample = false;
|
|
31
|
+
currentOffset += line.length + 1;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
// If inside an example block, look for `*/` that isn't the closing
|
|
35
|
+
// terminator of the block comment itself (which is always at the very end)
|
|
36
|
+
if (inExample) {
|
|
37
|
+
// Find `*/` occurrences within the original line content
|
|
38
|
+
let searchStart = 0;
|
|
39
|
+
while (searchStart < line.length) {
|
|
40
|
+
const idx = line.indexOf('*/', searchStart);
|
|
41
|
+
if (idx === -1)
|
|
42
|
+
break;
|
|
43
|
+
// The absolute offset within the full comment text
|
|
44
|
+
const absoluteOffset = currentOffset + idx;
|
|
45
|
+
// Don't flag the final `*/` that closes the entire comment —
|
|
46
|
+
// it always sits at the very end of the block comment.
|
|
47
|
+
const remaining = commentText.substring(absoluteOffset + 2).trim();
|
|
48
|
+
if (remaining.length > 0) {
|
|
49
|
+
offsets.push(absoluteOffset);
|
|
50
|
+
}
|
|
51
|
+
searchStart = idx + 2;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
currentOffset += line.length + 1;
|
|
55
|
+
}
|
|
56
|
+
return offsets;
|
|
57
|
+
}
|
|
58
|
+
exports.noJsdocTerminatorInExample = (0, eslint_devkit_1.createRule)({
|
|
59
|
+
name: 'no-jsdoc-terminator-in-example',
|
|
60
|
+
meta: {
|
|
61
|
+
type: 'problem',
|
|
62
|
+
docs: {
|
|
63
|
+
description: 'Detects `*/` sequences inside JSDoc @example blocks that prematurely close the comment',
|
|
64
|
+
},
|
|
65
|
+
fixable: 'code',
|
|
66
|
+
hasSuggestions: true,
|
|
67
|
+
messages: {
|
|
68
|
+
jsdocTerminatorInExample: (0, eslint_devkit_1.formatLLMMessage)({
|
|
69
|
+
icon: eslint_devkit_1.MessageIcons.WARNING,
|
|
70
|
+
issueName: 'JSDoc Terminator in @example',
|
|
71
|
+
description: 'The `*/` sequence inside an @example block will prematurely close the JSDoc comment, causing compilation errors. Wrap the pattern in quotes or use an alternative representation.',
|
|
72
|
+
severity: 'HIGH',
|
|
73
|
+
fix: "Wrap the pattern containing `*/` in quotes, e.g. `'*/*'` instead of `*/*`",
|
|
74
|
+
documentationLink: 'https://jsdoc.app/tags-example',
|
|
75
|
+
}),
|
|
76
|
+
wrapInQuotes: (0, eslint_devkit_1.formatLLMMessage)({
|
|
77
|
+
icon: eslint_devkit_1.MessageIcons.INFO,
|
|
78
|
+
issueName: 'Wrap Pattern in Quotes',
|
|
79
|
+
description: "Wrap the `*/` pattern in single quotes to prevent premature JSDoc termination",
|
|
80
|
+
severity: 'LOW',
|
|
81
|
+
fix: "Replace `*/` with `'*/'` (single-quoted) inside the @example block",
|
|
82
|
+
documentationLink: 'https://jsdoc.app/tags-example',
|
|
83
|
+
}),
|
|
84
|
+
},
|
|
85
|
+
schema: [],
|
|
86
|
+
},
|
|
87
|
+
defaultOptions: [],
|
|
88
|
+
create(context) {
|
|
89
|
+
const sourceCode = context.sourceCode || context.sourceCode;
|
|
90
|
+
return {
|
|
91
|
+
Program() {
|
|
92
|
+
const comments = sourceCode.getAllComments();
|
|
93
|
+
for (const comment of comments) {
|
|
94
|
+
// Only inspect block comments (JSDoc is always /* … */)
|
|
95
|
+
if (comment.type !== 'Block') {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const commentText = comment.value;
|
|
99
|
+
// Quick bail-out: no @example or no `*/` inside → nothing to check
|
|
100
|
+
if (!/@example\b/i.test(commentText) ||
|
|
101
|
+
!commentText.includes('*/')) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const offsets = findTerminatorsInExamples(commentText);
|
|
105
|
+
for (const offset of offsets) {
|
|
106
|
+
// Calculate the absolute position in the source file.
|
|
107
|
+
// comment.range[0] points to the opening `/*`, so the content
|
|
108
|
+
// starts at range[0] + 2.
|
|
109
|
+
const absoluteStart = comment.range[0] + 2 + offset;
|
|
110
|
+
const absoluteEnd = absoluteStart + 2; // length of `*/`
|
|
111
|
+
context.report({
|
|
112
|
+
loc: {
|
|
113
|
+
start: sourceCode.getLocFromIndex(absoluteStart),
|
|
114
|
+
end: sourceCode.getLocFromIndex(absoluteEnd),
|
|
115
|
+
},
|
|
116
|
+
messageId: 'jsdocTerminatorInExample',
|
|
117
|
+
suggest: [
|
|
118
|
+
{
|
|
119
|
+
messageId: 'wrapInQuotes',
|
|
120
|
+
fix: (fixer) => {
|
|
121
|
+
return fixer.replaceTextRange([absoluteStart, absoluteEnd], "'*/'");
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
},
|
|
131
|
+
});
|