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,351 @@
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.noUnhandledPromise = void 0;
9
+ const eslint_devkit_1 = require("@interlace/eslint-devkit");
10
+ const eslint_devkit_2 = require("@interlace/eslint-devkit");
11
+ /**
12
+ * Check if a node is a Promise-like expression
13
+ * For now, we check all CallExpressions since we can't statically determine
14
+ * which functions return promises. The isPromiseHandled function will filter out
15
+ * non-promise calls that are inside handled promise chains.
16
+ */
17
+ function isPromiseExpression(node) {
18
+ // Function calls that might return promises
19
+ if (node.type === 'CallExpression') {
20
+ return true;
21
+ }
22
+ // Await expressions (already handled)
23
+ if (node.type === 'AwaitExpression') {
24
+ return false; // Already handled
25
+ }
26
+ return false;
27
+ }
28
+ /**
29
+ * Check if a CallExpression is inside a promise chain callback
30
+ */
31
+ function isInsidePromiseCallback(node) {
32
+ let current = node;
33
+ let depth = 0;
34
+ const maxDepth = 10;
35
+ while (current && depth < maxDepth) {
36
+ const parent = current
37
+ .parent;
38
+ if (!parent)
39
+ break;
40
+ // Check if we're inside an arrow function or function expression
41
+ if (parent.type === 'ArrowFunctionExpression' ||
42
+ parent.type === 'FunctionExpression') {
43
+ // Check if this function is an argument to a promise method (.then, .catch, .finally)
44
+ const funcParent = parent
45
+ .parent;
46
+ if (funcParent &&
47
+ funcParent.type === 'CallExpression' &&
48
+ funcParent.callee &&
49
+ funcParent.callee.type === 'MemberExpression') {
50
+ const memberExpr = funcParent.callee;
51
+ if (memberExpr.property.type === 'Identifier') {
52
+ const methodName = memberExpr.property.name;
53
+ if (methodName === 'then' ||
54
+ methodName === 'catch' ||
55
+ methodName === 'finally') {
56
+ // We're inside a promise chain callback
57
+ return true;
58
+ }
59
+ }
60
+ }
61
+ }
62
+ current = parent;
63
+ depth++;
64
+ }
65
+ return false;
66
+ }
67
+ /**
68
+ * Check if promise is handled (has .catch, .then, or is in try/catch)
69
+ */
70
+ function isPromiseHandled(node) {
71
+ // For identifiers, check if they're used in a promise chain
72
+ if (node.type === 'Identifier') {
73
+ const parent = node.parent;
74
+ if (parent &&
75
+ parent.type === 'MemberExpression' &&
76
+ parent.object === node) {
77
+ if (parent.property.type === 'Identifier') {
78
+ const methodName = parent.property.name;
79
+ if (methodName === 'catch' ||
80
+ methodName === 'then' ||
81
+ methodName === 'finally') {
82
+ // Check if this MemberExpression is used as a callee (called)
83
+ const memberParent = parent.parent;
84
+ if (memberParent &&
85
+ memberParent.type === 'CallExpression' &&
86
+ memberParent.callee === parent) {
87
+ // Promise is handled by .then(), .catch(), or .finally()
88
+ return true;
89
+ }
90
+ }
91
+ }
92
+ }
93
+ }
94
+ // For CallExpressions, traverse up the AST to find if this promise is part of a handled chain
95
+ let current = node;
96
+ let depth = 0;
97
+ const maxDepth = 10;
98
+ while (current && depth < maxDepth) {
99
+ const parent = current
100
+ .parent;
101
+ if (!parent)
102
+ break;
103
+ // Check if parent is a MemberExpression with .catch/.then/.finally
104
+ if (parent.type === 'MemberExpression' && parent.object === current) {
105
+ if (parent.property.type === 'Identifier') {
106
+ const methodName = parent.property.name;
107
+ if (methodName === 'catch' ||
108
+ methodName === 'then' ||
109
+ methodName === 'finally') {
110
+ // Check if this MemberExpression is used as a callee (called)
111
+ const memberParent = parent.parent;
112
+ if (memberParent &&
113
+ memberParent.type === 'CallExpression' &&
114
+ memberParent.callee === parent) {
115
+ // Promise is handled by .then(), .catch(), or .finally()
116
+ return true;
117
+ }
118
+ }
119
+ }
120
+ }
121
+ // Check if in try/catch block
122
+ if (parent.type === 'TryStatement') {
123
+ return true;
124
+ }
125
+ // Check if in await expression
126
+ if (parent.type === 'AwaitExpression') {
127
+ return true;
128
+ }
129
+ current = parent;
130
+ depth++;
131
+ }
132
+ return false;
133
+ }
134
+ exports.noUnhandledPromise = (0, eslint_devkit_2.createRule)({
135
+ name: 'no-unhandled-promise',
136
+ meta: {
137
+ type: 'problem',
138
+ docs: {
139
+ description: 'Detects unhandled Promise rejections',
140
+ },
141
+ hasSuggestions: true,
142
+ messages: {
143
+ unhandledPromise: (0, eslint_devkit_1.formatLLMMessage)({
144
+ icon: eslint_devkit_1.MessageIcons.WARNING,
145
+ issueName: 'Unhandled promise',
146
+ cwe: 'CWE-1024',
147
+ description: 'Unhandled Promise rejection detected',
148
+ severity: 'HIGH',
149
+ fix: 'Add .catch() handler or use try/catch with await',
150
+ documentationLink: 'https://rules.sonarsource.com/javascript/RSPEC-4635/',
151
+ }),
152
+ addCatch: (0, eslint_devkit_1.formatLLMMessage)({
153
+ icon: eslint_devkit_1.MessageIcons.INFO,
154
+ issueName: 'Add catch handler',
155
+ description: 'Add .catch() handler to promise',
156
+ severity: 'LOW',
157
+ fix: 'promise.catch(error => console.error(error))',
158
+ documentationLink: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch',
159
+ }),
160
+ useTryCatch: (0, eslint_devkit_1.formatLLMMessage)({
161
+ icon: eslint_devkit_1.MessageIcons.INFO,
162
+ issueName: 'Use try/catch',
163
+ description: 'Use try/catch with await',
164
+ severity: 'LOW',
165
+ fix: 'try { await promise; } catch (error) { handle(error); }',
166
+ documentationLink: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch',
167
+ }),
168
+ useAwait: (0, eslint_devkit_1.formatLLMMessage)({
169
+ icon: eslint_devkit_1.MessageIcons.INFO,
170
+ issueName: 'Use await',
171
+ description: 'Use await to handle promise',
172
+ severity: 'LOW',
173
+ fix: 'await promise;',
174
+ documentationLink: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await',
175
+ }),
176
+ },
177
+ schema: [
178
+ {
179
+ type: 'object',
180
+ properties: {
181
+ ignoreInTests: {
182
+ type: 'boolean',
183
+ default: true,
184
+ description: 'Ignore promises in test files',
185
+ },
186
+ ignoreVoidExpressions: {
187
+ type: 'boolean',
188
+ default: false,
189
+ description: 'Ignore promises in void expressions',
190
+ },
191
+ },
192
+ additionalProperties: false,
193
+ },
194
+ ],
195
+ },
196
+ defaultOptions: [
197
+ {
198
+ ignoreInTests: true,
199
+ ignoreVoidExpressions: false,
200
+ },
201
+ ],
202
+ create(context, [options = {}]) {
203
+ const { ignoreInTests = true, ignoreVoidExpressions = false } = options || {};
204
+ const filename = context.getFilename();
205
+ const isTestFile = ignoreInTests && /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);
206
+ if (isTestFile) {
207
+ return {};
208
+ }
209
+ // const sourceCode = context.sourceCode || context.sourceCode; // Not used
210
+ /**
211
+ * Check call expressions for unhandled promises
212
+ */
213
+ function checkCallExpression(node) {
214
+ // Skip CallExpressions that are inside promise chain callbacks
215
+ if (isInsidePromiseCallback(node)) {
216
+ return;
217
+ }
218
+ // Skip calls to promise methods (.then, .catch, .finally) as they are handled by definition
219
+ // But only if they have meaningful callbacks
220
+ if (node.callee.type === 'MemberExpression' &&
221
+ node.callee.property.type === 'Identifier') {
222
+ const methodName = node.callee.property.name;
223
+ if (methodName === 'then' ||
224
+ methodName === 'catch' ||
225
+ methodName === 'finally') {
226
+ // Check if the callback is empty or meaningless
227
+ if (node.arguments.length > 0 &&
228
+ node.arguments[0].type === 'ArrowFunctionExpression') {
229
+ const callback = node.arguments[0];
230
+ if (callback.body.type === 'BlockStatement' &&
231
+ callback.body.body.length === 0) {
232
+ // Empty callback - don't skip, this should be flagged
233
+ }
234
+ else {
235
+ return; // Has meaningful callback, skip
236
+ }
237
+ }
238
+ else {
239
+ return; // Not an arrow function callback, assume it's handled
240
+ }
241
+ }
242
+ }
243
+ // Check if it's a promise-returning function
244
+ if (!isPromiseExpression(node)) {
245
+ return;
246
+ }
247
+ // Check if it's already handled
248
+ if (isPromiseHandled(node)) {
249
+ return;
250
+ }
251
+ // Check if it's in a void expression
252
+ if (ignoreVoidExpressions) {
253
+ const parent = node
254
+ .parent;
255
+ if (parent &&
256
+ parent.type === 'UnaryExpression' &&
257
+ parent.operator === 'void') {
258
+ return;
259
+ }
260
+ }
261
+ // Skip if this CallExpression is an argument to another CallExpression
262
+ // (e.g., console.log(fetch(url)) - we don't want to flag fetch(url) here)
263
+ const parent = node
264
+ .parent;
265
+ if (parent && parent.type === 'CallExpression') {
266
+ // Only skip if it's not part of a promise chain
267
+ // If it's the object of a MemberExpression with .then/.catch/.finally, it's a promise
268
+ const grandParent = parent.parent;
269
+ if (!(grandParent &&
270
+ grandParent.type === 'MemberExpression' &&
271
+ grandParent.object === parent &&
272
+ grandParent.property.type === 'Identifier' &&
273
+ (grandParent.property.name === 'then' ||
274
+ grandParent.property.name === 'catch' ||
275
+ grandParent.property.name === 'finally'))) {
276
+ return;
277
+ }
278
+ }
279
+ context.report({
280
+ node,
281
+ messageId: 'unhandledPromise',
282
+ suggest: [
283
+ {
284
+ messageId: 'addCatch',
285
+ fix: () => null, // Cannot auto-fix without context
286
+ },
287
+ {
288
+ messageId: 'useTryCatch',
289
+ fix: () => null,
290
+ },
291
+ {
292
+ messageId: 'useAwait',
293
+ fix: () => null,
294
+ },
295
+ ],
296
+ });
297
+ }
298
+ /**
299
+ * Check identifier expressions for unhandled promises
300
+ * Note: Currently unused, keeping for future implementation
301
+ */
302
+ /*
303
+ function checkIdentifier(node: TSESTree.Identifier) {
304
+ // Skip identifiers that are inside promise chain callbacks
305
+ if (isInsidePromiseCallback({ type: 'CallExpression', callee: node, arguments: [], optional: false } as TSESTree.CallExpression)) {
306
+ return;
307
+ }
308
+
309
+ // Check if it's a promise-like identifier
310
+ if (!isPromiseExpression(node)) {
311
+ return;
312
+ }
313
+
314
+ // Check if it's already handled
315
+ if (isPromiseHandled(node)) {
316
+ return;
317
+ }
318
+
319
+ // Check if it's in a void expression
320
+ if (ignoreVoidExpressions) {
321
+ const parent = (node as TSESTree.Node & { parent?: TSESTree.Node }).parent;
322
+ if (parent && parent.type === 'UnaryExpression' && parent.operator === 'void') {
323
+ return;
324
+ }
325
+ }
326
+
327
+ context.report({
328
+ node,
329
+ messageId: 'unhandledPromise',
330
+ suggest: [
331
+ {
332
+ messageId: 'addCatch',
333
+ fix: () => null,
334
+ },
335
+ {
336
+ messageId: 'useTryCatch',
337
+ fix: () => null,
338
+ },
339
+ {
340
+ messageId: 'useAwait',
341
+ fix: () => null,
342
+ },
343
+ ],
344
+ });
345
+ }
346
+ */
347
+ return {
348
+ CallExpression: checkCallExpression,
349
+ };
350
+ },
351
+ });
@@ -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-await-in-loop
8
+ * Disallow await inside loops without considering concurrency implications (unicorn-inspired)
9
+ */
10
+ import type { TSESLint } from '@interlace/eslint-devkit';
11
+ type MessageIds = 'awaitInLoop' | 'suggestPromiseAll' | 'suggestConcurrent' | 'considerSequential' | 'asyncLoopPattern';
12
+ export interface Options {
13
+ /** Allow await in for-of loops */
14
+ allowForOf?: boolean;
15
+ /** Allow await in while loops */
16
+ allowWhile?: boolean;
17
+ /** Check for potential concurrent execution opportunities */
18
+ checkConcurrency?: boolean;
19
+ }
20
+ type RuleOptions = [Options?];
21
+ export declare const noAwaitInLoop: TSESLint.RuleModule<MessageIds, RuleOptions, unknown, TSESLint.RuleListener> & {
22
+ name: string;
23
+ };
24
+ export {};
@@ -0,0 +1,234 @@
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.noAwaitInLoop = void 0;
9
+ const eslint_devkit_1 = require("@interlace/eslint-devkit");
10
+ const eslint_devkit_2 = require("@interlace/eslint-devkit");
11
+ exports.noAwaitInLoop = (0, eslint_devkit_1.createRule)({
12
+ name: 'no-await-in-loop',
13
+ meta: {
14
+ type: 'suggestion',
15
+ docs: {
16
+ description: 'Disallow await inside loops and suggest appropriate concurrency patterns',
17
+ },
18
+ hasSuggestions: false,
19
+ messages: {
20
+ awaitInLoop: (0, eslint_devkit_2.formatLLMMessage)({
21
+ icon: eslint_devkit_2.MessageIcons.WARNING,
22
+ issueName: 'Sequential Async Loop',
23
+ description: 'Await in loop forces sequential execution',
24
+ severity: 'MEDIUM',
25
+ fix: 'Consider Promise.all() for concurrent execution or extract async logic',
26
+ documentationLink: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function',
27
+ }),
28
+ suggestPromiseAll: (0, eslint_devkit_2.formatLLMMessage)({
29
+ icon: eslint_devkit_2.MessageIcons.INFO,
30
+ issueName: 'Use Promise.all',
31
+ description: 'Concurrent execution of independent operations',
32
+ severity: 'LOW',
33
+ fix: 'Promise.all(items.map(async (item) => await process(item)))',
34
+ documentationLink: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all',
35
+ }),
36
+ suggestConcurrent: (0, eslint_devkit_2.formatLLMMessage)({
37
+ icon: eslint_devkit_2.MessageIcons.INFO,
38
+ issueName: 'Use Promise.allSettled',
39
+ description: 'Concurrent execution with error handling',
40
+ severity: 'LOW',
41
+ fix: 'Promise.allSettled(items.map(async (item) => await process(item)))',
42
+ documentationLink: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled',
43
+ }),
44
+ considerSequential: (0, eslint_devkit_2.formatLLMMessage)({
45
+ icon: eslint_devkit_2.MessageIcons.INFO,
46
+ issueName: 'Sequential Control',
47
+ description: 'Operations require sequential execution',
48
+ severity: 'LOW',
49
+ fix: 'Add concurrency control or extract to async function',
50
+ documentationLink: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function',
51
+ }),
52
+ asyncLoopPattern: (0, eslint_devkit_2.formatLLMMessage)({
53
+ icon: eslint_devkit_2.MessageIcons.INFO,
54
+ issueName: 'Async Loop Pattern',
55
+ description: 'Use controlled concurrency library',
56
+ severity: 'LOW',
57
+ fix: 'Consider p-map, p-series, or similar for controlled concurrency',
58
+ documentationLink: 'https://github.com/sindresorhus/p-map',
59
+ }),
60
+ },
61
+ schema: [
62
+ {
63
+ type: 'object',
64
+ properties: {
65
+ allowForOf: {
66
+ type: 'boolean',
67
+ default: false,
68
+ },
69
+ allowWhile: {
70
+ type: 'boolean',
71
+ default: false,
72
+ },
73
+ checkConcurrency: {
74
+ type: 'boolean',
75
+ default: true,
76
+ },
77
+ },
78
+ additionalProperties: false,
79
+ },
80
+ ],
81
+ },
82
+ defaultOptions: [{ allowForOf: false, allowWhile: false, checkConcurrency: true }],
83
+ create(context) {
84
+ const [options] = context.options;
85
+ const { allowForOf = false, allowWhile = false } = options || {};
86
+ function analyzeLoop(node, loopType) {
87
+ // Skip allowed loop types
88
+ if ((loopType === 'ForOfStatement' && allowForOf) ||
89
+ (loopType === 'WhileStatement' && allowWhile)) {
90
+ return;
91
+ }
92
+ // Find await expressions within the loop
93
+ const awaitExpressions = [];
94
+ // Properties to skip to avoid circular references
95
+ const skipProperties = new Set(['parent', 'tokens', 'comments', 'loc', 'range']);
96
+ function findAwaits(currentNode) {
97
+ if (currentNode.type === 'AwaitExpression') {
98
+ awaitExpressions.push(currentNode);
99
+ }
100
+ // Don't traverse into nested functions (different scope)
101
+ if (currentNode.type !== 'FunctionDeclaration' &&
102
+ currentNode.type !== 'FunctionExpression' &&
103
+ currentNode.type !== 'ArrowFunctionExpression') {
104
+ for (const key in currentNode) {
105
+ if (skipProperties.has(key))
106
+ continue;
107
+ const child = currentNode[key];
108
+ if (Array.isArray(child)) {
109
+ child.forEach(item => {
110
+ if (item && typeof item === 'object' && 'type' in item) {
111
+ findAwaits(item);
112
+ }
113
+ });
114
+ }
115
+ else if (child && typeof child === 'object' && 'type' in child) {
116
+ findAwaits(child);
117
+ }
118
+ }
119
+ }
120
+ }
121
+ findAwaits(node);
122
+ if (awaitExpressions.length > 0) {
123
+ // Analyze the loop context to determine the best suggestion
124
+ const loopContext = analyzeLoopContext(node, loopType);
125
+ for (const awaitExpr of awaitExpressions) {
126
+ context.report({
127
+ node: awaitExpr,
128
+ messageId: 'awaitInLoop',
129
+ data: {
130
+ loopType: loopContext.loopType,
131
+ operations: loopContext.operationCount,
132
+ suggestion: getSuggestion(loopContext),
133
+ performance: loopContext.estimatedPerformance,
134
+ },
135
+ });
136
+ }
137
+ }
138
+ }
139
+ function analyzeLoopContext(node, loopType) {
140
+ const context = {
141
+ loopType,
142
+ operationCount: 1,
143
+ hasDependencies: false,
144
+ isSequential: true,
145
+ hasSideEffects: false,
146
+ estimatedPerformance: 'unknown',
147
+ operations: [],
148
+ node,
149
+ };
150
+ // Properties to skip to avoid circular references
151
+ const skipProps = new Set(['parent', 'tokens', 'comments', 'loc', 'range']);
152
+ // Analyze what operations are being performed in the loop
153
+ function analyzeOperations(currentNode) {
154
+ if (currentNode.type === 'CallExpression') {
155
+ if (currentNode.callee.type === 'Identifier') {
156
+ context.operations.push(currentNode.callee.name);
157
+ }
158
+ else if (currentNode.callee.type === 'MemberExpression' &&
159
+ currentNode.callee.property.type === 'Identifier') {
160
+ context.operations.push(currentNode.callee.property.name);
161
+ }
162
+ }
163
+ // Check for dependencies between iterations
164
+ if (currentNode.type === 'AssignmentExpression' ||
165
+ currentNode.type === 'UpdateExpression') {
166
+ context.hasDependencies = true;
167
+ }
168
+ // Check for side effects that might require sequential execution
169
+ if (currentNode.type === 'CallExpression' &&
170
+ (context.operations.includes('push') ||
171
+ context.operations.includes('splice') ||
172
+ context.operations.includes('delete'))) {
173
+ context.hasSideEffects = true;
174
+ }
175
+ // Count operations
176
+ if (currentNode.type === 'AwaitExpression') {
177
+ context.operationCount++;
178
+ }
179
+ // Recursively analyze
180
+ for (const key in currentNode) {
181
+ if (skipProps.has(key))
182
+ continue;
183
+ const child = currentNode[key];
184
+ if (Array.isArray(child)) {
185
+ child.forEach(item => {
186
+ if (item && typeof item === 'object' && 'type' in item) {
187
+ analyzeOperations(item);
188
+ }
189
+ });
190
+ }
191
+ else if (child && typeof child === 'object' && 'type' in child) {
192
+ analyzeOperations(child);
193
+ }
194
+ }
195
+ }
196
+ analyzeOperations(node);
197
+ // Determine if operations can be concurrent
198
+ if (context.hasDependencies || context.hasSideEffects) {
199
+ context.isSequential = true;
200
+ context.estimatedPerformance = `sequential (${context.operationCount} operations)`;
201
+ }
202
+ else {
203
+ context.isSequential = false;
204
+ context.estimatedPerformance = `potential ${context.operationCount}x speedup with concurrency`;
205
+ }
206
+ return context;
207
+ }
208
+ function getSuggestion(context) {
209
+ if (context.isSequential) {
210
+ return 'operations may need to be sequential - consider if concurrency is safe';
211
+ }
212
+ else {
213
+ return 'operations appear independent - consider Promise.all() for concurrency';
214
+ }
215
+ }
216
+ return {
217
+ ForStatement(node) {
218
+ analyzeLoop(node, 'ForStatement');
219
+ },
220
+ ForInStatement(node) {
221
+ analyzeLoop(node, 'ForInStatement');
222
+ },
223
+ ForOfStatement(node) {
224
+ analyzeLoop(node, 'ForOfStatement');
225
+ },
226
+ WhileStatement(node) {
227
+ analyzeLoop(node, 'WhileStatement');
228
+ },
229
+ DoWhileStatement(node) {
230
+ analyzeLoop(node, 'DoWhileStatement');
231
+ },
232
+ };
233
+ },
234
+ });
@@ -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-null-checks
8
+ * Detects potential null pointer dereferences
9
+ * CWE-476: NULL Pointer Dereference
10
+ *
11
+ * @see https://cwe.mitre.org/data/definitions/476.html
12
+ * @see https://rules.sonarsource.com/javascript/RSPEC-2259/
13
+ */
14
+ import type { TSESLint } from '@interlace/eslint-devkit';
15
+ type MessageIds = 'missingNullCheck' | 'useOptionalChaining' | 'useNullishCoalescing' | 'addExplicitCheck';
16
+ export interface Options {
17
+ /** Ignore in test files. Default: true */
18
+ ignoreInTests?: boolean;
19
+ /** Require explicit null checks. Default: false */
20
+ requireExplicitChecks?: boolean;
21
+ }
22
+ type RuleOptions = [Options?];
23
+ export declare const noMissingNullChecks: TSESLint.RuleModule<MessageIds, RuleOptions, unknown, TSESLint.RuleListener> & {
24
+ name: string;
25
+ };
26
+ export {};