eslint-plugin-reliability 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,26 @@
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-missing-error-context
8
+ * Detects thrown errors without context
9
+ *
10
+ * @see https://rules.sonarsource.com/javascript/RSPEC-1128/
11
+ */
12
+ import type { TSESLint } from '@interlace/eslint-devkit';
13
+ type MessageIds = 'missingErrorContext' | 'addErrorMessage' | 'addErrorStack' | 'useErrorClass';
14
+ export interface Options {
15
+ /** Require error message. Default: true */
16
+ requireMessage?: boolean;
17
+ /** Require stack trace. Default: false */
18
+ requireStackTrace?: boolean;
19
+ /** Ignore in test files. Default: true */
20
+ ignoreInTests?: boolean;
21
+ }
22
+ type RuleOptions = [Options?];
23
+ export declare const noMissingErrorContext: TSESLint.RuleModule<MessageIds, RuleOptions, unknown, TSESLint.RuleListener> & {
24
+ name: string;
25
+ };
26
+ export {};
@@ -0,0 +1,183 @@
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.noMissingErrorContext = void 0;
9
+ const eslint_devkit_1 = require("@interlace/eslint-devkit");
10
+ const eslint_devkit_2 = require("@interlace/eslint-devkit");
11
+ /**
12
+ * Check if error has a message
13
+ */
14
+ function hasErrorMessage(node) {
15
+ if (!node.argument) {
16
+ return false;
17
+ }
18
+ // Check if it's a new Error() with message (includes TypeError, ReferenceError, etc.)
19
+ if (node.argument.type === 'NewExpression' &&
20
+ node.argument.callee.type === 'Identifier' &&
21
+ (node.argument.callee.name === 'Error' ||
22
+ node.argument.callee.name.endsWith('Error'))) {
23
+ // Check if first argument is a string (message)
24
+ if (node.argument.arguments.length > 0) {
25
+ const firstArg = node.argument.arguments[0];
26
+ if (firstArg.type === 'Literal' && typeof firstArg.value === 'string') {
27
+ return firstArg.value.length > 0;
28
+ }
29
+ if (firstArg.type === 'TemplateLiteral') {
30
+ return true;
31
+ }
32
+ }
33
+ }
34
+ // Check if it's a string literal
35
+ if (node.argument.type === 'Literal' &&
36
+ typeof node.argument.value === 'string') {
37
+ return node.argument.value.length > 0;
38
+ }
39
+ // Check if it's a template literal
40
+ if (node.argument.type === 'TemplateLiteral') {
41
+ return true;
42
+ }
43
+ return false;
44
+ }
45
+ /**
46
+ * Check if error is an Error instance (has stack trace)
47
+ */
48
+ function hasErrorStack(node) {
49
+ if (!node.argument) {
50
+ return false;
51
+ }
52
+ // Check if it's a new Error() instance
53
+ if (node.argument.type === 'NewExpression' &&
54
+ node.argument.callee.type === 'Identifier') {
55
+ const calleeName = node.argument.callee.name;
56
+ // Error, TypeError, ReferenceError, etc. all have stack traces
57
+ if (calleeName === 'Error' || calleeName.endsWith('Error')) {
58
+ return true;
59
+ }
60
+ }
61
+ return false;
62
+ }
63
+ exports.noMissingErrorContext = (0, eslint_devkit_2.createRule)({
64
+ name: 'no-missing-error-context',
65
+ meta: {
66
+ type: 'suggestion',
67
+ docs: {
68
+ description: 'Detects thrown errors without context',
69
+ },
70
+ hasSuggestions: true,
71
+ messages: {
72
+ missingErrorContext: (0, eslint_devkit_1.formatLLMMessage)({
73
+ icon: eslint_devkit_1.MessageIcons.WARNING,
74
+ issueName: 'Missing error context',
75
+ description: 'Thrown error missing {{missing}}',
76
+ severity: 'MEDIUM',
77
+ fix: 'Add error message and use Error class for stack trace',
78
+ documentationLink: 'https://rules.sonarsource.com/javascript/RSPEC-1128/',
79
+ }),
80
+ addErrorMessage: (0, eslint_devkit_1.formatLLMMessage)({
81
+ icon: eslint_devkit_1.MessageIcons.INFO,
82
+ issueName: 'Add Error Message',
83
+ description: 'Add descriptive error message',
84
+ severity: 'LOW',
85
+ fix: 'throw new Error("descriptive message")',
86
+ documentationLink: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error',
87
+ }),
88
+ addErrorStack: (0, eslint_devkit_1.formatLLMMessage)({
89
+ icon: eslint_devkit_1.MessageIcons.INFO,
90
+ issueName: 'Use Error Class',
91
+ description: 'Use Error class for stack trace',
92
+ severity: 'LOW',
93
+ fix: 'throw new Error(message) instead of throw message',
94
+ documentationLink: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error',
95
+ }),
96
+ useErrorClass: (0, eslint_devkit_1.formatLLMMessage)({
97
+ icon: eslint_devkit_1.MessageIcons.INFO,
98
+ issueName: 'Use Specific Error',
99
+ description: 'Use specific error class',
100
+ severity: 'LOW',
101
+ fix: 'throw new TypeError("message") or throw new RangeError("message")',
102
+ documentationLink: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError',
103
+ }),
104
+ },
105
+ schema: [
106
+ {
107
+ type: 'object',
108
+ properties: {
109
+ requireMessage: {
110
+ type: 'boolean',
111
+ default: true,
112
+ description: 'Require error message',
113
+ },
114
+ requireStackTrace: {
115
+ type: 'boolean',
116
+ default: false,
117
+ description: 'Require stack trace',
118
+ },
119
+ ignoreInTests: {
120
+ type: 'boolean',
121
+ default: true,
122
+ description: 'Ignore in test files',
123
+ },
124
+ },
125
+ additionalProperties: false,
126
+ },
127
+ ],
128
+ },
129
+ defaultOptions: [
130
+ {
131
+ requireMessage: true,
132
+ requireStackTrace: false,
133
+ ignoreInTests: true,
134
+ },
135
+ ],
136
+ create(context, [options = {}]) {
137
+ const { requireMessage = true, requireStackTrace = false, ignoreInTests = true, } = options || {};
138
+ const filename = context.getFilename();
139
+ const isTestFile = ignoreInTests && /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);
140
+ if (isTestFile) {
141
+ return {};
142
+ }
143
+ /**
144
+ * Check throw statements
145
+ */
146
+ function checkThrowStatement(node) {
147
+ const missing = [];
148
+ if (requireMessage && !hasErrorMessage(node)) {
149
+ missing.push('message');
150
+ }
151
+ if (requireStackTrace && !hasErrorStack(node)) {
152
+ missing.push('stack trace');
153
+ }
154
+ if (missing.length === 0) {
155
+ return;
156
+ }
157
+ context.report({
158
+ node,
159
+ messageId: 'missingErrorContext',
160
+ data: {
161
+ missing: missing.join(' and '),
162
+ },
163
+ suggest: [
164
+ {
165
+ messageId: 'addErrorMessage',
166
+ fix: () => null, // Cannot auto-fix without context
167
+ },
168
+ {
169
+ messageId: 'addErrorStack',
170
+ fix: () => null,
171
+ },
172
+ {
173
+ messageId: 'useErrorClass',
174
+ fix: () => null,
175
+ },
176
+ ],
177
+ });
178
+ }
179
+ return {
180
+ ThrowStatement: checkThrowStatement,
181
+ };
182
+ },
183
+ });
@@ -0,0 +1,24 @@
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-silent-errors
8
+ * Detects empty catch blocks
9
+ *
10
+ * @see https://rules.sonarsource.com/javascript/RSPEC-1186/
11
+ */
12
+ import type { TSESLint } from '@interlace/eslint-devkit';
13
+ type MessageIds = 'silentError' | 'addErrorLogging' | 'addErrorHandling' | 'rethrowError';
14
+ export interface Options {
15
+ /** Allow empty catch if comment explains why. Default: false */
16
+ allowWithComment?: boolean;
17
+ /** Ignore in test files. Default: true */
18
+ ignoreInTests?: boolean;
19
+ }
20
+ type RuleOptions = [Options?];
21
+ export declare const noSilentErrors: TSESLint.RuleModule<MessageIds, RuleOptions, unknown, TSESLint.RuleListener> & {
22
+ name: string;
23
+ };
24
+ export {};
@@ -0,0 +1,178 @@
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.noSilentErrors = void 0;
9
+ const eslint_devkit_1 = require("@interlace/eslint-devkit");
10
+ const eslint_devkit_2 = require("@interlace/eslint-devkit");
11
+ /**
12
+ * Check if catch block is empty or only has comments
13
+ */
14
+ function isEmptyCatchBlock(catchClause) {
15
+ const body = catchClause.body;
16
+ /* v8 ignore next 3 -- defensive: catch clause body is always BlockStatement in valid JS */
17
+ if (!body || body.type !== 'BlockStatement') {
18
+ return true;
19
+ }
20
+ // Check if body only has comments or is empty
21
+ const statements = body.body;
22
+ if (statements.length === 0) {
23
+ return true;
24
+ }
25
+ // Check if all statements are just comments (not possible in AST, but check for empty statements)
26
+ const nonEmptyStatements = statements.filter((stmt) => stmt.type !== 'EmptyStatement');
27
+ return nonEmptyStatements.length === 0;
28
+ }
29
+ /**
30
+ * Check if catch block has a comment explaining why it's empty
31
+ */
32
+ function hasExplanatoryComment(catchClause, sourceCode) {
33
+ // Check for comments before the catch clause
34
+ const comments = sourceCode.getAllComments();
35
+ const catchStart = catchClause.loc?.start;
36
+ /* v8 ignore next 3 -- defensive: catch clause always has location, and no comments = no match */
37
+ if (!catchStart || !comments.length) {
38
+ return false;
39
+ }
40
+ // Look for explanatory comments near the catch clause
41
+ const explanatoryPatterns = [
42
+ /intentional/i,
43
+ /expected/i,
44
+ /ignore/i,
45
+ /silent/i,
46
+ /noop/i,
47
+ /no-op/i,
48
+ /by design/i,
49
+ /known issue/i,
50
+ /legacy/i,
51
+ /third.?party/i,
52
+ /framework/i,
53
+ /library/i,
54
+ /not implemented/i,
55
+ /todo/i,
56
+ /fixme/i,
57
+ ];
58
+ // Check comments before the catch clause (within 2 lines)
59
+ for (const comment of comments) {
60
+ if (comment.loc && catchStart.line - comment.loc.end.line <= 2) {
61
+ const commentText = comment.value.toLowerCase();
62
+ if (explanatoryPatterns.some((pattern) => pattern.test(commentText))) {
63
+ return true;
64
+ }
65
+ }
66
+ }
67
+ return false;
68
+ }
69
+ exports.noSilentErrors = (0, eslint_devkit_2.createRule)({
70
+ name: 'no-silent-errors',
71
+ meta: {
72
+ type: 'problem',
73
+ docs: {
74
+ description: 'Detects empty catch blocks',
75
+ },
76
+ hasSuggestions: true,
77
+ messages: {
78
+ silentError: (0, eslint_devkit_1.formatLLMMessage)({
79
+ icon: eslint_devkit_1.MessageIcons.WARNING,
80
+ issueName: 'Silent error',
81
+ description: 'Empty catch block detected - errors are silently ignored',
82
+ severity: 'MEDIUM',
83
+ fix: 'Add error logging or handling in catch block',
84
+ documentationLink: 'https://rules.sonarsource.com/javascript/RSPEC-1186/',
85
+ }),
86
+ addErrorLogging: (0, eslint_devkit_1.formatLLMMessage)({
87
+ icon: eslint_devkit_1.MessageIcons.INFO,
88
+ issueName: 'Add Error Logging',
89
+ description: 'Add error logging to catch block',
90
+ severity: 'LOW',
91
+ fix: 'catch (error) { console.error(error); }',
92
+ documentationLink: 'https://rules.sonarsource.com/javascript/RSPEC-1186/',
93
+ }),
94
+ addErrorHandling: (0, eslint_devkit_1.formatLLMMessage)({
95
+ icon: eslint_devkit_1.MessageIcons.INFO,
96
+ issueName: 'Add Error Handling',
97
+ description: 'Add error handling to catch block',
98
+ severity: 'LOW',
99
+ fix: 'catch (error) { handleError(error); }',
100
+ documentationLink: 'https://rules.sonarsource.com/javascript/RSPEC-1186/',
101
+ }),
102
+ rethrowError: (0, eslint_devkit_1.formatLLMMessage)({
103
+ icon: eslint_devkit_1.MessageIcons.INFO,
104
+ issueName: 'Rethrow Error',
105
+ description: 'Rethrow error if needed',
106
+ severity: 'LOW',
107
+ fix: 'catch (error) { throw new CustomError(error); }',
108
+ documentationLink: 'https://rules.sonarsource.com/javascript/RSPEC-1186/',
109
+ }),
110
+ },
111
+ schema: [
112
+ {
113
+ type: 'object',
114
+ properties: {
115
+ allowWithComment: {
116
+ type: 'boolean',
117
+ default: false,
118
+ description: 'Allow empty catch if comment explains why',
119
+ },
120
+ ignoreInTests: {
121
+ type: 'boolean',
122
+ default: true,
123
+ description: 'Ignore in test files',
124
+ },
125
+ },
126
+ additionalProperties: false,
127
+ },
128
+ ],
129
+ },
130
+ defaultOptions: [
131
+ {
132
+ allowWithComment: false,
133
+ ignoreInTests: true,
134
+ },
135
+ ],
136
+ create(context, [options = {}]) {
137
+ const { allowWithComment = false, ignoreInTests = true } = options || {};
138
+ const filename = context.getFilename();
139
+ const isTestFile = ignoreInTests && /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);
140
+ if (isTestFile) {
141
+ return {};
142
+ }
143
+ const sourceCode = context.sourceCode || context.sourceCode;
144
+ /**
145
+ * Check catch clauses
146
+ */
147
+ function checkCatchClause(node) {
148
+ if (!isEmptyCatchBlock(node)) {
149
+ return;
150
+ }
151
+ // Check if comment explains why it's empty
152
+ if (allowWithComment && hasExplanatoryComment(node, sourceCode)) {
153
+ return;
154
+ }
155
+ context.report({
156
+ node,
157
+ messageId: 'silentError',
158
+ suggest: [
159
+ {
160
+ messageId: 'addErrorLogging',
161
+ fix: () => null, // Cannot auto-fix without context
162
+ },
163
+ {
164
+ messageId: 'addErrorHandling',
165
+ fix: () => null,
166
+ },
167
+ {
168
+ messageId: 'rethrowError',
169
+ fix: () => null,
170
+ },
171
+ ],
172
+ });
173
+ }
174
+ return {
175
+ CatchClause: checkCatchClause,
176
+ };
177
+ },
178
+ });
@@ -0,0 +1,26 @@
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-unhandled-promise
8
+ * Detects unhandled Promise rejections
9
+ * CWE-1024: Comparison of Classes by Name
10
+ *
11
+ * @see https://cwe.mitre.org/data/definitions/1024.html
12
+ * @see https://rules.sonarsource.com/javascript/RSPEC-4635/
13
+ */
14
+ import type { TSESLint } from '@interlace/eslint-devkit';
15
+ type MessageIds = 'unhandledPromise' | 'addCatch' | 'useTryCatch' | 'useAwait';
16
+ export interface Options {
17
+ /** Ignore promises in test files. Default: true */
18
+ ignoreInTests?: boolean;
19
+ /** Ignore promises in void expressions. Default: false */
20
+ ignoreVoidExpressions?: boolean;
21
+ }
22
+ type RuleOptions = [Options?];
23
+ export declare const noUnhandledPromise: TSESLint.RuleModule<MessageIds, RuleOptions, unknown, TSESLint.RuleListener> & {
24
+ name: string;
25
+ };
26
+ export {};