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.noMissingNullChecks = void 0;
9
+ const eslint_devkit_1 = require("@interlace/eslint-devkit");
10
+ const eslint_devkit_2 = require("@interlace/eslint-devkit");
11
+ /**
12
+ * Check if property access has null/undefined check
13
+ */
14
+ function hasNullCheck(node, sourceCode) {
15
+ // Check if node itself uses optional chaining
16
+ if (node.optional) {
17
+ return true;
18
+ }
19
+ // Check if parent is optional chaining
20
+ const parent = node.parent;
21
+ if (parent && parent.type === 'ChainExpression') {
22
+ return true; // Optional chaining handles null/undefined
23
+ }
24
+ // Check if used with nullish coalescing
25
+ if (usesNullishCoalescing(node)) {
26
+ return true;
27
+ }
28
+ // Check for basic explicit null checks in if statements
29
+ // This is a simplified check that looks for patterns like:
30
+ // if (obj !== null) { obj.property; }
31
+ if (hasExplicitNullCheck(node, sourceCode)) {
32
+ return true;
33
+ }
34
+ // For more sophisticated null checking, we'd need control flow analysis
35
+ // This is a simplified implementation that checks for basic patterns
36
+ return false;
37
+ }
38
+ /**
39
+ * Check for explicit null checks in if statements
40
+ */
41
+ function hasExplicitNullCheck(node, sourceCode) {
42
+ // Walk up the AST to find if statements
43
+ let current = node;
44
+ let depth = 0;
45
+ const maxDepth = 10;
46
+ while (current && depth < maxDepth) {
47
+ const parent = current
48
+ .parent;
49
+ if (parent && parent.type === 'IfStatement') {
50
+ // Check if the test condition contains a null check for our object
51
+ const test = parent.test;
52
+ if (isNullCheckForObject(test, node.object, sourceCode)) {
53
+ return true;
54
+ }
55
+ }
56
+ current = parent;
57
+ depth++;
58
+ }
59
+ return false;
60
+ }
61
+ /**
62
+ * Check if a test expression is a null check for a specific object
63
+ */
64
+ function isNullCheckForObject(test, object, sourceCode) {
65
+ // Handle binary expressions like obj !== null
66
+ if (test.type === 'BinaryExpression') {
67
+ const { left, right, operator } = test;
68
+ if (operator === '!==' ||
69
+ operator === '!=' ||
70
+ operator === '===' ||
71
+ operator === '==') {
72
+ const leftText = sourceCode.getText(left);
73
+ const rightText = sourceCode.getText(right);
74
+ const objectText = sourceCode.getText(object);
75
+ // Check if one side matches our object and the other is null/undefined
76
+ if ((leftText === objectText &&
77
+ (rightText === 'null' || rightText === 'undefined')) ||
78
+ (rightText === objectText &&
79
+ (leftText === 'null' || leftText === 'undefined'))) {
80
+ return true;
81
+ }
82
+ }
83
+ }
84
+ // Handle logical expressions like obj !== null && obj !== undefined
85
+ if (test.type === 'LogicalExpression') {
86
+ return (isNullCheckForObject(test.left, object, sourceCode) ||
87
+ isNullCheckForObject(test.right, object, sourceCode));
88
+ }
89
+ return false;
90
+ }
91
+ /**
92
+ * Check if expression uses nullish coalescing
93
+ */
94
+ function usesNullishCoalescing(node) {
95
+ let current = node;
96
+ let depth = 0;
97
+ const maxDepth = 5;
98
+ while (current && depth < maxDepth) {
99
+ const parent = current
100
+ .parent;
101
+ if (parent &&
102
+ parent.type === 'LogicalExpression' &&
103
+ parent.operator === '??') {
104
+ return true;
105
+ }
106
+ current = parent;
107
+ depth++;
108
+ }
109
+ return false;
110
+ }
111
+ exports.noMissingNullChecks = (0, eslint_devkit_2.createRule)({
112
+ name: 'no-missing-null-checks',
113
+ meta: {
114
+ type: 'problem',
115
+ docs: {
116
+ description: 'Detects potential null pointer dereferences',
117
+ },
118
+ hasSuggestions: true,
119
+ messages: {
120
+ missingNullCheck: (0, eslint_devkit_1.formatLLMMessage)({
121
+ icon: eslint_devkit_1.MessageIcons.WARNING,
122
+ issueName: 'Missing null check',
123
+ cwe: 'CWE-476',
124
+ description: 'Potential null/undefined dereference detected',
125
+ severity: 'HIGH',
126
+ fix: 'Use optional chaining (?.) or add explicit null check',
127
+ documentationLink: 'https://rules.sonarsource.com/javascript/RSPEC-2259/',
128
+ }),
129
+ useOptionalChaining: (0, eslint_devkit_1.formatLLMMessage)({
130
+ icon: eslint_devkit_1.MessageIcons.INFO,
131
+ issueName: 'Use Optional Chaining',
132
+ description: 'Use optional chaining operator',
133
+ severity: 'LOW',
134
+ fix: 'obj?.property?.method()',
135
+ documentationLink: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining',
136
+ }),
137
+ useNullishCoalescing: (0, eslint_devkit_1.formatLLMMessage)({
138
+ icon: eslint_devkit_1.MessageIcons.INFO,
139
+ issueName: 'Use Nullish Coalescing',
140
+ description: 'Use nullish coalescing operator',
141
+ severity: 'LOW',
142
+ fix: 'value ?? defaultValue',
143
+ documentationLink: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing',
144
+ }),
145
+ addExplicitCheck: (0, eslint_devkit_1.formatLLMMessage)({
146
+ icon: eslint_devkit_1.MessageIcons.INFO,
147
+ issueName: 'Add Explicit Check',
148
+ description: 'Add explicit null check',
149
+ severity: 'LOW',
150
+ fix: 'if (obj !== null) { obj.property }',
151
+ documentationLink: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/null',
152
+ }),
153
+ },
154
+ schema: [
155
+ {
156
+ type: 'object',
157
+ properties: {
158
+ ignoreInTests: {
159
+ type: 'boolean',
160
+ default: true,
161
+ description: 'Ignore in test files',
162
+ },
163
+ requireExplicitChecks: {
164
+ type: 'boolean',
165
+ default: false,
166
+ description: 'Require explicit null checks',
167
+ },
168
+ },
169
+ additionalProperties: false,
170
+ },
171
+ ],
172
+ },
173
+ defaultOptions: [
174
+ {
175
+ ignoreInTests: true,
176
+ requireExplicitChecks: false,
177
+ },
178
+ ],
179
+ create(context, [options = {}]) {
180
+ const { ignoreInTests = true,
181
+ // requireExplicitChecks = false, // Not used
182
+ } = options || {};
183
+ const filename = context.getFilename();
184
+ const isTestFile = ignoreInTests && /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);
185
+ if (isTestFile) {
186
+ return {};
187
+ }
188
+ const sourceCode = context.sourceCode || context.sourceCode;
189
+ // Track reported MemberExpression nodes to prevent duplicate reports
190
+ // Key format: "start-end" from node.range
191
+ const reportedMemberExpressions = new Set();
192
+ /**
193
+ * Get a unique key for a MemberExpression node to track if it's been reported
194
+ */
195
+ function getMemberExpressionKey(node) {
196
+ // Use the node's range for a unique identifier
197
+ // Range is [start, end] character positions in the source
198
+ if (node.range && Array.isArray(node.range) && node.range.length >= 2) {
199
+ return `me-${node.range[0]}-${node.range[1]}`;
200
+ }
201
+ // Fallback: use location if range is not available
202
+ const loc = node
203
+ .loc;
204
+ if (loc && loc.start) {
205
+ return `me-${loc.start.line}-${loc.start.column}-${loc.end?.line || loc.start.line}-${loc.end?.column || loc.start.column}`;
206
+ }
207
+ // Last resort: use a hash of the node structure
208
+ return `me-${JSON.stringify(node).slice(0, 50)}`;
209
+ }
210
+ /**
211
+ * Check member expressions for null safety
212
+ */
213
+ function checkMemberExpression(node) {
214
+ // Skip if already using optional chaining
215
+ if (node.optional) {
216
+ return;
217
+ }
218
+ // Skip if parent is optional chaining
219
+ const parent = node
220
+ .parent;
221
+ if (parent && parent.type === 'ChainExpression') {
222
+ return;
223
+ }
224
+ // Only report on the "deepest" member expression in a chain
225
+ // If this member expression is the object of another member expression,
226
+ // don't report it yet - let the deepest one be reported
227
+ if (parent &&
228
+ parent.type === 'MemberExpression' &&
229
+ parent.object === node) {
230
+ return; // This is an intermediate member expression
231
+ }
232
+ // Check if object might be null/undefined
233
+ // Check for Identifier or nested MemberExpression
234
+ const objectNode = node.object;
235
+ let shouldCheck = false;
236
+ if (objectNode.type === 'Identifier') {
237
+ shouldCheck = true;
238
+ }
239
+ else if (objectNode.type === 'MemberExpression') {
240
+ // Nested member expressions like value.nested.deep
241
+ shouldCheck = true;
242
+ }
243
+ if (shouldCheck && !hasNullCheck(node, sourceCode)) {
244
+ const nodeKey = getMemberExpressionKey(node);
245
+ if (reportedMemberExpressions.has(nodeKey)) {
246
+ return; // Already reported
247
+ }
248
+ try {
249
+ reportedMemberExpressions.add(nodeKey);
250
+ context.report({
251
+ node,
252
+ messageId: 'missingNullCheck',
253
+ suggest: [
254
+ {
255
+ messageId: 'useOptionalChaining',
256
+ fix: () => null,
257
+ },
258
+ {
259
+ messageId: 'useNullishCoalescing',
260
+ fix: () => null,
261
+ },
262
+ {
263
+ messageId: 'addExplicitCheck',
264
+ fix: () => null,
265
+ },
266
+ ],
267
+ });
268
+ /* c8 ignore next 4 -- defensive error handling, hard to trigger in tests */
269
+ }
270
+ catch {
271
+ // Silently skip if there's an error
272
+ return;
273
+ }
274
+ }
275
+ }
276
+ /**
277
+ * Check call expressions for null safety (e.g., obj.method())
278
+ * Only check if it's an actual method call, not just a property access
279
+ */
280
+ function checkCallExpression(node) {
281
+ /* c8 ignore next 4 -- defensive type check, always true when called by ESLint visitor */
282
+ // Ensure this is actually a CallExpression (not just a MemberExpression)
283
+ if (node.type !== 'CallExpression') {
284
+ return;
285
+ }
286
+ // Only check if callee is a member expression (e.g., obj.method())
287
+ // This ensures we only check method calls, not property accesses
288
+ if (node.callee.type === 'MemberExpression') {
289
+ const memberExpr = node.callee;
290
+ // Skip if already using optional chaining
291
+ if (memberExpr.optional) {
292
+ return;
293
+ }
294
+ // Skip if parent is optional chaining
295
+ const parent = memberExpr.parent;
296
+ if (parent && parent.type === 'ChainExpression') {
297
+ return;
298
+ }
299
+ // Skip if this MemberExpression was already reported by checkMemberExpression
300
+ // We can't easily check this, so we'll rely on the fact that CallExpression
301
+ // is only triggered for actual method calls, not property accesses
302
+ // Check if object might be null/undefined
303
+ const objectNode = memberExpr.object;
304
+ let shouldCheck = false;
305
+ if (objectNode.type === 'Identifier') {
306
+ shouldCheck = true;
307
+ }
308
+ else if (objectNode.type === 'MemberExpression') {
309
+ // Nested member expressions
310
+ shouldCheck = true;
311
+ }
312
+ if (shouldCheck && !hasNullCheck(memberExpr, sourceCode)) {
313
+ const nodeKey = getMemberExpressionKey(memberExpr);
314
+ if (reportedMemberExpressions.has(nodeKey)) {
315
+ return; // Already reported by checkMemberExpression
316
+ }
317
+ try {
318
+ reportedMemberExpressions.add(nodeKey);
319
+ context.report({
320
+ node: memberExpr,
321
+ messageId: 'missingNullCheck',
322
+ suggest: [
323
+ {
324
+ messageId: 'useOptionalChaining',
325
+ fix: () => null,
326
+ },
327
+ {
328
+ messageId: 'useNullishCoalescing',
329
+ fix: () => null,
330
+ },
331
+ {
332
+ messageId: 'addExplicitCheck',
333
+ fix: () => null,
334
+ },
335
+ ],
336
+ });
337
+ /* c8 ignore next 4 -- defensive error handling, hard to trigger in tests */
338
+ }
339
+ catch {
340
+ // Silently skip if there's an error
341
+ return;
342
+ }
343
+ }
344
+ }
345
+ }
346
+ return {
347
+ MemberExpression: checkMemberExpression,
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-unsafe-type-narrowing
8
+ * Detects unsafe type narrowing patterns
9
+ *
10
+ * @see https://rules.sonarsource.com/javascript/RSPEC-4326/
11
+ */
12
+ import type { TSESLint } from '@interlace/eslint-devkit';
13
+ type MessageIds = 'unsafeTypeNarrowing' | 'useTypeGuard' | 'useProperNarrowing' | 'validateBeforeAssert';
14
+ export interface Options {
15
+ /** Ignore in test files. Default: true */
16
+ ignoreInTests?: boolean;
17
+ /** Allow type assertions with comments. Default: false */
18
+ allowWithComment?: boolean;
19
+ }
20
+ type RuleOptions = [Options?];
21
+ export declare const noUnsafeTypeNarrowing: TSESLint.RuleModule<MessageIds, RuleOptions, unknown, TSESLint.RuleListener> & {
22
+ name: string;
23
+ };
24
+ export {};
@@ -0,0 +1,176 @@
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.noUnsafeTypeNarrowing = void 0;
9
+ const eslint_devkit_1 = require("@interlace/eslint-devkit");
10
+ const eslint_devkit_2 = require("@interlace/eslint-devkit");
11
+ /**
12
+ * Check if type assertion is unsafe (as unknown as T)
13
+ */
14
+ function isUnsafeTypeAssertion(node) {
15
+ // Check for double assertion pattern: as unknown as T or as any as T
16
+ if (node.expression.type === 'TSAsExpression') {
17
+ const innerAssertion = node.expression;
18
+ // Check if inner assertion is to 'unknown'
19
+ if (innerAssertion.typeAnnotation.type === 'TSUnknownKeyword') {
20
+ return true; // as unknown as T pattern
21
+ }
22
+ // Also check for 'any' type
23
+ if (innerAssertion.typeAnnotation.type === 'TSAnyKeyword') {
24
+ return true; // as any as T pattern
25
+ }
26
+ }
27
+ // Don't flag direct assertions to unknown/any - those are handled by TSC
28
+ // Only flag the double assertion pattern which bypasses type safety
29
+ return false;
30
+ }
31
+ /**
32
+ * Check if type assertion has explanatory comment
33
+ */
34
+ function hasExplanatoryComment(node, sourceCode) {
35
+ const comments = sourceCode.getAllComments();
36
+ const nodeStart = node.loc?.start;
37
+ if (!nodeStart || !comments.length) {
38
+ return false;
39
+ }
40
+ // Look for explanatory comments near the type assertion
41
+ const explanatoryPatterns = [
42
+ /type.?guard/i,
43
+ /validated/i,
44
+ /checked/i,
45
+ /safe/i,
46
+ /known/i,
47
+ /intentional/i,
48
+ /necessary/i,
49
+ /framework/i,
50
+ /library/i,
51
+ /third.?party/i,
52
+ /legacy/i,
53
+ /todo/i,
54
+ /fixme/i,
55
+ ];
56
+ // Check comments before the assertion (within 1 line)
57
+ for (const comment of comments) {
58
+ if (comment.loc && nodeStart.line - comment.loc.end.line <= 1) {
59
+ const commentText = comment.value.toLowerCase();
60
+ if (explanatoryPatterns.some((pattern) => pattern.test(commentText))) {
61
+ return true;
62
+ }
63
+ }
64
+ }
65
+ return false;
66
+ }
67
+ exports.noUnsafeTypeNarrowing = (0, eslint_devkit_2.createRule)({
68
+ name: 'no-unsafe-type-narrowing',
69
+ meta: {
70
+ type: 'problem',
71
+ docs: {
72
+ description: 'Detects unsafe type narrowing patterns',
73
+ },
74
+ hasSuggestions: true,
75
+ messages: {
76
+ unsafeTypeNarrowing: (0, eslint_devkit_1.formatLLMMessage)({
77
+ icon: eslint_devkit_1.MessageIcons.WARNING,
78
+ issueName: 'Unsafe type narrowing',
79
+ description: 'Unsafe type assertion or narrowing detected',
80
+ severity: 'MEDIUM',
81
+ fix: 'Use type guards or proper validation before type assertion',
82
+ documentationLink: 'https://rules.sonarsource.com/javascript/RSPEC-4326/',
83
+ }),
84
+ useTypeGuard: (0, eslint_devkit_1.formatLLMMessage)({
85
+ icon: eslint_devkit_1.MessageIcons.INFO,
86
+ issueName: 'Use Type Guard',
87
+ description: 'Use type guard function',
88
+ severity: 'LOW',
89
+ fix: 'function isType(value: unknown): value is Type { return ... }',
90
+ documentationLink: 'https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates',
91
+ }),
92
+ useProperNarrowing: (0, eslint_devkit_1.formatLLMMessage)({
93
+ icon: eslint_devkit_1.MessageIcons.INFO,
94
+ issueName: 'Use Type Narrowing',
95
+ description: 'Use proper type narrowing',
96
+ severity: 'LOW',
97
+ fix: 'if (typeof value === "string") { ... }',
98
+ documentationLink: 'https://www.typescriptlang.org/docs/handbook/2/narrowing.html',
99
+ }),
100
+ validateBeforeAssert: (0, eslint_devkit_1.formatLLMMessage)({
101
+ icon: eslint_devkit_1.MessageIcons.INFO,
102
+ issueName: 'Validate First',
103
+ description: 'Validate before type assertion',
104
+ severity: 'LOW',
105
+ fix: 'if (isValid(value)) { const typed = value as Type; }',
106
+ documentationLink: 'https://www.typescriptlang.org/docs/handbook/2/narrowing.html',
107
+ }),
108
+ },
109
+ schema: [
110
+ {
111
+ type: 'object',
112
+ properties: {
113
+ ignoreInTests: {
114
+ type: 'boolean',
115
+ default: true,
116
+ description: 'Ignore in test files',
117
+ },
118
+ allowWithComment: {
119
+ type: 'boolean',
120
+ default: false,
121
+ description: 'Allow type assertions with comments',
122
+ },
123
+ },
124
+ additionalProperties: false,
125
+ },
126
+ ],
127
+ },
128
+ defaultOptions: [
129
+ {
130
+ ignoreInTests: true,
131
+ allowWithComment: false,
132
+ },
133
+ ],
134
+ create(context, [options = {}]) {
135
+ const { ignoreInTests = true, allowWithComment = false } = options || {};
136
+ const filename = context.getFilename();
137
+ const isTestFile = ignoreInTests && /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);
138
+ if (isTestFile) {
139
+ return {};
140
+ }
141
+ const sourceCode = context.sourceCode || context.sourceCode;
142
+ /**
143
+ * Check type assertions
144
+ */
145
+ function checkTypeAssertion(node) {
146
+ if (!isUnsafeTypeAssertion(node)) {
147
+ return;
148
+ }
149
+ // Check if comment explains the unsafe assertion
150
+ if (allowWithComment && hasExplanatoryComment(node, sourceCode)) {
151
+ return;
152
+ }
153
+ context.report({
154
+ node,
155
+ messageId: 'unsafeTypeNarrowing',
156
+ suggest: [
157
+ {
158
+ messageId: 'useTypeGuard',
159
+ fix: () => null, // Cannot auto-fix without context
160
+ },
161
+ {
162
+ messageId: 'useProperNarrowing',
163
+ fix: () => null,
164
+ },
165
+ {
166
+ messageId: 'validateBeforeAssert',
167
+ fix: () => null,
168
+ },
169
+ ],
170
+ });
171
+ }
172
+ return {
173
+ TSAsExpression: checkTypeAssertion,
174
+ };
175
+ },
176
+ });
@@ -0,0 +1,12 @@
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
+ export interface Options {
7
+ }
8
+ type RuleOptions = [Options?];
9
+ export declare const requireNetworkTimeout: import("@typescript-eslint/utils/ts-eslint").RuleModule<"violationDetected", RuleOptions, unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
10
+ name: string;
11
+ };
12
+ export {};
@@ -0,0 +1,57 @@
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.requireNetworkTimeout = void 0;
9
+ /**
10
+ * @fileoverview Require timeout limits for network requests
11
+ * @see https://owasp.org/www-project-mobile-top-10/
12
+ * @see https://cwe.mitre.org/data/definitions/770.html
13
+ */
14
+ const eslint_devkit_1 = require("@interlace/eslint-devkit");
15
+ exports.requireNetworkTimeout = (0, eslint_devkit_1.createRule)({
16
+ name: 'require-network-timeout',
17
+ meta: {
18
+ type: 'problem',
19
+ docs: {
20
+ description: 'Require timeout limits for network requests',
21
+ },
22
+ messages: {
23
+ violationDetected: (0, eslint_devkit_1.formatLLMMessage)({
24
+ icon: eslint_devkit_1.MessageIcons.SECURITY,
25
+ issueName: 'violation Detected',
26
+ cwe: 'CWE-400',
27
+ description: 'Require timeout limits for network requests detected - fetch/axios without timeout option',
28
+ severity: 'MEDIUM',
29
+ fix: 'Review and apply secure practices',
30
+ documentationLink: 'https://cwe.mitre.org/data/definitions/400.html',
31
+ }),
32
+ },
33
+ schema: [],
34
+ },
35
+ defaultOptions: [],
36
+ create(context) {
37
+ return {
38
+ CallExpression(node) {
39
+ const callee = node.callee;
40
+ const isFetch = callee.type === eslint_devkit_1.AST_NODE_TYPES.Identifier && callee.name === 'fetch';
41
+ const isAxios = callee.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
42
+ callee.object.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
43
+ callee.object.name === 'axios';
44
+ if (isFetch || isAxios) {
45
+ const optionsArg = node.arguments[1];
46
+ const hasTimeout = optionsArg?.type === eslint_devkit_1.AST_NODE_TYPES.ObjectExpression &&
47
+ optionsArg.properties.some((p) => p.type === eslint_devkit_1.AST_NODE_TYPES.Property &&
48
+ p.key.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
49
+ p.key.name === 'timeout');
50
+ if (!hasTimeout) {
51
+ context.report({ node, messageId: 'violationDetected' });
52
+ }
53
+ }
54
+ },
55
+ };
56
+ },
57
+ });