@sun-asterisk/sunlint 1.2.1 → 1.2.2
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/config/rule-analysis-strategies.js +18 -2
- package/engines/eslint-engine.js +9 -11
- package/engines/heuristic-engine.js +55 -31
- package/package.json +2 -1
- package/rules/README.md +252 -0
- package/rules/common/C002_no_duplicate_code/analyzer.js +65 -0
- package/rules/common/C002_no_duplicate_code/config.json +23 -0
- package/rules/common/C003_no_vague_abbreviations/analyzer.js +418 -0
- package/rules/common/C003_no_vague_abbreviations/config.json +35 -0
- package/rules/common/C006_function_naming/analyzer.js +504 -0
- package/rules/common/C006_function_naming/config.json +86 -0
- package/rules/common/C006_function_naming/smart-analyzer.js +503 -0
- package/rules/common/C010_limit_block_nesting/analyzer.js +389 -0
- package/rules/common/C012_command_query_separation/analyzer.js +481 -0
- package/rules/common/C012_command_query_separation/ast-analyzer.js +495 -0
- package/rules/common/C013_no_dead_code/analyzer.js +206 -0
- package/rules/common/C014_dependency_injection/analyzer.js +338 -0
- package/rules/common/C017_constructor_logic/analyzer.js +314 -0
- package/rules/common/C019_log_level_usage/analyzer.js +362 -0
- package/rules/common/C019_log_level_usage/config.json +121 -0
- package/rules/common/C029_catch_block_logging/analyzer-backup.js +426 -0
- package/rules/common/C029_catch_block_logging/analyzer-fixed.js +130 -0
- package/rules/common/C029_catch_block_logging/analyzer-multi-tech.js +487 -0
- package/rules/common/C029_catch_block_logging/analyzer-simple.js +110 -0
- package/rules/common/C029_catch_block_logging/analyzer-smart-pipeline.js +755 -0
- package/rules/common/C029_catch_block_logging/analyzer.js +129 -0
- package/rules/common/C029_catch_block_logging/ast-analyzer-backup.js +441 -0
- package/rules/common/C029_catch_block_logging/ast-analyzer-new.js +127 -0
- package/rules/common/C029_catch_block_logging/ast-analyzer.js +133 -0
- package/rules/common/C029_catch_block_logging/cfg-analyzer.js +408 -0
- package/rules/common/C029_catch_block_logging/config.json +59 -0
- package/rules/common/C029_catch_block_logging/dataflow-analyzer.js +454 -0
- package/rules/common/C029_catch_block_logging/multi-language-ast-engine.js +700 -0
- package/rules/common/C029_catch_block_logging/pattern-learning-analyzer.js +568 -0
- package/rules/common/C029_catch_block_logging/semantic-analyzer.js +459 -0
- package/rules/common/C031_validation_separation/analyzer.js +186 -0
- package/rules/common/C041_no_sensitive_hardcode/analyzer.js +292 -0
- package/rules/common/C041_no_sensitive_hardcode/ast-analyzer.js +296 -0
- package/rules/common/C042_boolean_name_prefix/analyzer.js +300 -0
- package/rules/common/C043_no_console_or_print/analyzer.js +431 -0
- package/rules/common/C047_no_duplicate_retry_logic/analyzer.js +590 -0
- package/rules/common/C075_explicit_return_types/analyzer.js +103 -0
- package/rules/common/C076_single_test_behavior/analyzer.js +121 -0
- package/rules/docs/C002_no_duplicate_code.md +57 -0
- package/rules/docs/C031_validation_separation.md +72 -0
- package/rules/index.js +155 -0
- package/rules/migration/converter.js +385 -0
- package/rules/migration/mapping.json +164 -0
- package/rules/parser/constants.js +31 -0
- package/rules/parser/file-config.js +80 -0
- package/rules/parser/rule-parser-simple.js +305 -0
- package/rules/parser/rule-parser.js +527 -0
- package/rules/security/S015_insecure_tls_certificate/analyzer.js +150 -0
- package/rules/security/S015_insecure_tls_certificate/ast-analyzer.js +237 -0
- package/rules/security/S023_no_json_injection/analyzer.js +278 -0
- package/rules/security/S023_no_json_injection/ast-analyzer.js +359 -0
- package/rules/security/S026_json_schema_validation/analyzer.js +251 -0
- package/rules/security/S026_json_schema_validation/config.json +27 -0
- package/rules/security/S027_no_hardcoded_secrets/analyzer.js +436 -0
- package/rules/security/S027_no_hardcoded_secrets/config.json +29 -0
- package/rules/security/S029_csrf_protection/analyzer.js +330 -0
- package/rules/tests/C002_no_duplicate_code.test.js +50 -0
- package/rules/universal/C010/generic.js +0 -0
- package/rules/universal/C010/tree-sitter-analyzer.js +0 -0
- package/rules/utils/ast-utils.js +191 -0
- package/rules/utils/base-analyzer.js +98 -0
- package/rules/utils/pattern-matchers.js +239 -0
- package/rules/utils/rule-helpers.js +264 -0
- package/rules/utils/severity-constants.js +93 -0
- package/scripts/generate_insights.js +188 -0
- package/scripts/merge-reports.js +0 -424
- package/scripts/test-scripts/README.md +0 -22
- package/scripts/test-scripts/test-c041-comparison.js +0 -114
- package/scripts/test-scripts/test-c041-eslint.js +0 -67
- package/scripts/test-scripts/test-eslint-rules.js +0 -146
- package/scripts/test-scripts/test-real-world.js +0 -44
- package/scripts/test-scripts/test-rules-on-real-projects.js +0 -86
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* C010 Rule: Limit Block Nesting Depth
|
|
3
|
+
* Prevents deep nesting of blocks (if/for/while/switch/try) to improve code readability
|
|
4
|
+
* Maximum recommended depth: 3 levels
|
|
5
|
+
* Severity: warning
|
|
6
|
+
* Category: Maintainability
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
class C010LimitBlockNestingAnalyzer {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.ruleId = 'C010';
|
|
15
|
+
this.ruleName = 'Limit Block Nesting';
|
|
16
|
+
this.description = 'Do not exceed maximum block nesting depth for better readability';
|
|
17
|
+
this.severity = 'warning';
|
|
18
|
+
this.maxDepth = 3;
|
|
19
|
+
|
|
20
|
+
// Control flow blocks that create nesting - Match ESLint rule exactly
|
|
21
|
+
// Only: if, for, while, do-while, switch, try-catch statements
|
|
22
|
+
this.blockPatterns = [
|
|
23
|
+
// if/else patterns - handle both same-line and multi-line blocks
|
|
24
|
+
{ pattern: /^\s*if\s*\(.*\)\s*\{/, type: 'if', opens: true },
|
|
25
|
+
{ pattern: /^\s*if\s*\(.*\)\s*$/, type: 'if-pending', opens: false, needsBrace: true },
|
|
26
|
+
{ pattern: /^\s*else\s*if\s*\(.*\)\s*\{/, type: 'else-if', opens: true },
|
|
27
|
+
{ pattern: /^\s*else\s*if\s*\(.*\)\s*$/, type: 'else-if-pending', opens: false, needsBrace: true },
|
|
28
|
+
{ pattern: /^\s*else\s*\{/, type: 'else', opens: true },
|
|
29
|
+
{ pattern: /^\s*else\s*$/, type: 'else-pending', opens: false, needsBrace: true },
|
|
30
|
+
|
|
31
|
+
// Loop patterns - handle both same-line and multi-line blocks
|
|
32
|
+
{ pattern: /^\s*for\s*\(.*\)\s*\{/, type: 'for', opens: true },
|
|
33
|
+
{ pattern: /^\s*for\s*\(.*\)\s*$/, type: 'for-pending', opens: false, needsBrace: true },
|
|
34
|
+
{ pattern: /^\s*while\s*\(.*\)\s*\{/, type: 'while', opens: true },
|
|
35
|
+
{ pattern: /^\s*while\s*\(.*\)\s*$/, type: 'while-pending', opens: false, needsBrace: true },
|
|
36
|
+
{ pattern: /^\s*do\s*\{/, type: 'do-while', opens: true },
|
|
37
|
+
{ pattern: /^\s*do\s*$/, type: 'do-while-pending', opens: false, needsBrace: true },
|
|
38
|
+
|
|
39
|
+
// Switch statements (not individual case blocks)
|
|
40
|
+
{ pattern: /^\s*switch\s*\(.*\)\s*\{/, type: 'switch', opens: true },
|
|
41
|
+
{ pattern: /^\s*switch\s*\(.*\)\s*$/, type: 'switch-pending', opens: false, needsBrace: true },
|
|
42
|
+
|
|
43
|
+
// Try-catch patterns
|
|
44
|
+
{ pattern: /^\s*try\s*\{/, type: 'try', opens: true },
|
|
45
|
+
{ pattern: /^\s*try\s*$/, type: 'try-pending', opens: false, needsBrace: true },
|
|
46
|
+
{ pattern: /^\s*catch\s*\(.*\)\s*\{/, type: 'catch', opens: true },
|
|
47
|
+
{ pattern: /^\s*catch\s*\(.*\)\s*$/, type: 'catch-pending', opens: false, needsBrace: true },
|
|
48
|
+
{ pattern: /^\s*finally\s*\{/, type: 'finally', opens: true },
|
|
49
|
+
{ pattern: /^\s*finally\s*$/, type: 'finally-pending', opens: false, needsBrace: true },
|
|
50
|
+
|
|
51
|
+
// With statements (rarely used but included for completeness)
|
|
52
|
+
{ pattern: /^\s*with\s*\(.*\)\s*\{/, type: 'with', opens: true },
|
|
53
|
+
{ pattern: /^\s*with\s*\(.*\)\s*$/, type: 'with-pending', opens: false, needsBrace: true },
|
|
54
|
+
|
|
55
|
+
// Standalone opening brace (follows pending blocks)
|
|
56
|
+
{ pattern: /^\s*\{\s*$/, type: 'brace-block', opens: true }
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
// Track pending blocks that expect a brace on next line
|
|
60
|
+
this.pendingBlocks = [];
|
|
61
|
+
|
|
62
|
+
// Patterns for inline blocks (without braces)
|
|
63
|
+
this.inlineBlockPatterns = [
|
|
64
|
+
{ pattern: /^\s*if\s*\(.*\)\s*[^{]/, type: 'if-inline' },
|
|
65
|
+
{ pattern: /^\s*else\s+if\s*\(.*\)\s*[^{]/, type: 'else-if-inline' },
|
|
66
|
+
{ pattern: /^\s*else\s+[^{]/, type: 'else-inline' },
|
|
67
|
+
{ pattern: /^\s*for\s*\(.*\)\s*[^{]/, type: 'for-inline' },
|
|
68
|
+
{ pattern: /^\s*while\s*\(.*\)\s*[^{]/, type: 'while-inline' }
|
|
69
|
+
];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async analyze(files, language, config = {}) {
|
|
73
|
+
const violations = [];
|
|
74
|
+
|
|
75
|
+
if (config?.rules?.C010?.maxDepth) {
|
|
76
|
+
this.maxDepth = config.rules.C010.maxDepth;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
for (const filePath of files) {
|
|
80
|
+
try {
|
|
81
|
+
if (this.isTestFile(filePath)) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const fileContent = fs.readFileSync(filePath, 'utf8');
|
|
86
|
+
const fileViolations = await this.analyzeFile(filePath, fileContent, language, config);
|
|
87
|
+
violations.push(...fileViolations);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.warn(`C010 analysis error for ${filePath}:`, error.message);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return violations;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async analyzeFile(filePath, fileContent, language, config) {
|
|
97
|
+
const violations = [];
|
|
98
|
+
|
|
99
|
+
// Reset pending blocks for each file
|
|
100
|
+
this.pendingBlocks = [];
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const lines = fileContent.split('\n');
|
|
104
|
+
let controlFlowStack = []; // Only track control flow blocks
|
|
105
|
+
let commentState = { inMultiLine: false, startChar: 0 };
|
|
106
|
+
|
|
107
|
+
for (let i = 0; i < lines.length; i++) {
|
|
108
|
+
const line = lines[i];
|
|
109
|
+
const lineNumber = i + 1;
|
|
110
|
+
|
|
111
|
+
// Update comment state and skip if in comment
|
|
112
|
+
commentState = this.updateCommentState(line, commentState);
|
|
113
|
+
if (this.isLineInComment(line, commentState)) {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const trimmedLine = line.trim();
|
|
118
|
+
if (!trimmedLine) continue;
|
|
119
|
+
|
|
120
|
+
// Track control flow statements
|
|
121
|
+
const controlFlowMatch = this.detectControlFlow(trimmedLine);
|
|
122
|
+
if (controlFlowMatch) {
|
|
123
|
+
// Check if this line has opening brace (same line)
|
|
124
|
+
if (trimmedLine.includes('{')) {
|
|
125
|
+
controlFlowStack.push({
|
|
126
|
+
type: controlFlowMatch.type,
|
|
127
|
+
line: lineNumber,
|
|
128
|
+
column: this.getBlockStartColumn(line)
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Check depth violation at the control flow statement
|
|
132
|
+
if (controlFlowStack.length > this.maxDepth) {
|
|
133
|
+
violations.push(this.createViolation(
|
|
134
|
+
filePath,
|
|
135
|
+
lineNumber,
|
|
136
|
+
this.getBlockStartColumn(line),
|
|
137
|
+
line,
|
|
138
|
+
controlFlowStack.length,
|
|
139
|
+
controlFlowStack
|
|
140
|
+
));
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
// Look ahead for opening brace on next line
|
|
144
|
+
let braceLineIndex = -1;
|
|
145
|
+
for (let j = i + 1; j < Math.min(i + 3, lines.length); j++) {
|
|
146
|
+
if (lines[j].trim() === '{') {
|
|
147
|
+
braceLineIndex = j;
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
if (lines[j].trim() !== '') break; // Stop if non-empty, non-brace line
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (braceLineIndex >= 0) {
|
|
154
|
+
controlFlowStack.push({
|
|
155
|
+
type: controlFlowMatch.type,
|
|
156
|
+
line: braceLineIndex + 1,
|
|
157
|
+
column: this.getBlockStartColumn(lines[braceLineIndex])
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Check depth violation at the opening brace
|
|
161
|
+
if (controlFlowStack.length > this.maxDepth) {
|
|
162
|
+
violations.push(this.createViolation(
|
|
163
|
+
filePath,
|
|
164
|
+
braceLineIndex + 1,
|
|
165
|
+
this.getBlockStartColumn(lines[braceLineIndex]),
|
|
166
|
+
lines[braceLineIndex],
|
|
167
|
+
controlFlowStack.length,
|
|
168
|
+
controlFlowStack
|
|
169
|
+
));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Handle closing braces - only remove if we have control flow blocks
|
|
176
|
+
const closeBraces = (line.match(/\}/g) || []).length;
|
|
177
|
+
for (let j = 0; j < closeBraces && controlFlowStack.length > 0; j++) {
|
|
178
|
+
controlFlowStack.pop();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
} catch (error) {
|
|
183
|
+
console.warn(`C010 analysis error for ${filePath}:`, error.message);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return violations;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
detectControlFlow(line) {
|
|
190
|
+
// Match control flow keywords that create nesting
|
|
191
|
+
const patterns = [
|
|
192
|
+
{ pattern: /^\s*if\s*\(/, type: 'if' },
|
|
193
|
+
{ pattern: /^\s*else\s+if\s*\(/, type: 'else-if' },
|
|
194
|
+
{ pattern: /^\s*else\s*$/, type: 'else' },
|
|
195
|
+
{ pattern: /^\s*for\s*\(/, type: 'for' },
|
|
196
|
+
{ pattern: /^\s*while\s*\(/, type: 'while' },
|
|
197
|
+
{ pattern: /^\s*do\s*$/, type: 'do-while' },
|
|
198
|
+
{ pattern: /^\s*switch\s*\(/, type: 'switch' },
|
|
199
|
+
{ pattern: /^\s*try\s*$/, type: 'try' },
|
|
200
|
+
{ pattern: /^\s*catch\s*\(/, type: 'catch' },
|
|
201
|
+
{ pattern: /^\s*finally\s*$/, type: 'finally' },
|
|
202
|
+
{ pattern: /^\s*with\s*\(/, type: 'with' },
|
|
203
|
+
// Handle closing brace followed by control flow
|
|
204
|
+
{ pattern: /^\s*}\s*else\s+if\s*\(/, type: 'else-if' },
|
|
205
|
+
{ pattern: /^\s*}\s*else\s*$/, type: 'else' },
|
|
206
|
+
{ pattern: /^\s*}\s*catch\s*\(/, type: 'catch' },
|
|
207
|
+
{ pattern: /^\s*}\s*finally\s*$/, type: 'finally' }
|
|
208
|
+
];
|
|
209
|
+
|
|
210
|
+
for (const pattern of patterns) {
|
|
211
|
+
if (pattern.pattern.test(line)) {
|
|
212
|
+
return { type: pattern.type };
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
updateCommentState(line, currentState) {
|
|
220
|
+
let { inMultiLine, startChar } = currentState;
|
|
221
|
+
let pos = startChar;
|
|
222
|
+
|
|
223
|
+
while (pos < line.length) {
|
|
224
|
+
if (!inMultiLine) {
|
|
225
|
+
// Check for single line comment
|
|
226
|
+
if (line.substr(pos, 2) === '//') {
|
|
227
|
+
return { inMultiLine: false, startChar: line.length };
|
|
228
|
+
}
|
|
229
|
+
// Check for multi-line comment start
|
|
230
|
+
if (line.substr(pos, 2) === '/*') {
|
|
231
|
+
inMultiLine = true;
|
|
232
|
+
pos += 2;
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
} else {
|
|
236
|
+
// Check for multi-line comment end
|
|
237
|
+
if (line.substr(pos, 2) === '*/') {
|
|
238
|
+
inMultiLine = false;
|
|
239
|
+
pos += 2;
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
pos++;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return { inMultiLine, startChar: 0 };
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
isLineInComment(line, commentState) {
|
|
250
|
+
const trimmed = line.trim();
|
|
251
|
+
|
|
252
|
+
// Single line comment
|
|
253
|
+
if (trimmed.startsWith('//')) return true;
|
|
254
|
+
|
|
255
|
+
// JSDoc style comment
|
|
256
|
+
if (trimmed.startsWith('*') && !trimmed.startsWith('*/')) return true;
|
|
257
|
+
|
|
258
|
+
// In multi-line comment
|
|
259
|
+
if (commentState.inMultiLine) {
|
|
260
|
+
// Unless the line ends the comment, it's a comment line
|
|
261
|
+
return !line.includes('*/');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
detectBlockOpening(trimmedLine, fullLine) {
|
|
268
|
+
// First check if this is a standalone opening brace that follows a pending block
|
|
269
|
+
if (trimmedLine === '{' && this.pendingBlocks.length > 0) {
|
|
270
|
+
const pendingBlock = this.pendingBlocks.pop();
|
|
271
|
+
return {
|
|
272
|
+
opens: true,
|
|
273
|
+
type: pendingBlock.type.replace('-pending', ''),
|
|
274
|
+
column: this.getBlockStartColumn(fullLine),
|
|
275
|
+
inline: false
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Check for block patterns
|
|
280
|
+
for (const blockPattern of this.blockPatterns) {
|
|
281
|
+
if (blockPattern.pattern.test(trimmedLine)) {
|
|
282
|
+
if (blockPattern.needsBrace) {
|
|
283
|
+
// This is a pending block, add to pending list
|
|
284
|
+
this.pendingBlocks.push({
|
|
285
|
+
type: blockPattern.type,
|
|
286
|
+
line: fullLine
|
|
287
|
+
});
|
|
288
|
+
return { opens: false };
|
|
289
|
+
} else {
|
|
290
|
+
return {
|
|
291
|
+
opens: blockPattern.opens,
|
|
292
|
+
type: blockPattern.type,
|
|
293
|
+
column: this.getBlockStartColumn(fullLine),
|
|
294
|
+
inline: false
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return { opens: false };
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
detectInlineBlock(trimmedLine) {
|
|
304
|
+
// Skip if line ends with { or ;
|
|
305
|
+
if (trimmedLine.endsWith('{') || trimmedLine.endsWith(';')) {
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
for (const pattern of this.inlineBlockPatterns) {
|
|
310
|
+
if (pattern.pattern.test(trimmedLine)) {
|
|
311
|
+
return { type: pattern.type };
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
isClosingBrace(line) {
|
|
319
|
+
// Match closing brace, possibly followed by else/catch/finally
|
|
320
|
+
return /^\s*}\s*(else|catch|finally)?\s*(\{|$)/.test(line);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
handleClosingBrace(blockStack) {
|
|
324
|
+
if (blockStack.length > 0) {
|
|
325
|
+
// Remove the most recent block
|
|
326
|
+
blockStack.pop();
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
calculateEffectiveDepth(blockStack) {
|
|
331
|
+
// Count only non-inline blocks for depth calculation
|
|
332
|
+
return blockStack.filter(block => !block.inline).length;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
getBlockStartColumn(line) {
|
|
336
|
+
const match = line.match(/^\s*/);
|
|
337
|
+
return match ? match[0].length + 1 : 1;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
isTestFile(filePath) {
|
|
341
|
+
const testPatterns = [
|
|
342
|
+
/\.test\.(js|ts|jsx|tsx)$/,
|
|
343
|
+
/\.spec\.(js|ts|jsx|tsx)$/,
|
|
344
|
+
/\/__tests__\//,
|
|
345
|
+
/\/tests?\//,
|
|
346
|
+
/\.e2e\./,
|
|
347
|
+
/test\.config\./,
|
|
348
|
+
/jest\.config\./,
|
|
349
|
+
/vitest\.config\./,
|
|
350
|
+
/cypress\//
|
|
351
|
+
];
|
|
352
|
+
|
|
353
|
+
return testPatterns.some(pattern => pattern.test(filePath));
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
createViolation(filePath, lineNumber, column, sourceLine, depth, blockStack) {
|
|
357
|
+
return {
|
|
358
|
+
ruleId: this.ruleId,
|
|
359
|
+
severity: this.severity,
|
|
360
|
+
message: `Block nesting depth ${depth} exceeds maximum of ${this.maxDepth}. Consider refactoring to reduce complexity.`,
|
|
361
|
+
filePath: filePath,
|
|
362
|
+
line: lineNumber,
|
|
363
|
+
column: column,
|
|
364
|
+
source: sourceLine.trim(),
|
|
365
|
+
suggestion: this.getSuggestion(depth),
|
|
366
|
+
nestingStack: blockStack.map(b => ({
|
|
367
|
+
type: b.type,
|
|
368
|
+
line: b.line,
|
|
369
|
+
inline: b.inline || false
|
|
370
|
+
}))
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
getSuggestion(currentDepth) {
|
|
375
|
+
const suggestions = [
|
|
376
|
+
"Extract nested logic into separate functions",
|
|
377
|
+
"Use early returns to reduce nesting",
|
|
378
|
+
"Consider using guard clauses",
|
|
379
|
+
"Break complex conditions into meaningful variables",
|
|
380
|
+
"Use strategy pattern for complex conditional logic",
|
|
381
|
+
"Consider using a state machine for complex flow control"
|
|
382
|
+
];
|
|
383
|
+
|
|
384
|
+
const index = Math.min(currentDepth - this.maxDepth - 1, suggestions.length - 1);
|
|
385
|
+
return suggestions[Math.max(0, index)];
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
module.exports = C010LimitBlockNestingAnalyzer;
|