eslint-plugin-reliability 3.0.4 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-reliability",
3
- "version": "3.0.4",
3
+ "version": "3.1.0",
4
4
  "description": "ESLint rules for runtime stability, fault tolerance, and type safety.",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
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
- '@interlace/reliability': {
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
- '@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";
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
- '@interlace/reliability': exports.plugin,
51
+ reliability: exports.plugin,
49
52
  },
50
53
  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
+ '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
+ });