@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.
Files changed (77) hide show
  1. package/config/rule-analysis-strategies.js +18 -2
  2. package/engines/eslint-engine.js +9 -11
  3. package/engines/heuristic-engine.js +55 -31
  4. package/package.json +2 -1
  5. package/rules/README.md +252 -0
  6. package/rules/common/C002_no_duplicate_code/analyzer.js +65 -0
  7. package/rules/common/C002_no_duplicate_code/config.json +23 -0
  8. package/rules/common/C003_no_vague_abbreviations/analyzer.js +418 -0
  9. package/rules/common/C003_no_vague_abbreviations/config.json +35 -0
  10. package/rules/common/C006_function_naming/analyzer.js +504 -0
  11. package/rules/common/C006_function_naming/config.json +86 -0
  12. package/rules/common/C006_function_naming/smart-analyzer.js +503 -0
  13. package/rules/common/C010_limit_block_nesting/analyzer.js +389 -0
  14. package/rules/common/C012_command_query_separation/analyzer.js +481 -0
  15. package/rules/common/C012_command_query_separation/ast-analyzer.js +495 -0
  16. package/rules/common/C013_no_dead_code/analyzer.js +206 -0
  17. package/rules/common/C014_dependency_injection/analyzer.js +338 -0
  18. package/rules/common/C017_constructor_logic/analyzer.js +314 -0
  19. package/rules/common/C019_log_level_usage/analyzer.js +362 -0
  20. package/rules/common/C019_log_level_usage/config.json +121 -0
  21. package/rules/common/C029_catch_block_logging/analyzer-backup.js +426 -0
  22. package/rules/common/C029_catch_block_logging/analyzer-fixed.js +130 -0
  23. package/rules/common/C029_catch_block_logging/analyzer-multi-tech.js +487 -0
  24. package/rules/common/C029_catch_block_logging/analyzer-simple.js +110 -0
  25. package/rules/common/C029_catch_block_logging/analyzer-smart-pipeline.js +755 -0
  26. package/rules/common/C029_catch_block_logging/analyzer.js +129 -0
  27. package/rules/common/C029_catch_block_logging/ast-analyzer-backup.js +441 -0
  28. package/rules/common/C029_catch_block_logging/ast-analyzer-new.js +127 -0
  29. package/rules/common/C029_catch_block_logging/ast-analyzer.js +133 -0
  30. package/rules/common/C029_catch_block_logging/cfg-analyzer.js +408 -0
  31. package/rules/common/C029_catch_block_logging/config.json +59 -0
  32. package/rules/common/C029_catch_block_logging/dataflow-analyzer.js +454 -0
  33. package/rules/common/C029_catch_block_logging/multi-language-ast-engine.js +700 -0
  34. package/rules/common/C029_catch_block_logging/pattern-learning-analyzer.js +568 -0
  35. package/rules/common/C029_catch_block_logging/semantic-analyzer.js +459 -0
  36. package/rules/common/C031_validation_separation/analyzer.js +186 -0
  37. package/rules/common/C041_no_sensitive_hardcode/analyzer.js +292 -0
  38. package/rules/common/C041_no_sensitive_hardcode/ast-analyzer.js +296 -0
  39. package/rules/common/C042_boolean_name_prefix/analyzer.js +300 -0
  40. package/rules/common/C043_no_console_or_print/analyzer.js +431 -0
  41. package/rules/common/C047_no_duplicate_retry_logic/analyzer.js +590 -0
  42. package/rules/common/C075_explicit_return_types/analyzer.js +103 -0
  43. package/rules/common/C076_single_test_behavior/analyzer.js +121 -0
  44. package/rules/docs/C002_no_duplicate_code.md +57 -0
  45. package/rules/docs/C031_validation_separation.md +72 -0
  46. package/rules/index.js +155 -0
  47. package/rules/migration/converter.js +385 -0
  48. package/rules/migration/mapping.json +164 -0
  49. package/rules/parser/constants.js +31 -0
  50. package/rules/parser/file-config.js +80 -0
  51. package/rules/parser/rule-parser-simple.js +305 -0
  52. package/rules/parser/rule-parser.js +527 -0
  53. package/rules/security/S015_insecure_tls_certificate/analyzer.js +150 -0
  54. package/rules/security/S015_insecure_tls_certificate/ast-analyzer.js +237 -0
  55. package/rules/security/S023_no_json_injection/analyzer.js +278 -0
  56. package/rules/security/S023_no_json_injection/ast-analyzer.js +359 -0
  57. package/rules/security/S026_json_schema_validation/analyzer.js +251 -0
  58. package/rules/security/S026_json_schema_validation/config.json +27 -0
  59. package/rules/security/S027_no_hardcoded_secrets/analyzer.js +436 -0
  60. package/rules/security/S027_no_hardcoded_secrets/config.json +29 -0
  61. package/rules/security/S029_csrf_protection/analyzer.js +330 -0
  62. package/rules/tests/C002_no_duplicate_code.test.js +50 -0
  63. package/rules/universal/C010/generic.js +0 -0
  64. package/rules/universal/C010/tree-sitter-analyzer.js +0 -0
  65. package/rules/utils/ast-utils.js +191 -0
  66. package/rules/utils/base-analyzer.js +98 -0
  67. package/rules/utils/pattern-matchers.js +239 -0
  68. package/rules/utils/rule-helpers.js +264 -0
  69. package/rules/utils/severity-constants.js +93 -0
  70. package/scripts/generate_insights.js +188 -0
  71. package/scripts/merge-reports.js +0 -424
  72. package/scripts/test-scripts/README.md +0 -22
  73. package/scripts/test-scripts/test-c041-comparison.js +0 -114
  74. package/scripts/test-scripts/test-c041-eslint.js +0 -67
  75. package/scripts/test-scripts/test-eslint-rules.js +0 -146
  76. package/scripts/test-scripts/test-real-world.js +0 -44
  77. 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;