eslint-plugin-reliability 3.1.9 → 3.1.11
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 +1 -1
- package/package.json +2 -3
- package/src/index.js +1 -52
- package/src/lib/eslint-plugin-reliability.js +1 -6
- package/src/oxlint.js +1 -3
- package/src/rules/error-handling/error-message.js +1 -131
- package/src/rules/error-handling/no-missing-error-context.js +1 -180
- package/src/rules/error-handling/no-silent-errors.js +1 -157
- package/src/rules/error-handling/no-unhandled-promise.js +1 -321
- package/src/rules/reliability/no-await-in-loop.js +1 -218
- package/src/rules/reliability/no-jsdoc-terminator-in-example.js +1 -109
- package/src/rules/reliability/no-missing-null-checks.js +1 -413
- package/src/rules/reliability/no-unsafe-type-narrowing.js +1 -155
- package/src/rules/reliability/require-network-timeout.js +1 -50
|
@@ -1,109 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.noJsdocTerminatorInExample = void 0;
|
|
4
|
-
exports.findTerminatorsInExamples = findTerminatorsInExamples;
|
|
5
|
-
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
6
|
-
function findTerminatorsInExamples(commentText) {
|
|
7
|
-
const lines = commentText.split('\n');
|
|
8
|
-
let inExample = false;
|
|
9
|
-
const offsets = [];
|
|
10
|
-
let currentOffset = 0;
|
|
11
|
-
for (const line of lines) {
|
|
12
|
-
const stripped = line.replace(/^\s*\*?\s?/, '');
|
|
13
|
-
if (/^\s*@example\b/i.test(stripped)) {
|
|
14
|
-
inExample = true;
|
|
15
|
-
currentOffset += line.length + 1;
|
|
16
|
-
continue;
|
|
17
|
-
}
|
|
18
|
-
if (/^\s*@\w+/.test(stripped) && !stripped.startsWith('@example')) {
|
|
19
|
-
inExample = false;
|
|
20
|
-
currentOffset += line.length + 1;
|
|
21
|
-
continue;
|
|
22
|
-
}
|
|
23
|
-
if (inExample) {
|
|
24
|
-
let searchStart = 0;
|
|
25
|
-
while (searchStart < line.length) {
|
|
26
|
-
const idx = line.indexOf('*/', searchStart);
|
|
27
|
-
if (idx === -1)
|
|
28
|
-
break;
|
|
29
|
-
const absoluteOffset = currentOffset + idx;
|
|
30
|
-
const remaining = commentText.substring(absoluteOffset + 2).trim();
|
|
31
|
-
if (remaining.length > 0) {
|
|
32
|
-
offsets.push(absoluteOffset);
|
|
33
|
-
}
|
|
34
|
-
searchStart = idx + 2;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
currentOffset += line.length + 1;
|
|
38
|
-
}
|
|
39
|
-
return offsets;
|
|
40
|
-
}
|
|
41
|
-
exports.noJsdocTerminatorInExample = (0, eslint_devkit_1.createRule)({
|
|
42
|
-
name: 'no-jsdoc-terminator-in-example',
|
|
43
|
-
meta: {
|
|
44
|
-
type: 'problem',
|
|
45
|
-
docs: {
|
|
46
|
-
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-reliability/docs/rules/no-jsdoc-terminator-in-example.md',
|
|
47
|
-
description: 'Detects `*/` sequences inside JSDoc @example blocks that prematurely close the comment',
|
|
48
|
-
},
|
|
49
|
-
hasSuggestions: true,
|
|
50
|
-
messages: {
|
|
51
|
-
jsdocTerminatorInExample: (0, eslint_devkit_1.formatLLMMessage)({
|
|
52
|
-
icon: eslint_devkit_1.MessageIcons.WARNING,
|
|
53
|
-
issueName: 'JSDoc Terminator in @example',
|
|
54
|
-
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.',
|
|
55
|
-
severity: 'HIGH',
|
|
56
|
-
fix: "Wrap the pattern containing `*/` in quotes, e.g. `'*/*'` instead of `*/*`",
|
|
57
|
-
documentationLink: 'https://jsdoc.app/tags-example',
|
|
58
|
-
}),
|
|
59
|
-
wrapInQuotes: (0, eslint_devkit_1.formatLLMMessage)({
|
|
60
|
-
icon: eslint_devkit_1.MessageIcons.INFO,
|
|
61
|
-
issueName: 'Wrap Pattern in Quotes',
|
|
62
|
-
description: "Wrap the `*/` pattern in single quotes to prevent premature JSDoc termination",
|
|
63
|
-
severity: 'LOW',
|
|
64
|
-
fix: "Replace `*/` with `'*/'` (single-quoted) inside the @example block",
|
|
65
|
-
documentationLink: 'https://jsdoc.app/tags-example',
|
|
66
|
-
}),
|
|
67
|
-
},
|
|
68
|
-
schema: [],
|
|
69
|
-
},
|
|
70
|
-
defaultOptions: [],
|
|
71
|
-
create(context) {
|
|
72
|
-
const sourceCode = context.sourceCode;
|
|
73
|
-
return {
|
|
74
|
-
Program() {
|
|
75
|
-
const comments = sourceCode.getAllComments();
|
|
76
|
-
for (const comment of comments) {
|
|
77
|
-
if (comment.type !== 'Block') {
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
const commentText = comment.value;
|
|
81
|
-
if (!/@example\b/i.test(commentText) ||
|
|
82
|
-
!commentText.includes('*/')) {
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
const offsets = findTerminatorsInExamples(commentText);
|
|
86
|
-
for (const offset of offsets) {
|
|
87
|
-
const absoluteStart = comment.range[0] + 2 + offset;
|
|
88
|
-
const absoluteEnd = absoluteStart + 2;
|
|
89
|
-
context.report({
|
|
90
|
-
loc: {
|
|
91
|
-
start: sourceCode.getLocFromIndex(absoluteStart),
|
|
92
|
-
end: sourceCode.getLocFromIndex(absoluteEnd),
|
|
93
|
-
},
|
|
94
|
-
messageId: 'jsdocTerminatorInExample',
|
|
95
|
-
suggest: [
|
|
96
|
-
{
|
|
97
|
-
messageId: 'wrapInQuotes',
|
|
98
|
-
fix: (fixer) => {
|
|
99
|
-
return fixer.replaceTextRange([absoluteStart, absoluteEnd], "'*/'");
|
|
100
|
-
},
|
|
101
|
-
},
|
|
102
|
-
],
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
},
|
|
107
|
-
};
|
|
108
|
-
},
|
|
109
|
-
});
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noJsdocTerminatorInExample=void 0;exports.findTerminatorsInExamples=findTerminatorsInExamples;const eslint_devkit_1=require("@interlace/eslint-devkit");function findTerminatorsInExamples(commentText){const lines=commentText.split("\n");let inExample=false;const offsets=[];let currentOffset=0;for(const line of lines){const stripped=line.replace(/^\s*\*?\s?/,"");if(/^\s*@example\b/i.test(stripped)){inExample=true;currentOffset+=line.length+1;continue}if(/^\s*@\w+/.test(stripped)&&!stripped.startsWith("@example")){inExample=false;currentOffset+=line.length+1;continue}if(inExample){let searchStart=0;while(searchStart<line.length){const idx=line.indexOf("*/",searchStart);if(idx===-1)break;const absoluteOffset=currentOffset+idx;const remaining=commentText.substring(absoluteOffset+2).trim();if(remaining.length>0){offsets.push(absoluteOffset)}searchStart=idx+2}}currentOffset+=line.length+1}return offsets}exports.noJsdocTerminatorInExample=(0,eslint_devkit_1.createRule)({name:"no-jsdoc-terminator-in-example",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-reliability/docs/rules/no-jsdoc-terminator-in-example.md",description:"Detects `*/` sequences inside JSDoc @example blocks that prematurely close the comment"},hasSuggestions:true,messages:{jsdocTerminatorInExample:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"JSDoc Terminator in @example",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.",severity:"HIGH",fix:"Wrap the pattern containing `*/` in quotes, e.g. `'*/*'` instead of `*/*`",documentationLink:"https://jsdoc.app/tags-example"}),wrapInQuotes:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Wrap Pattern in Quotes",description:"Wrap the `*/` pattern in single quotes to prevent premature JSDoc termination",severity:"LOW",fix:"Replace `*/` with `'*/'` (single-quoted) inside the @example block",documentationLink:"https://jsdoc.app/tags-example"})},schema:[]},defaultOptions:[],create(context){const sourceCode=context.sourceCode;return{Program(){const comments=sourceCode.getAllComments();for(const comment of comments){if(comment.type!=="Block"){continue}const commentText=comment.value;if(!/@example\b/i.test(commentText)||!commentText.includes("*/")){continue}const offsets=findTerminatorsInExamples(commentText);for(const offset of offsets){const absoluteStart=comment.range[0]+2+offset;const absoluteEnd=absoluteStart+2;context.report({loc:{start:sourceCode.getLocFromIndex(absoluteStart),end:sourceCode.getLocFromIndex(absoluteEnd)},messageId:"jsdocTerminatorInExample",suggest:[{messageId:"wrapInQuotes",fix:fixer=>{return fixer.replaceTextRange([absoluteStart,absoluteEnd],"'*/'")}}]})}}}}}});
|
|
@@ -1,413 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.noMissingNullChecks = void 0;
|
|
4
|
-
exports.hasNullCheck = hasNullCheck;
|
|
5
|
-
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
6
|
-
const eslint_devkit_2 = require("@interlace/eslint-devkit");
|
|
7
|
-
const NEVER_NULL_GLOBALS = new Set([
|
|
8
|
-
'console', 'process', 'Buffer', '__dirname', '__filename', 'module', 'exports', 'require',
|
|
9
|
-
'navigator', 'location', 'history',
|
|
10
|
-
'Math', 'JSON', 'Object', 'Array', 'Number', 'String', 'Boolean', 'Date',
|
|
11
|
-
'RegExp', 'Promise', 'Symbol', 'Map', 'Set', 'WeakMap', 'WeakSet',
|
|
12
|
-
'Proxy', 'Reflect', 'Intl', 'BigInt', 'WebAssembly', 'Atomics',
|
|
13
|
-
'URL', 'URLSearchParams', 'TextEncoder', 'TextDecoder',
|
|
14
|
-
'AbortController', 'AbortSignal', 'EventTarget', 'Event', 'CustomEvent',
|
|
15
|
-
'FormData', 'Blob', 'File', 'FileReader', 'Headers', 'Request', 'Response',
|
|
16
|
-
'Error', 'TypeError', 'RangeError', 'SyntaxError', 'URIError',
|
|
17
|
-
'EvalError', 'ReferenceError', 'AggregateError',
|
|
18
|
-
'fetch', 'crypto', 'performance', 'queueMicrotask',
|
|
19
|
-
'setTimeout', 'clearTimeout', 'setInterval', 'clearInterval',
|
|
20
|
-
'setImmediate', 'clearImmediate', 'requestAnimationFrame', 'cancelAnimationFrame',
|
|
21
|
-
'logger', 'log', 'winston', 'pino', 'bunyan',
|
|
22
|
-
]);
|
|
23
|
-
function isProvablyNonNullableIdentifier(ident, scope) {
|
|
24
|
-
if (NEVER_NULL_GLOBALS.has(ident.name))
|
|
25
|
-
return true;
|
|
26
|
-
let s = scope;
|
|
27
|
-
while (s) {
|
|
28
|
-
const variable = s.variables.find((v) => v.name === ident.name);
|
|
29
|
-
if (variable) {
|
|
30
|
-
for (const def of variable.defs) {
|
|
31
|
-
if (def.type === 'CatchClause')
|
|
32
|
-
return true;
|
|
33
|
-
if (def.type === 'ImportBinding')
|
|
34
|
-
return true;
|
|
35
|
-
if (def.type === 'Variable' && def.node?.type === 'VariableDeclarator') {
|
|
36
|
-
const init = def.node.init;
|
|
37
|
-
if (!init)
|
|
38
|
-
continue;
|
|
39
|
-
if (init.type === 'NewExpression')
|
|
40
|
-
return true;
|
|
41
|
-
if (init.type === 'ArrayExpression')
|
|
42
|
-
return true;
|
|
43
|
-
if (init.type === 'ObjectExpression')
|
|
44
|
-
return true;
|
|
45
|
-
if (init.type === 'TemplateLiteral')
|
|
46
|
-
return true;
|
|
47
|
-
if (init.type === 'ClassExpression')
|
|
48
|
-
return true;
|
|
49
|
-
if (init.type === 'Literal' && init.value !== null)
|
|
50
|
-
return true;
|
|
51
|
-
if (init.type === 'AwaitExpression' &&
|
|
52
|
-
init.argument.type === 'CallExpression' &&
|
|
53
|
-
init.argument.callee.type === 'Identifier' &&
|
|
54
|
-
init.argument.callee.name === 'fetch')
|
|
55
|
-
return true;
|
|
56
|
-
if (init.type === 'CallExpression' &&
|
|
57
|
-
init.callee.type === 'MemberExpression' &&
|
|
58
|
-
init.callee.object.type === 'Identifier' &&
|
|
59
|
-
(init.callee.object.name === 'Object' || init.callee.object.name === 'Array' ||
|
|
60
|
-
init.callee.object.name === 'JSON'))
|
|
61
|
-
return true;
|
|
62
|
-
}
|
|
63
|
-
if (def.type === 'FunctionName')
|
|
64
|
-
return true;
|
|
65
|
-
if (def.type === 'ClassName')
|
|
66
|
-
return true;
|
|
67
|
-
if (def.type === 'Parameter')
|
|
68
|
-
return true;
|
|
69
|
-
}
|
|
70
|
-
return false;
|
|
71
|
-
}
|
|
72
|
-
s = s.upper;
|
|
73
|
-
}
|
|
74
|
-
return false;
|
|
75
|
-
}
|
|
76
|
-
function hasNullCheck(node, sourceCode) {
|
|
77
|
-
if (node.optional) {
|
|
78
|
-
return true;
|
|
79
|
-
}
|
|
80
|
-
const parent = node.parent;
|
|
81
|
-
if (parent && parent.type === 'ChainExpression') {
|
|
82
|
-
return true;
|
|
83
|
-
}
|
|
84
|
-
if (usesNullishCoalescing(node)) {
|
|
85
|
-
return true;
|
|
86
|
-
}
|
|
87
|
-
const objectText = sourceCode.getText(node.object);
|
|
88
|
-
const immediateParent = parent;
|
|
89
|
-
const nodeOrCall = immediateParent?.type === 'CallExpression' &&
|
|
90
|
-
immediateParent.callee === node
|
|
91
|
-
? immediateParent
|
|
92
|
-
: node;
|
|
93
|
-
const andParent = nodeOrCall
|
|
94
|
-
.parent;
|
|
95
|
-
if (andParent?.type === 'LogicalExpression' &&
|
|
96
|
-
andParent.operator === '&&' &&
|
|
97
|
-
andParent.right === nodeOrCall) {
|
|
98
|
-
const leftText = sourceCode.getText(andParent.left);
|
|
99
|
-
if (leftText === objectText || leftText.endsWith(objectText))
|
|
100
|
-
return true;
|
|
101
|
-
}
|
|
102
|
-
let cur = node;
|
|
103
|
-
for (let depth = 0; depth < 8; depth++) {
|
|
104
|
-
const p = cur.parent;
|
|
105
|
-
if (!p)
|
|
106
|
-
break;
|
|
107
|
-
if (p.type === 'ConditionalExpression' &&
|
|
108
|
-
p.consequent === cur) {
|
|
109
|
-
if (sourceCode.getText(p.test) === objectText) {
|
|
110
|
-
return true;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
cur = p;
|
|
114
|
-
}
|
|
115
|
-
if (hasExplicitNullCheck(node, sourceCode)) {
|
|
116
|
-
return true;
|
|
117
|
-
}
|
|
118
|
-
return false;
|
|
119
|
-
}
|
|
120
|
-
function hasExplicitNullCheck(node, sourceCode) {
|
|
121
|
-
let current = node;
|
|
122
|
-
let depth = 0;
|
|
123
|
-
const maxDepth = 10;
|
|
124
|
-
while (current && depth < maxDepth) {
|
|
125
|
-
const parent = current
|
|
126
|
-
.parent;
|
|
127
|
-
if (parent && parent.type === 'IfStatement') {
|
|
128
|
-
const test = parent.test;
|
|
129
|
-
if (isNullCheckForObject(test, node.object, sourceCode)) {
|
|
130
|
-
return true;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
current = parent;
|
|
134
|
-
depth++;
|
|
135
|
-
}
|
|
136
|
-
return false;
|
|
137
|
-
}
|
|
138
|
-
function isNullCheckForObject(test, object, sourceCode) {
|
|
139
|
-
const objectText = sourceCode.getText(object);
|
|
140
|
-
if (test.type === 'Identifier' || test.type === 'MemberExpression') {
|
|
141
|
-
const testText = sourceCode.getText(test);
|
|
142
|
-
if (testText === objectText || objectText.startsWith(testText + '.')) {
|
|
143
|
-
return true;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
if (test.type === 'BinaryExpression') {
|
|
147
|
-
const { left, right, operator } = test;
|
|
148
|
-
if (operator === '!==' ||
|
|
149
|
-
operator === '!=' ||
|
|
150
|
-
operator === '===' ||
|
|
151
|
-
operator === '==') {
|
|
152
|
-
const leftText = sourceCode.getText(left);
|
|
153
|
-
const rightText = sourceCode.getText(right);
|
|
154
|
-
if ((leftText === objectText &&
|
|
155
|
-
(rightText === 'null' || rightText === 'undefined')) ||
|
|
156
|
-
(rightText === objectText &&
|
|
157
|
-
(leftText === 'null' || leftText === 'undefined'))) {
|
|
158
|
-
return true;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
if (test.type === 'LogicalExpression') {
|
|
163
|
-
return (isNullCheckForObject(test.left, object, sourceCode) ||
|
|
164
|
-
isNullCheckForObject(test.right, object, sourceCode));
|
|
165
|
-
}
|
|
166
|
-
return false;
|
|
167
|
-
}
|
|
168
|
-
function usesNullishCoalescing(node) {
|
|
169
|
-
let current = node;
|
|
170
|
-
let depth = 0;
|
|
171
|
-
const maxDepth = 5;
|
|
172
|
-
while (current && depth < maxDepth) {
|
|
173
|
-
const parent = current
|
|
174
|
-
.parent;
|
|
175
|
-
if (parent &&
|
|
176
|
-
parent.type === 'LogicalExpression' &&
|
|
177
|
-
parent.operator === '??') {
|
|
178
|
-
return true;
|
|
179
|
-
}
|
|
180
|
-
current = parent;
|
|
181
|
-
depth++;
|
|
182
|
-
}
|
|
183
|
-
return false;
|
|
184
|
-
}
|
|
185
|
-
exports.noMissingNullChecks = (0, eslint_devkit_2.createRule)({
|
|
186
|
-
name: 'no-missing-null-checks',
|
|
187
|
-
meta: {
|
|
188
|
-
type: 'problem',
|
|
189
|
-
docs: {
|
|
190
|
-
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-reliability/docs/rules/no-missing-null-checks.md',
|
|
191
|
-
description: 'Detects potential null pointer dereferences',
|
|
192
|
-
cwe: 'CWE-476',
|
|
193
|
-
cvss: 7.5,
|
|
194
|
-
},
|
|
195
|
-
hasSuggestions: true,
|
|
196
|
-
messages: {
|
|
197
|
-
missingNullCheck: (0, eslint_devkit_1.formatLLMMessage)({
|
|
198
|
-
icon: eslint_devkit_1.MessageIcons.WARNING,
|
|
199
|
-
issueName: 'Missing null check',
|
|
200
|
-
cwe: 'CWE-476',
|
|
201
|
-
description: 'Potential null/undefined dereference detected',
|
|
202
|
-
severity: 'HIGH',
|
|
203
|
-
fix: 'Use optional chaining (?.) or add explicit null check',
|
|
204
|
-
documentationLink: 'https://rules.sonarsource.com/javascript/RSPEC-2259/',
|
|
205
|
-
}),
|
|
206
|
-
useOptionalChaining: (0, eslint_devkit_1.formatLLMMessage)({
|
|
207
|
-
icon: eslint_devkit_1.MessageIcons.INFO,
|
|
208
|
-
issueName: 'Use Optional Chaining',
|
|
209
|
-
description: 'Use optional chaining operator',
|
|
210
|
-
severity: 'LOW',
|
|
211
|
-
fix: 'obj?.property?.method()',
|
|
212
|
-
documentationLink: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining',
|
|
213
|
-
}),
|
|
214
|
-
useNullishCoalescing: (0, eslint_devkit_1.formatLLMMessage)({
|
|
215
|
-
icon: eslint_devkit_1.MessageIcons.INFO,
|
|
216
|
-
issueName: 'Use Nullish Coalescing',
|
|
217
|
-
description: 'Use nullish coalescing operator',
|
|
218
|
-
severity: 'LOW',
|
|
219
|
-
fix: 'value ?? defaultValue',
|
|
220
|
-
documentationLink: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing',
|
|
221
|
-
}),
|
|
222
|
-
addExplicitCheck: (0, eslint_devkit_1.formatLLMMessage)({
|
|
223
|
-
icon: eslint_devkit_1.MessageIcons.INFO,
|
|
224
|
-
issueName: 'Add Explicit Check',
|
|
225
|
-
description: 'Add explicit null check',
|
|
226
|
-
severity: 'LOW',
|
|
227
|
-
fix: 'if (obj !== null) { obj.property }',
|
|
228
|
-
documentationLink: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/null',
|
|
229
|
-
}),
|
|
230
|
-
},
|
|
231
|
-
schema: [
|
|
232
|
-
{
|
|
233
|
-
type: 'object',
|
|
234
|
-
properties: {
|
|
235
|
-
ignoreInTests: {
|
|
236
|
-
type: 'boolean',
|
|
237
|
-
default: true,
|
|
238
|
-
description: 'Ignore in test files',
|
|
239
|
-
},
|
|
240
|
-
requireExplicitChecks: {
|
|
241
|
-
type: 'boolean',
|
|
242
|
-
default: false,
|
|
243
|
-
description: 'Require explicit null checks',
|
|
244
|
-
},
|
|
245
|
-
},
|
|
246
|
-
additionalProperties: false,
|
|
247
|
-
},
|
|
248
|
-
],
|
|
249
|
-
},
|
|
250
|
-
defaultOptions: [
|
|
251
|
-
{
|
|
252
|
-
ignoreInTests: true,
|
|
253
|
-
requireExplicitChecks: false,
|
|
254
|
-
},
|
|
255
|
-
],
|
|
256
|
-
create(context, [options = {}]) {
|
|
257
|
-
const { ignoreInTests = true, } = options || {};
|
|
258
|
-
const filename = context.filename;
|
|
259
|
-
const isTestFile = ignoreInTests && /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);
|
|
260
|
-
if (isTestFile) {
|
|
261
|
-
return {};
|
|
262
|
-
}
|
|
263
|
-
const sourceCode = context.sourceCode;
|
|
264
|
-
const reportedMemberExpressions = new Set();
|
|
265
|
-
function getMemberExpressionKey(node) {
|
|
266
|
-
if (node.range && Array.isArray(node.range) && node.range.length >= 2) {
|
|
267
|
-
return `me-${node.range[0]}-${node.range[1]}`;
|
|
268
|
-
}
|
|
269
|
-
const loc = node
|
|
270
|
-
.loc;
|
|
271
|
-
if (loc && loc.start) {
|
|
272
|
-
return `me-${loc.start.line}-${loc.start.column}-${loc.end?.line || loc.start.line}-${loc.end?.column || loc.start.column}`;
|
|
273
|
-
}
|
|
274
|
-
return `me-${JSON.stringify(node).slice(0, 50)}`;
|
|
275
|
-
}
|
|
276
|
-
function checkMemberExpression(node) {
|
|
277
|
-
if (node.optional) {
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
const parent = node
|
|
281
|
-
.parent;
|
|
282
|
-
if (parent && parent.type === 'ChainExpression') {
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
if (parent &&
|
|
286
|
-
parent.type === 'MemberExpression' &&
|
|
287
|
-
parent.object === node) {
|
|
288
|
-
return;
|
|
289
|
-
}
|
|
290
|
-
const objectNode = node.object;
|
|
291
|
-
let shouldCheck = false;
|
|
292
|
-
if (objectNode.type === 'Identifier') {
|
|
293
|
-
if (isProvablyNonNullableIdentifier(objectNode, sourceCode.getScope(node))) {
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
296
|
-
shouldCheck = true;
|
|
297
|
-
}
|
|
298
|
-
else if (objectNode.type === 'MemberExpression') {
|
|
299
|
-
let base = objectNode;
|
|
300
|
-
while (base.type === 'MemberExpression') {
|
|
301
|
-
base = base.object;
|
|
302
|
-
}
|
|
303
|
-
if (base.type === 'ThisExpression')
|
|
304
|
-
return;
|
|
305
|
-
if (base.type === 'Identifier' &&
|
|
306
|
-
isProvablyNonNullableIdentifier(base, sourceCode.getScope(node))) {
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
shouldCheck = true;
|
|
310
|
-
}
|
|
311
|
-
if (shouldCheck && !hasNullCheck(node, sourceCode)) {
|
|
312
|
-
const nodeKey = getMemberExpressionKey(node);
|
|
313
|
-
if (reportedMemberExpressions.has(nodeKey)) {
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
try {
|
|
317
|
-
reportedMemberExpressions.add(nodeKey);
|
|
318
|
-
context.report({
|
|
319
|
-
node,
|
|
320
|
-
messageId: 'missingNullCheck',
|
|
321
|
-
suggest: [
|
|
322
|
-
{
|
|
323
|
-
messageId: 'useOptionalChaining',
|
|
324
|
-
fix: () => null,
|
|
325
|
-
},
|
|
326
|
-
{
|
|
327
|
-
messageId: 'useNullishCoalescing',
|
|
328
|
-
fix: () => null,
|
|
329
|
-
},
|
|
330
|
-
{
|
|
331
|
-
messageId: 'addExplicitCheck',
|
|
332
|
-
fix: () => null,
|
|
333
|
-
},
|
|
334
|
-
],
|
|
335
|
-
});
|
|
336
|
-
}
|
|
337
|
-
catch {
|
|
338
|
-
return;
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
function checkCallExpression(node) {
|
|
343
|
-
if (node.type !== 'CallExpression') {
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
if (node.callee.type === 'MemberExpression') {
|
|
347
|
-
const memberExpr = node.callee;
|
|
348
|
-
if (memberExpr.optional) {
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
|
-
const parent = memberExpr.parent;
|
|
352
|
-
if (parent && parent.type === 'ChainExpression') {
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
const objectNode = memberExpr.object;
|
|
356
|
-
let shouldCheck = false;
|
|
357
|
-
if (objectNode.type === 'Identifier') {
|
|
358
|
-
if (isProvablyNonNullableIdentifier(objectNode, sourceCode.getScope(memberExpr))) {
|
|
359
|
-
return;
|
|
360
|
-
}
|
|
361
|
-
shouldCheck = true;
|
|
362
|
-
}
|
|
363
|
-
else if (objectNode.type === 'MemberExpression') {
|
|
364
|
-
let base = objectNode;
|
|
365
|
-
while (base.type === 'MemberExpression') {
|
|
366
|
-
base = base.object;
|
|
367
|
-
}
|
|
368
|
-
if (base.type === 'ThisExpression')
|
|
369
|
-
return;
|
|
370
|
-
if (base.type === 'Identifier' &&
|
|
371
|
-
isProvablyNonNullableIdentifier(base, sourceCode.getScope(memberExpr))) {
|
|
372
|
-
return;
|
|
373
|
-
}
|
|
374
|
-
shouldCheck = true;
|
|
375
|
-
}
|
|
376
|
-
if (shouldCheck && !hasNullCheck(memberExpr, sourceCode)) {
|
|
377
|
-
const nodeKey = getMemberExpressionKey(memberExpr);
|
|
378
|
-
if (reportedMemberExpressions.has(nodeKey)) {
|
|
379
|
-
return;
|
|
380
|
-
}
|
|
381
|
-
try {
|
|
382
|
-
reportedMemberExpressions.add(nodeKey);
|
|
383
|
-
context.report({
|
|
384
|
-
node: memberExpr,
|
|
385
|
-
messageId: 'missingNullCheck',
|
|
386
|
-
suggest: [
|
|
387
|
-
{
|
|
388
|
-
messageId: 'useOptionalChaining',
|
|
389
|
-
fix: () => null,
|
|
390
|
-
},
|
|
391
|
-
{
|
|
392
|
-
messageId: 'useNullishCoalescing',
|
|
393
|
-
fix: () => null,
|
|
394
|
-
},
|
|
395
|
-
{
|
|
396
|
-
messageId: 'addExplicitCheck',
|
|
397
|
-
fix: () => null,
|
|
398
|
-
},
|
|
399
|
-
],
|
|
400
|
-
});
|
|
401
|
-
}
|
|
402
|
-
catch {
|
|
403
|
-
return;
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
return {
|
|
409
|
-
MemberExpression: checkMemberExpression,
|
|
410
|
-
CallExpression: checkCallExpression,
|
|
411
|
-
};
|
|
412
|
-
},
|
|
413
|
-
});
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noMissingNullChecks=void 0;exports.hasNullCheck=hasNullCheck;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");const NEVER_NULL_GLOBALS=new Set(["console","process","Buffer","__dirname","__filename","module","exports","require","navigator","location","history","Math","JSON","Object","Array","Number","String","Boolean","Date","RegExp","Promise","Symbol","Map","Set","WeakMap","WeakSet","Proxy","Reflect","Intl","BigInt","WebAssembly","Atomics","URL","URLSearchParams","TextEncoder","TextDecoder","AbortController","AbortSignal","EventTarget","Event","CustomEvent","FormData","Blob","File","FileReader","Headers","Request","Response","Error","TypeError","RangeError","SyntaxError","URIError","EvalError","ReferenceError","AggregateError","fetch","crypto","performance","queueMicrotask","setTimeout","clearTimeout","setInterval","clearInterval","setImmediate","clearImmediate","requestAnimationFrame","cancelAnimationFrame","logger","log","winston","pino","bunyan"]);function isProvablyNonNullableIdentifier(ident,scope){if(NEVER_NULL_GLOBALS.has(ident.name))return true;let s=scope;while(s){const variable=s.variables.find(v=>v.name===ident.name);if(variable){for(const def of variable.defs){if(def.type==="CatchClause")return true;if(def.type==="ImportBinding")return true;if(def.type==="Variable"&&def.node?.type==="VariableDeclarator"){const init=def.node.init;if(!init)continue;if(init.type==="NewExpression")return true;if(init.type==="ArrayExpression")return true;if(init.type==="ObjectExpression")return true;if(init.type==="TemplateLiteral")return true;if(init.type==="ClassExpression")return true;if(init.type==="Literal"&&init.value!==null)return true;if(init.type==="AwaitExpression"&&init.argument.type==="CallExpression"&&init.argument.callee.type==="Identifier"&&init.argument.callee.name==="fetch")return true;if(init.type==="CallExpression"&&init.callee.type==="MemberExpression"&&init.callee.object.type==="Identifier"&&(init.callee.object.name==="Object"||init.callee.object.name==="Array"||init.callee.object.name==="JSON"))return true}if(def.type==="FunctionName")return true;if(def.type==="ClassName")return true;if(def.type==="Parameter")return true}return false}s=s.upper}return false}function hasNullCheck(node,sourceCode){if(node.optional){return true}const parent=node.parent;if(parent&&parent.type==="ChainExpression"){return true}if(usesNullishCoalescing(node)){return true}const objectText=sourceCode.getText(node.object);const immediateParent=parent;const nodeOrCall=immediateParent?.type==="CallExpression"&&immediateParent.callee===node?immediateParent:node;const andParent=nodeOrCall.parent;if(andParent?.type==="LogicalExpression"&&andParent.operator==="&&"&&andParent.right===nodeOrCall){const leftText=sourceCode.getText(andParent.left);if(leftText===objectText||leftText.endsWith(objectText))return true}let cur=node;for(let depth=0;depth<8;depth++){const p=cur.parent;if(!p)break;if(p.type==="ConditionalExpression"&&p.consequent===cur){if(sourceCode.getText(p.test)===objectText){return true}}cur=p}if(hasExplicitNullCheck(node,sourceCode)){return true}return false}function hasExplicitNullCheck(node,sourceCode){let current=node;let depth=0;const maxDepth=10;while(current&&depth<maxDepth){const parent=current.parent;if(parent&&parent.type==="IfStatement"){const test=parent.test;if(isNullCheckForObject(test,node.object,sourceCode)){return true}}current=parent;depth++}return false}function isNullCheckForObject(test,object,sourceCode){const objectText=sourceCode.getText(object);if(test.type==="Identifier"||test.type==="MemberExpression"){const testText=sourceCode.getText(test);if(testText===objectText||objectText.startsWith(testText+".")){return true}}if(test.type==="BinaryExpression"){const{left,right,operator}=test;if(operator==="!=="||operator==="!="||operator==="==="||operator==="=="){const leftText=sourceCode.getText(left);const rightText=sourceCode.getText(right);if(leftText===objectText&&(rightText==="null"||rightText==="undefined")||rightText===objectText&&(leftText==="null"||leftText==="undefined")){return true}}}if(test.type==="LogicalExpression"){return isNullCheckForObject(test.left,object,sourceCode)||isNullCheckForObject(test.right,object,sourceCode)}return false}function usesNullishCoalescing(node){let current=node;let depth=0;const maxDepth=5;while(current&&depth<maxDepth){const parent=current.parent;if(parent&&parent.type==="LogicalExpression"&&parent.operator==="??"){return true}current=parent;depth++}return false}exports.noMissingNullChecks=(0,eslint_devkit_2.createRule)({name:"no-missing-null-checks",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-reliability/docs/rules/no-missing-null-checks.md",description:"Detects potential null pointer dereferences",cwe:"CWE-476",cvss:7.5},hasSuggestions:true,messages:{missingNullCheck:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Missing null check",cwe:"CWE-476",description:"Potential null/undefined dereference detected",severity:"HIGH",fix:"Use optional chaining (?.) or add explicit null check",documentationLink:"https://rules.sonarsource.com/javascript/RSPEC-2259/"}),useOptionalChaining:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Optional Chaining",description:"Use optional chaining operator",severity:"LOW",fix:"obj?.property?.method()",documentationLink:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining"}),useNullishCoalescing:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Nullish Coalescing",description:"Use nullish coalescing operator",severity:"LOW",fix:"value ?? defaultValue",documentationLink:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing"}),addExplicitCheck:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Add Explicit Check",description:"Add explicit null check",severity:"LOW",fix:"if (obj !== null) { obj.property }",documentationLink:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/null"})},schema:[{type:"object",properties:{ignoreInTests:{type:"boolean",default:true,description:"Ignore in test files"},requireExplicitChecks:{type:"boolean",default:false,description:"Require explicit null checks"}},additionalProperties:false}]},defaultOptions:[{ignoreInTests:true,requireExplicitChecks:false}],create(context,[options={}]){const{ignoreInTests=true}=options||{};const filename=context.filename;const isTestFile=ignoreInTests&&/\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);if(isTestFile){return{}}const sourceCode=context.sourceCode;const reportedMemberExpressions=new Set;function getMemberExpressionKey(node){if(node.range&&Array.isArray(node.range)&&node.range.length>=2){return`me-${node.range[0]}-${node.range[1]}`}const loc=node.loc;if(loc&&loc.start){return`me-${loc.start.line}-${loc.start.column}-${loc.end?.line||loc.start.line}-${loc.end?.column||loc.start.column}`}return`me-${JSON.stringify(node).slice(0,50)}`}function checkMemberExpression(node){if(node.optional){return}const parent=node.parent;if(parent&&parent.type==="ChainExpression"){return}if(parent&&parent.type==="MemberExpression"&&parent.object===node){return}const objectNode=node.object;let shouldCheck=false;if(objectNode.type==="Identifier"){if(isProvablyNonNullableIdentifier(objectNode,sourceCode.getScope(node))){return}shouldCheck=true}else if(objectNode.type==="MemberExpression"){let base=objectNode;while(base.type==="MemberExpression"){base=base.object}if(base.type==="ThisExpression")return;if(base.type==="Identifier"&&isProvablyNonNullableIdentifier(base,sourceCode.getScope(node))){return}shouldCheck=true}if(shouldCheck&&!hasNullCheck(node,sourceCode)){const nodeKey=getMemberExpressionKey(node);if(reportedMemberExpressions.has(nodeKey)){return}try{reportedMemberExpressions.add(nodeKey);context.report({node,messageId:"missingNullCheck",suggest:[{messageId:"useOptionalChaining",fix:()=>null},{messageId:"useNullishCoalescing",fix:()=>null},{messageId:"addExplicitCheck",fix:()=>null}]})}catch{return}}}function checkCallExpression(node){if(node.type!=="CallExpression"){return}if(node.callee.type==="MemberExpression"){const memberExpr=node.callee;if(memberExpr.optional){return}const parent=memberExpr.parent;if(parent&&parent.type==="ChainExpression"){return}const objectNode=memberExpr.object;let shouldCheck=false;if(objectNode.type==="Identifier"){if(isProvablyNonNullableIdentifier(objectNode,sourceCode.getScope(memberExpr))){return}shouldCheck=true}else if(objectNode.type==="MemberExpression"){let base=objectNode;while(base.type==="MemberExpression"){base=base.object}if(base.type==="ThisExpression")return;if(base.type==="Identifier"&&isProvablyNonNullableIdentifier(base,sourceCode.getScope(memberExpr))){return}shouldCheck=true}if(shouldCheck&&!hasNullCheck(memberExpr,sourceCode)){const nodeKey=getMemberExpressionKey(memberExpr);if(reportedMemberExpressions.has(nodeKey)){return}try{reportedMemberExpressions.add(nodeKey);context.report({node:memberExpr,messageId:"missingNullCheck",suggest:[{messageId:"useOptionalChaining",fix:()=>null},{messageId:"useNullishCoalescing",fix:()=>null},{messageId:"addExplicitCheck",fix:()=>null}]})}catch{return}}}}return{MemberExpression:checkMemberExpression,CallExpression:checkCallExpression}}});
|