eslint-plugin-maintainability 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.
- package/CHANGELOG.md +63 -0
- package/LICENSE +23 -0
- package/README.md +116 -0
- package/package.json +65 -0
- package/src/index.d.ts +180 -0
- package/src/index.js +56 -0
- package/src/rules/error-handling/error-message.d.ts +20 -0
- package/src/rules/error-handling/error-message.js +146 -0
- package/src/rules/error-handling/no-missing-error-context.d.ts +26 -0
- package/src/rules/error-handling/no-missing-error-context.js +183 -0
- package/src/rules/error-handling/no-silent-errors.d.ts +24 -0
- package/src/rules/error-handling/no-silent-errors.js +178 -0
- package/src/rules/error-handling/no-unhandled-promise.d.ts +26 -0
- package/src/rules/error-handling/no-unhandled-promise.js +351 -0
- package/src/rules/maintainability/cognitive-complexity.d.ts +28 -0
- package/src/rules/maintainability/cognitive-complexity.js +381 -0
- package/src/rules/maintainability/consistent-function-scoping.d.ts +20 -0
- package/src/rules/maintainability/consistent-function-scoping.js +226 -0
- package/src/rules/maintainability/identical-functions.d.ts +27 -0
- package/src/rules/maintainability/identical-functions.js +310 -0
- package/src/rules/maintainability/max-parameters.d.ts +29 -0
- package/src/rules/maintainability/max-parameters.js +143 -0
- package/src/rules/maintainability/nested-complexity-hotspots.d.ts +31 -0
- package/src/rules/maintainability/nested-complexity-hotspots.js +190 -0
- package/src/rules/maintainability/no-lonely-if.d.ts +19 -0
- package/src/rules/maintainability/no-lonely-if.js +120 -0
- package/src/rules/maintainability/no-nested-ternary.d.ts +19 -0
- package/src/rules/maintainability/no-nested-ternary.js +165 -0
- package/src/rules/maintainability/no-unreadable-iife.d.ts +24 -0
- package/src/rules/maintainability/no-unreadable-iife.js +239 -0
- package/src/types/index.d.ts +48 -0
- package/src/types/index.js +7 -0
|
@@ -0,0 +1,146 @@
|
|
|
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.errorMessage = void 0;
|
|
9
|
+
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
10
|
+
const eslint_devkit_2 = require("@interlace/eslint-devkit");
|
|
11
|
+
exports.errorMessage = (0, eslint_devkit_1.createRule)({
|
|
12
|
+
name: 'error-message',
|
|
13
|
+
meta: {
|
|
14
|
+
type: 'problem',
|
|
15
|
+
docs: {
|
|
16
|
+
description: 'Enforce providing a message when creating built-in Error objects for better debugging',
|
|
17
|
+
},
|
|
18
|
+
hasSuggestions: true,
|
|
19
|
+
messages: {
|
|
20
|
+
missingErrorMessage: (0, eslint_devkit_2.formatLLMMessage)({
|
|
21
|
+
icon: eslint_devkit_2.MessageIcons.INFO,
|
|
22
|
+
issueName: 'Missing Error Message',
|
|
23
|
+
description: 'Error constructor called without message parameter',
|
|
24
|
+
severity: 'HIGH',
|
|
25
|
+
fix: 'Add descriptive error message: new Error("description")',
|
|
26
|
+
documentationLink: 'https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/error-message.md',
|
|
27
|
+
}),
|
|
28
|
+
addErrorMessage: (0, eslint_devkit_2.formatLLMMessage)({
|
|
29
|
+
icon: eslint_devkit_2.MessageIcons.WARNING,
|
|
30
|
+
issueName: 'Add Error Message',
|
|
31
|
+
description: 'Add descriptive error message to constructor',
|
|
32
|
+
severity: 'HIGH',
|
|
33
|
+
fix: 'Add descriptive error message: new Error("description")',
|
|
34
|
+
documentationLink: 'https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/error-message.md',
|
|
35
|
+
}),
|
|
36
|
+
},
|
|
37
|
+
schema: [
|
|
38
|
+
{
|
|
39
|
+
type: 'object',
|
|
40
|
+
properties: {
|
|
41
|
+
allowEmptyCatch: {
|
|
42
|
+
type: 'boolean',
|
|
43
|
+
default: false,
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
additionalProperties: false,
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
defaultOptions: [{ allowEmptyCatch: false }],
|
|
51
|
+
create(context) {
|
|
52
|
+
const [options] = context.options;
|
|
53
|
+
const { allowEmptyCatch = false } = options || {};
|
|
54
|
+
// Built-in Error constructors that should have messages
|
|
55
|
+
const errorConstructors = new Set([
|
|
56
|
+
'Error',
|
|
57
|
+
'TypeError',
|
|
58
|
+
'ReferenceError',
|
|
59
|
+
'SyntaxError',
|
|
60
|
+
'RangeError',
|
|
61
|
+
'EvalError',
|
|
62
|
+
'URIError',
|
|
63
|
+
]);
|
|
64
|
+
function isErrorConstructor(name) {
|
|
65
|
+
return errorConstructors.has(name);
|
|
66
|
+
}
|
|
67
|
+
function hasMessageArgument(node) {
|
|
68
|
+
// Check if there are arguments
|
|
69
|
+
if (!node.arguments || node.arguments.length === 0) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
// Check if first argument is a non-empty string or expression
|
|
73
|
+
const firstArg = node.arguments[0];
|
|
74
|
+
if (firstArg.type === 'Literal') {
|
|
75
|
+
// Allow non-empty strings
|
|
76
|
+
return (typeof firstArg.value === 'string' && firstArg.value.trim().length > 0);
|
|
77
|
+
}
|
|
78
|
+
// Allow any expression (variable, function call, etc.) as it might be dynamic
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
function isInCatchClause(node) {
|
|
82
|
+
let current = node;
|
|
83
|
+
while (current) {
|
|
84
|
+
if (current.type === 'CatchClause') {
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
current = current.parent;
|
|
88
|
+
}
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
function checkErrorCreation(node) {
|
|
92
|
+
let constructorName = null;
|
|
93
|
+
if (node.type === 'NewExpression') {
|
|
94
|
+
// new Error(...)
|
|
95
|
+
if (node.callee.type === 'Identifier') {
|
|
96
|
+
constructorName = node.callee.name;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
else if (node.type === 'CallExpression') {
|
|
100
|
+
// Error(...) - function call style
|
|
101
|
+
if (node.callee.type === 'Identifier') {
|
|
102
|
+
constructorName = node.callee.name;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (!constructorName || !isErrorConstructor(constructorName)) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
// Allow empty catch if option is enabled
|
|
109
|
+
if (allowEmptyCatch && isInCatchClause(node)) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
// Check if message is provided
|
|
113
|
+
if (!hasMessageArgument(node)) {
|
|
114
|
+
context.report({
|
|
115
|
+
node,
|
|
116
|
+
messageId: 'missingErrorMessage',
|
|
117
|
+
data: {
|
|
118
|
+
constructor: constructorName,
|
|
119
|
+
},
|
|
120
|
+
suggest: [
|
|
121
|
+
{
|
|
122
|
+
messageId: 'addErrorMessage',
|
|
123
|
+
data: { constructor: constructorName },
|
|
124
|
+
fix(fixer) {
|
|
125
|
+
if (node.arguments.length === 0) {
|
|
126
|
+
// No arguments: replace the empty parentheses with ("Error message")
|
|
127
|
+
const parenStart = node.callee.range[1];
|
|
128
|
+
const parenEnd = node.range[1];
|
|
129
|
+
return fixer.replaceTextRange([parenStart, parenEnd], '("Error message")');
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
// Has arguments: replace the first argument with "Error message"
|
|
133
|
+
return fixer.replaceText(node.arguments[0], '"Error message"');
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
NewExpression: checkErrorCreation,
|
|
143
|
+
CallExpression: checkErrorCreation,
|
|
144
|
+
};
|
|
145
|
+
},
|
|
146
|
+
});
|
|
@@ -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 {};
|