@sun-asterisk/sunlint 1.3.0 → 1.3.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 (124) hide show
  1. package/CHANGELOG.md +115 -1
  2. package/CONTRIBUTING.md +249 -605
  3. package/README.md +3 -4
  4. package/config/ci-cd.json +54 -0
  5. package/config/development.json +56 -0
  6. package/config/large-project.json +143 -0
  7. package/config/presets/all.json +0 -1
  8. package/config/release.json +70 -0
  9. package/config/rule-analysis-strategies.js +38 -3
  10. package/config/rules/enhanced-rules-registry.json +474 -1179
  11. package/config/rules/rules-registry-generated.json +3 -3
  12. package/core/cli-action-handler.js +24 -30
  13. package/core/cli-program.js +11 -3
  14. package/core/config-merger.js +29 -2
  15. package/core/enhanced-rules-registry.js +3 -2
  16. package/core/semantic-engine.js +129 -19
  17. package/core/semantic-rule-base.js +4 -2
  18. package/core/unified-rule-registry.js +1 -1
  19. package/docs/COMMAND-EXAMPLES.md +134 -0
  20. package/docs/LARGE-PROJECT-GUIDE.md +324 -0
  21. package/engines/heuristic-engine.js +135 -16
  22. package/integrations/eslint/plugin/index.js +0 -2
  23. package/integrations/eslint/plugin/rules/common/c003-no-vague-abbreviations.js +59 -1
  24. package/integrations/eslint/plugin/rules/common/c006-function-name-verb-noun.js +26 -1
  25. package/integrations/eslint/plugin/rules/common/c030-use-custom-error-classes.js +54 -19
  26. package/origin-rules/common-en.md +19 -15
  27. package/package.json +1 -1
  28. package/rules/common/C002_no_duplicate_code/analyzer.js +334 -36
  29. package/rules/common/C003_no_vague_abbreviations/analyzer.js +220 -35
  30. package/rules/common/C006_function_naming/analyzer.js +29 -3
  31. package/rules/common/C010_limit_block_nesting/analyzer.js +181 -337
  32. package/rules/common/C010_limit_block_nesting/config.json +64 -0
  33. package/rules/common/C010_limit_block_nesting/regex-based-analyzer.js +379 -0
  34. package/rules/common/C010_limit_block_nesting/symbol-based-analyzer.js +231 -0
  35. package/rules/common/C013_no_dead_code/analyzer.js +75 -177
  36. package/rules/common/C013_no_dead_code/config.json +61 -0
  37. package/rules/common/C013_no_dead_code/regex-based-analyzer.js +345 -0
  38. package/rules/common/C013_no_dead_code/symbol-based-analyzer.js +640 -0
  39. package/rules/common/C014_dependency_injection/analyzer.js +48 -313
  40. package/rules/common/C014_dependency_injection/config.json +26 -0
  41. package/rules/common/C014_dependency_injection/symbol-based-analyzer.js +751 -0
  42. package/rules/common/C017_constructor_logic/analyzer.js +254 -17
  43. package/rules/common/C017_constructor_logic/semantic-analyzer.js +340 -0
  44. package/rules/common/C018_no_throw_generic_error/analyzer.js +232 -0
  45. package/rules/common/C018_no_throw_generic_error/config.json +50 -0
  46. package/rules/common/C018_no_throw_generic_error/regex-based-analyzer.js +387 -0
  47. package/rules/common/C018_no_throw_generic_error/symbol-based-analyzer.js +314 -0
  48. package/rules/common/C019_log_level_usage/analyzer.js +110 -317
  49. package/rules/common/C019_log_level_usage/pattern-analyzer.js +88 -0
  50. package/rules/common/C019_log_level_usage/system-log-analyzer.js +1267 -0
  51. package/rules/common/C023_no_duplicate_variable/analyzer.js +180 -0
  52. package/rules/common/C023_no_duplicate_variable/config.json +50 -0
  53. package/rules/common/C023_no_duplicate_variable/symbol-based-analyzer.js +158 -0
  54. package/rules/common/C024_no_scatter_hardcoded_constants/analyzer.js +180 -0
  55. package/rules/common/C024_no_scatter_hardcoded_constants/config.json +50 -0
  56. package/rules/common/C024_no_scatter_hardcoded_constants/symbol-based-analyzer.js +181 -0
  57. package/rules/common/C030_use_custom_error_classes/analyzer.js +200 -0
  58. package/rules/common/C033_separate_service_repository/README.md +78 -0
  59. package/rules/common/C033_separate_service_repository/analyzer.js +160 -0
  60. package/rules/common/C033_separate_service_repository/config.json +50 -0
  61. package/rules/common/C033_separate_service_repository/regex-based-analyzer.js +585 -0
  62. package/rules/common/C033_separate_service_repository/symbol-based-analyzer.js +368 -0
  63. package/rules/common/C035_error_logging_context/STRATEGY.md +99 -0
  64. package/rules/common/C035_error_logging_context/analyzer.js +232 -0
  65. package/rules/common/C035_error_logging_context/config.json +54 -0
  66. package/rules/common/C035_error_logging_context/regex-based-analyzer.js +299 -0
  67. package/rules/common/C035_error_logging_context/symbol-based-analyzer.js +454 -0
  68. package/rules/common/C040_centralized_validation/analyzer.js +165 -0
  69. package/rules/common/C040_centralized_validation/config.json +46 -0
  70. package/rules/common/C040_centralized_validation/regex-based-analyzer.js +243 -0
  71. package/rules/common/C040_centralized_validation/symbol-based-analyzer.js +416 -0
  72. package/rules/common/{C076_single_test_behavior → C072_single_test_behavior}/analyzer.js +6 -6
  73. package/rules/common/C076_explicit_function_types/README.md +30 -0
  74. package/rules/common/C076_explicit_function_types/analyzer.js +172 -0
  75. package/rules/common/C076_explicit_function_types/config.json +15 -0
  76. package/rules/common/C076_explicit_function_types/semantic-analyzer.js +341 -0
  77. package/rules/index.js +6 -1
  78. package/rules/parser/rule-parser.js +13 -2
  79. package/rules/security/S005_no_origin_auth/README.md +226 -0
  80. package/rules/security/S005_no_origin_auth/analyzer.js +184 -0
  81. package/rules/security/S005_no_origin_auth/ast-analyzer.js +406 -0
  82. package/rules/security/S005_no_origin_auth/config.json +85 -0
  83. package/rules/security/S006_no_plaintext_recovery_codes/README.md +139 -0
  84. package/rules/security/S006_no_plaintext_recovery_codes/analyzer.js +306 -0
  85. package/rules/security/S006_no_plaintext_recovery_codes/config.json +48 -0
  86. package/rules/security/S007_no_plaintext_otp/README.md +198 -0
  87. package/rules/security/S007_no_plaintext_otp/analyzer.js +406 -0
  88. package/rules/security/S007_no_plaintext_otp/config.json +79 -0
  89. package/rules/security/S007_no_plaintext_otp/semantic-analyzer.js +609 -0
  90. package/rules/security/S007_no_plaintext_otp/semantic-config.json +195 -0
  91. package/rules/security/S007_no_plaintext_otp/semantic-wrapper.js +280 -0
  92. package/rules/security/S009_no_insecure_encryption/README.md +158 -0
  93. package/rules/security/S009_no_insecure_encryption/analyzer.js +319 -0
  94. package/rules/security/S009_no_insecure_encryption/config.json +55 -0
  95. package/rules/security/S010_no_insecure_encryption/README.md +224 -0
  96. package/rules/security/S010_no_insecure_encryption/analyzer.js +493 -0
  97. package/rules/security/S010_no_insecure_encryption/config.json +48 -0
  98. package/rules/security/S016_no_sensitive_querystring/STRATEGY.md +149 -0
  99. package/rules/security/S016_no_sensitive_querystring/analyzer.js +276 -0
  100. package/rules/security/S016_no_sensitive_querystring/config.json +127 -0
  101. package/rules/security/S016_no_sensitive_querystring/regex-based-analyzer.js +258 -0
  102. package/rules/security/S016_no_sensitive_querystring/symbol-based-analyzer.js +495 -0
  103. package/rules/security/S027_no_hardcoded_secrets/analyzer.js +180 -366
  104. package/rules/security/S027_no_hardcoded_secrets/categories.json +153 -0
  105. package/rules/security/S027_no_hardcoded_secrets/categorized-analyzer.js +250 -0
  106. package/rules/security/S048_no_current_password_in_reset/README.md +222 -0
  107. package/rules/security/S048_no_current_password_in_reset/analyzer.js +366 -0
  108. package/rules/security/S048_no_current_password_in_reset/config.json +48 -0
  109. package/rules/security/S055_content_type_validation/README.md +176 -0
  110. package/rules/security/S055_content_type_validation/analyzer.js +312 -0
  111. package/rules/security/S055_content_type_validation/config.json +48 -0
  112. package/rules/utils/rule-helpers.js +140 -1
  113. package/scripts/consolidate-config.js +116 -0
  114. package/scripts/prepare-release.sh +1 -1
  115. package/config/rules/rules-registry.json +0 -765
  116. package/docs/ESLINT-INTEGRATION-STRATEGY.md +0 -392
  117. package/docs/FUTURE_PACKAGES.md +0 -83
  118. package/docs/HEURISTIC_VS_AI.md +0 -113
  119. package/docs/PRODUCTION_DEPLOYMENT_ANALYSIS.md +0 -112
  120. package/docs/PRODUCTION_SIZE_IMPACT.md +0 -183
  121. package/docs/RELEASE_GUIDE.md +0 -230
  122. package/docs/STANDARDIZED-CATEGORY-FILTERING.md +0 -156
  123. package/integrations/eslint/plugin/rules/common/c076-single-behavior-per-test.js +0 -254
  124. package/rules/common/C006_function_naming/smart-analyzer.js +0 -503
@@ -0,0 +1,379 @@
1
+ /**
2
+ * Regex-based analyzer for C010 - Block Nesting Detection
3
+ * Purpose: Fallback analyzer when AST analysis is not available
4
+ * Note: Less accurate than symbol-based approach but more performant
5
+ */
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+ const { CommentDetector } = require('../../utils/rule-helpers');
10
+
11
+ class C010RegexBasedAnalyzer {
12
+ constructor(semanticEngine = null) {
13
+ this.ruleId = 'C010';
14
+ this.ruleName = 'Limit Block Nesting (Regex-Based)';
15
+ this.description = 'Do not exceed maximum block nesting depth for better readability';
16
+ this.severity = 'warning';
17
+ this.maxDepth = 3;
18
+ this.semanticEngine = semanticEngine;
19
+ this.verbose = false;
20
+
21
+ // Control flow blocks that create nesting - Match ESLint rule exactly
22
+ // Only: if, for, while, do-while, switch, try-catch statements
23
+ this.blockPatterns = [
24
+ // if/else patterns - handle both same-line and multi-line blocks
25
+ { pattern: /^\s*if\s*\(.*\)\s*\{/, type: 'if', opens: true },
26
+ { pattern: /^\s*if\s*\(.*\)\s*$/, type: 'if-pending', opens: false, needsBrace: true },
27
+ { pattern: /^\s*else\s*if\s*\(.*\)\s*\{/, type: 'else-if', opens: true },
28
+ { pattern: /^\s*else\s*if\s*\(.*\)\s*$/, type: 'else-if-pending', opens: false, needsBrace: true },
29
+ { pattern: /^\s*else\s*\{/, type: 'else', opens: true },
30
+ { pattern: /^\s*else\s*$/, type: 'else-pending', opens: false, needsBrace: true },
31
+
32
+ // Loop patterns - handle both same-line and multi-line blocks
33
+ { pattern: /^\s*for\s*\(.*\)\s*\{/, type: 'for', opens: true },
34
+ { pattern: /^\s*for\s*\(.*\)\s*$/, type: 'for-pending', opens: false, needsBrace: true },
35
+ { pattern: /^\s*while\s*\(.*\)\s*\{/, type: 'while', opens: true },
36
+ { pattern: /^\s*while\s*\(.*\)\s*$/, type: 'while-pending', opens: false, needsBrace: true },
37
+ { pattern: /^\s*do\s*\{/, type: 'do-while', opens: true },
38
+ { pattern: /^\s*do\s*$/, type: 'do-while-pending', opens: false, needsBrace: true },
39
+
40
+ // Switch statements (not individual case blocks)
41
+ { pattern: /^\s*switch\s*\(.*\)\s*\{/, type: 'switch', opens: true },
42
+ { pattern: /^\s*switch\s*\(.*\)\s*$/, type: 'switch-pending', opens: false, needsBrace: true },
43
+
44
+ // Try-catch patterns
45
+ { pattern: /^\s*try\s*\{/, type: 'try', opens: true },
46
+ { pattern: /^\s*try\s*$/, type: 'try-pending', opens: false, needsBrace: true },
47
+ { pattern: /^\s*catch\s*\(.*\)\s*\{/, type: 'catch', opens: true },
48
+ { pattern: /^\s*catch\s*\(.*\)\s*$/, type: 'catch-pending', opens: false, needsBrace: true },
49
+ { pattern: /^\s*finally\s*\{/, type: 'finally', opens: true },
50
+ { pattern: /^\s*finally\s*$/, type: 'finally-pending', opens: false, needsBrace: true },
51
+
52
+ // With statements (rarely used but included for completeness)
53
+ { pattern: /^\s*with\s*\(.*\)\s*\{/, type: 'with', opens: true },
54
+ { pattern: /^\s*with\s*\(.*\)\s*$/, type: 'with-pending', opens: false, needsBrace: true },
55
+
56
+ // Standalone opening brace (follows pending blocks)
57
+ { pattern: /^\s*\{\s*$/, type: 'brace-block', opens: true }
58
+ ];
59
+
60
+ // Track pending blocks that expect a brace on next line
61
+ this.pendingBlocks = [];
62
+
63
+ // Patterns for inline blocks (without braces)
64
+ this.inlineBlockPatterns = [
65
+ { pattern: /^\s*if\s*\(.*\)\s*[^{]/, type: 'if-inline' },
66
+ { pattern: /^\s*else\s+if\s*\(.*\)\s*[^{]/, type: 'else-if-inline' },
67
+ { pattern: /^\s*else\s+[^{]/, type: 'else-inline' },
68
+ { pattern: /^\s*for\s*\(.*\)\s*[^{]/, type: 'for-inline' },
69
+ { pattern: /^\s*while\s*\(.*\)\s*[^{]/, type: 'while-inline' }
70
+ ];
71
+ }
72
+
73
+ async initialize(semanticEngine = null) {
74
+ if (semanticEngine) {
75
+ this.semanticEngine = semanticEngine;
76
+ }
77
+ this.verbose = semanticEngine?.verbose || false;
78
+
79
+ if (this.verbose) {
80
+ console.log(`[DEBUG] 🔧 C010 Regex-Based: Analyzer initialized with max depth: ${this.maxDepth}`);
81
+ }
82
+ }
83
+
84
+ async analyzeFileBasic(filePath, options = {}) {
85
+ if (process.env.SUNLINT_DEBUG) {
86
+ console.log(`🔧 [C010 Regex] analyzeFileBasic() called for: ${filePath}`);
87
+ }
88
+
89
+ try {
90
+ if (this.isTestFile(filePath)) {
91
+ return [];
92
+ }
93
+
94
+ const fileContent = fs.readFileSync(filePath, 'utf8');
95
+ const violations = await this.analyzeFile(filePath, fileContent, 'typescript', options);
96
+
97
+ if (process.env.SUNLINT_DEBUG) {
98
+ console.log(`🔧 [C010 Regex] Found ${violations.length} violations in ${filePath}`);
99
+ }
100
+
101
+ return violations;
102
+ } catch (error) {
103
+ console.warn(`C010 regex analysis error for ${filePath}:`, error.message);
104
+ return [];
105
+ }
106
+ }
107
+
108
+ async analyze(files, language, config = {}) {
109
+ const violations = [];
110
+
111
+ if (config?.rules?.C010?.maxDepth) {
112
+ this.maxDepth = config.rules.C010.maxDepth;
113
+ }
114
+
115
+ for (const filePath of files) {
116
+ try {
117
+ if (this.isTestFile(filePath)) {
118
+ continue;
119
+ }
120
+
121
+ const fileContent = fs.readFileSync(filePath, 'utf8');
122
+ const fileViolations = await this.analyzeFile(filePath, fileContent, language, config);
123
+ violations.push(...fileViolations);
124
+ } catch (error) {
125
+ console.warn(`C010 analysis error for ${filePath}:`, error.message);
126
+ }
127
+ }
128
+
129
+ return violations;
130
+ }
131
+
132
+ async analyzeFile(filePath, fileContent, language, config) {
133
+ const violations = [];
134
+
135
+ // Reset pending blocks for each file
136
+ this.pendingBlocks = [];
137
+
138
+ try {
139
+ const lines = fileContent.split('\n');
140
+ let controlFlowStack = []; // Only track control flow blocks
141
+
142
+ // Use CommentDetector to filter comment lines
143
+ const filteredLines = CommentDetector.filterCommentLines(lines);
144
+
145
+ for (let i = 0; i < filteredLines.length; i++) {
146
+ const { line, lineNumber, isComment } = filteredLines[i];
147
+
148
+ // Skip comment lines
149
+ if (isComment) {
150
+ continue;
151
+ }
152
+
153
+ const trimmedLine = line.trim();
154
+ if (!trimmedLine) continue;
155
+
156
+ // Track control flow statements
157
+ const controlFlowMatch = this.detectControlFlow(trimmedLine);
158
+ if (controlFlowMatch) {
159
+ // Check if this line has opening brace (same line)
160
+ if (trimmedLine.includes('{')) {
161
+ controlFlowStack.push({
162
+ type: controlFlowMatch.type,
163
+ line: lineNumber,
164
+ column: this.getBlockStartColumn(line)
165
+ });
166
+
167
+ // Check depth violation at the control flow statement
168
+ if (controlFlowStack.length > this.maxDepth) {
169
+ violations.push(this.createViolation(
170
+ filePath,
171
+ lineNumber,
172
+ this.getBlockStartColumn(line),
173
+ line,
174
+ controlFlowStack.length,
175
+ controlFlowStack
176
+ ));
177
+ }
178
+ } else {
179
+ // Look ahead for opening brace on next line
180
+ let braceLineIndex = -1;
181
+ for (let j = i + 1; j < Math.min(i + 3, filteredLines.length); j++) {
182
+ const nextFilteredLine = filteredLines[j];
183
+ if (nextFilteredLine.isComment) continue; // Skip comment lines
184
+ if (nextFilteredLine.line.trim() === '{') {
185
+ braceLineIndex = nextFilteredLine.lineNumber - 1; // Convert back to 0-based
186
+ break;
187
+ }
188
+ if (nextFilteredLine.line.trim() !== '') break; // Stop if non-empty, non-brace line
189
+ }
190
+
191
+ if (braceLineIndex >= 0) {
192
+ controlFlowStack.push({
193
+ type: controlFlowMatch.type,
194
+ line: braceLineIndex + 1,
195
+ column: this.getBlockStartColumn(lines[braceLineIndex])
196
+ });
197
+
198
+ // Check depth violation at the opening brace
199
+ if (controlFlowStack.length > this.maxDepth) {
200
+ violations.push(this.createViolation(
201
+ filePath,
202
+ braceLineIndex + 1,
203
+ this.getBlockStartColumn(lines[braceLineIndex]),
204
+ lines[braceLineIndex],
205
+ controlFlowStack.length,
206
+ controlFlowStack
207
+ ));
208
+ }
209
+ }
210
+ }
211
+ }
212
+
213
+ // Handle closing braces - only remove if we have control flow blocks
214
+ const closeBraces = (line.match(/\}/g) || []).length;
215
+ for (let j = 0; j < closeBraces && controlFlowStack.length > 0; j++) {
216
+ controlFlowStack.pop();
217
+ }
218
+ }
219
+
220
+ } catch (error) {
221
+ console.warn(`C010 analysis error for ${filePath}:`, error.message);
222
+ }
223
+
224
+ return violations;
225
+ }
226
+
227
+ detectControlFlow(line) {
228
+ // Match control flow keywords that create nesting
229
+ const patterns = [
230
+ { pattern: /^\s*if\s*\(/, type: 'if' },
231
+ { pattern: /^\s*else\s+if\s*\(/, type: 'else-if' },
232
+ { pattern: /^\s*else\s*$/, type: 'else' },
233
+ { pattern: /^\s*for\s*\(/, type: 'for' },
234
+ { pattern: /^\s*while\s*\(/, type: 'while' },
235
+ { pattern: /^\s*do\s*$/, type: 'do-while' },
236
+ { pattern: /^\s*switch\s*\(/, type: 'switch' },
237
+ { pattern: /^\s*try\s*$/, type: 'try' },
238
+ { pattern: /^\s*catch\s*\(/, type: 'catch' },
239
+ { pattern: /^\s*finally\s*$/, type: 'finally' },
240
+ { pattern: /^\s*with\s*\(/, type: 'with' },
241
+ // Handle closing brace followed by control flow
242
+ { pattern: /^\s*}\s*else\s+if\s*\(/, type: 'else-if' },
243
+ { pattern: /^\s*}\s*else\s*$/, type: 'else' },
244
+ { pattern: /^\s*}\s*catch\s*\(/, type: 'catch' },
245
+ { pattern: /^\s*}\s*finally\s*$/, type: 'finally' }
246
+ ];
247
+
248
+ for (const pattern of patterns) {
249
+ if (pattern.pattern.test(line)) {
250
+ return { type: pattern.type };
251
+ }
252
+ }
253
+
254
+ return null;
255
+ }
256
+
257
+ detectBlockOpening(trimmedLine, fullLine) {
258
+ // First check if this is a standalone opening brace that follows a pending block
259
+ if (trimmedLine === '{' && this.pendingBlocks.length > 0) {
260
+ const pendingBlock = this.pendingBlocks.pop();
261
+ return {
262
+ opens: true,
263
+ type: pendingBlock.type.replace('-pending', ''),
264
+ column: this.getBlockStartColumn(fullLine),
265
+ inline: false
266
+ };
267
+ }
268
+
269
+ // Check for block patterns
270
+ for (const blockPattern of this.blockPatterns) {
271
+ if (blockPattern.pattern.test(trimmedLine)) {
272
+ if (blockPattern.needsBrace) {
273
+ // This is a pending block, add to pending list
274
+ this.pendingBlocks.push({
275
+ type: blockPattern.type,
276
+ line: fullLine
277
+ });
278
+ return { opens: false };
279
+ } else {
280
+ return {
281
+ opens: blockPattern.opens,
282
+ type: blockPattern.type,
283
+ column: this.getBlockStartColumn(fullLine),
284
+ inline: false
285
+ };
286
+ }
287
+ }
288
+ }
289
+
290
+ return { opens: false };
291
+ }
292
+
293
+ detectInlineBlock(trimmedLine) {
294
+ // Skip if line ends with { or ;
295
+ if (trimmedLine.endsWith('{') || trimmedLine.endsWith(';')) {
296
+ return null;
297
+ }
298
+
299
+ for (const pattern of this.inlineBlockPatterns) {
300
+ if (pattern.pattern.test(trimmedLine)) {
301
+ return { type: pattern.type };
302
+ }
303
+ }
304
+
305
+ return null;
306
+ }
307
+
308
+ isClosingBrace(line) {
309
+ // Match closing brace, possibly followed by else/catch/finally
310
+ return /^\s*}\s*(else|catch|finally)?\s*(\{|$)/.test(line);
311
+ }
312
+
313
+ handleClosingBrace(blockStack) {
314
+ if (blockStack.length > 0) {
315
+ // Remove the most recent block
316
+ blockStack.pop();
317
+ }
318
+ }
319
+
320
+ calculateEffectiveDepth(blockStack) {
321
+ // Count only non-inline blocks for depth calculation
322
+ return blockStack.filter(block => !block.inline).length;
323
+ }
324
+
325
+ getBlockStartColumn(line) {
326
+ const match = line.match(/^\s*/);
327
+ return match ? match[0].length + 1 : 1;
328
+ }
329
+
330
+ isTestFile(filePath) {
331
+ const testPatterns = [
332
+ /\.test\.(js|ts|jsx|tsx)$/,
333
+ /\.spec\.(js|ts|jsx|tsx)$/,
334
+ /\/__tests__\//,
335
+ /\/tests?\//,
336
+ /\.e2e\./,
337
+ /test\.config\./,
338
+ /jest\.config\./,
339
+ /vitest\.config\./,
340
+ /cypress\//
341
+ ];
342
+
343
+ return testPatterns.some(pattern => pattern.test(filePath));
344
+ }
345
+
346
+ createViolation(filePath, lineNumber, column, sourceLine, depth, blockStack) {
347
+ return {
348
+ ruleId: this.ruleId,
349
+ severity: this.severity,
350
+ message: `🔄 [REGEX] Block nesting depth ${depth} exceeds maximum of ${this.maxDepth}. Consider refactoring to reduce complexity.`,
351
+ filePath: filePath,
352
+ line: lineNumber,
353
+ column: column,
354
+ source: sourceLine.trim(),
355
+ suggestion: this.getSuggestion(depth),
356
+ nestingStack: blockStack.map(b => ({
357
+ type: b.type,
358
+ line: b.line,
359
+ inline: b.inline || false
360
+ }))
361
+ };
362
+ }
363
+
364
+ getSuggestion(currentDepth) {
365
+ const suggestions = [
366
+ "Extract nested logic into separate functions",
367
+ "Use early returns to reduce nesting",
368
+ "Consider using guard clauses",
369
+ "Break complex conditions into meaningful variables",
370
+ "Use strategy pattern for complex conditional logic",
371
+ "Consider using a state machine for complex flow control"
372
+ ];
373
+
374
+ const index = Math.min(currentDepth - this.maxDepth - 1, suggestions.length - 1);
375
+ return suggestions[Math.max(0, index)];
376
+ }
377
+ }
378
+
379
+ module.exports = C010RegexBasedAnalyzer;
@@ -0,0 +1,231 @@
1
+ /**
2
+ * Symbol-based analyzer for C010 - Block Nesting Detection using AST
3
+ * Purpose: Use AST traversal to accurately detect nested block statements
4
+ * Advantage: More accurate than regex, handles complex syntax naturally
5
+ */
6
+
7
+ const { SyntaxKind } = require('ts-morph');
8
+
9
+ class C010SymbolBasedAnalyzer {
10
+ constructor(semanticEngine = null) {
11
+ this.ruleId = 'C010';
12
+ this.ruleName = 'Limit Block Nesting (Symbol-Based)';
13
+ this.semanticEngine = semanticEngine;
14
+ this.verbose = false;
15
+
16
+ // Configuration
17
+ this.maxNestingLevel = 3;
18
+
19
+ // Block statement kinds that count toward nesting (use ts-morph SyntaxKind)
20
+ this.blockStatementKinds = new Set([
21
+ SyntaxKind.IfStatement,
22
+ SyntaxKind.ForStatement,
23
+ SyntaxKind.ForInStatement,
24
+ SyntaxKind.ForOfStatement,
25
+ SyntaxKind.WhileStatement,
26
+ SyntaxKind.DoStatement,
27
+ SyntaxKind.SwitchStatement,
28
+ SyntaxKind.TryStatement,
29
+ SyntaxKind.CatchClause,
30
+ SyntaxKind.Block
31
+ ]);
32
+
33
+ // Statements that DON'T count toward nesting
34
+ this.nonNestingKinds = new Set([
35
+ SyntaxKind.FunctionDeclaration,
36
+ SyntaxKind.MethodDeclaration,
37
+ SyntaxKind.ArrowFunction,
38
+ SyntaxKind.FunctionExpression,
39
+ SyntaxKind.Constructor,
40
+ SyntaxKind.GetAccessor,
41
+ SyntaxKind.SetAccessor,
42
+ SyntaxKind.ClassDeclaration,
43
+ SyntaxKind.InterfaceDeclaration,
44
+ SyntaxKind.ObjectLiteralExpression,
45
+ SyntaxKind.ArrayLiteralExpression
46
+ ]);
47
+ }
48
+
49
+ async initialize(semanticEngine = null) {
50
+ if (semanticEngine) {
51
+ this.semanticEngine = semanticEngine;
52
+ }
53
+ this.verbose = semanticEngine?.verbose || false;
54
+
55
+ if (this.verbose) {
56
+ console.log(`[DEBUG] 🔧 C010 Symbol-Based: Analyzer initialized with max nesting level: ${this.maxNestingLevel}`);
57
+ }
58
+ }
59
+
60
+ async analyze(files, language, options = {}) {
61
+ const violations = [];
62
+
63
+ if (process.env.SUNLINT_DEBUG) {
64
+ console.log(`[C010 Symbol-Based] Starting analysis for ${files.length} files`);
65
+ console.log(`[C010 Symbol-Based] Semantic engine available: ${!!this.semanticEngine}`);
66
+ console.log(`[C010 Symbol-Based] Project available: ${!!this.semanticEngine?.project}`);
67
+ }
68
+
69
+ if (!this.semanticEngine?.project) {
70
+ if (this.verbose || process.env.SUNLINT_DEBUG) {
71
+ console.warn('[C010 Symbol-Based] No semantic engine available, skipping analysis');
72
+ }
73
+ return violations;
74
+ }
75
+
76
+ for (const filePath of files) {
77
+ try {
78
+ if (process.env.SUNLINT_DEBUG) {
79
+ console.log(`[C010 Symbol-Based] Analyzing file: ${filePath}`);
80
+ }
81
+ const fileViolations = await this.analyzeFileWithSymbols(filePath, options);
82
+ violations.push(...fileViolations);
83
+ } catch (error) {
84
+ if (this.verbose || process.env.SUNLINT_DEBUG) {
85
+ console.warn(`[C010 Symbol-Based] Analysis failed for ${filePath}:`, error.message);
86
+ console.warn(`[C010 Symbol-Based] Error stack:`, error.stack);
87
+ }
88
+ }
89
+ }
90
+
91
+ if (process.env.SUNLINT_DEBUG) {
92
+ console.log(`[C010 Symbol-Based] Analysis complete: ${violations.length} violations found`);
93
+ }
94
+
95
+ return violations;
96
+ }
97
+
98
+ async analyzeFileWithSymbols(filePath, options = {}) {
99
+ const violations = [];
100
+ const sourceFile = this.semanticEngine.project.getSourceFile(filePath); if (!sourceFile) {
101
+ if (this.verbose) {
102
+ console.warn(`[C010 Symbol-Based] Source file not found: ${filePath}`);
103
+ }
104
+ return violations;
105
+ }
106
+
107
+ // Traverse AST and track nesting depth
108
+ this.traverseNode(sourceFile, 0, violations, filePath);
109
+
110
+ if (this.verbose) {
111
+ console.log(`[C010 Symbol-Based] Found ${violations.length} violations in ${filePath}`);
112
+ }
113
+
114
+ return violations;
115
+ }
116
+
117
+ /**
118
+ * Recursively traverse AST nodes and track block nesting depth
119
+ */
120
+ traverseNode(node, currentDepth, violations, filePath) {
121
+ const nodeKind = node.getKind();
122
+
123
+ // Check if this node starts a new nesting level
124
+ let newDepth = currentDepth;
125
+ let isBlockStatement = false;
126
+
127
+ if (this.isNestingStatement(node)) {
128
+ newDepth = currentDepth + 1;
129
+ isBlockStatement = true;
130
+
131
+ // Check if nesting exceeds maximum allowed
132
+ if (newDepth > this.maxNestingLevel) {
133
+ const startPos = node.getStart();
134
+ const sourceFile = node.getSourceFile();
135
+ const lineAndChar = sourceFile.getLineAndColumnAtPos(startPos);
136
+
137
+ violations.push({
138
+ ruleId: this.ruleId,
139
+ severity: 'warning',
140
+ message: `Block nesting is too deep (level ${newDepth}). Maximum allowed is ${this.maxNestingLevel} levels.`,
141
+ filePath: filePath,
142
+ line: lineAndChar.line + 1,
143
+ column: lineAndChar.column + 1,
144
+ context: this.getNodeContext(node)
145
+ });
146
+ }
147
+ }
148
+
149
+ // Recursively analyze child nodes
150
+ node.forEachChild(child => {
151
+ // Don't increase depth for function boundaries
152
+ if (this.isFunctionBoundary(child)) {
153
+ // Reset depth for function/method/class boundaries
154
+ this.traverseNode(child, 0, violations, filePath);
155
+ } else {
156
+ this.traverseNode(child, newDepth, violations, filePath);
157
+ }
158
+ });
159
+ }
160
+
161
+ /**
162
+ * Check if a node represents a statement that increases nesting depth
163
+ */
164
+ isNestingStatement(node) {
165
+ const kind = node.getKind();
166
+
167
+ // Basic block statements
168
+ if (this.blockStatementKinds.has(kind)) {
169
+ return true;
170
+ }
171
+
172
+ // Special case: Block statements inside other constructs
173
+ if (kind === SyntaxKind.Block) {
174
+ const parent = node.getParent();
175
+ if (parent && this.blockStatementKinds.has(parent.getKind())) {
176
+ return false; // Block is part of the parent statement
177
+ }
178
+ return true;
179
+ }
180
+
181
+ return false;
182
+ }
183
+
184
+ /**
185
+ * Check if a node represents a function boundary (resets nesting depth)
186
+ */
187
+ isFunctionBoundary(node) {
188
+ return this.nonNestingKinds.has(node.getKind());
189
+ }
190
+
191
+ /**
192
+ * Get context information for a violation
193
+ */
194
+ getNodeContext(node) {
195
+ const text = node.getText();
196
+ const lines = text.split('\n');
197
+ const firstLine = lines[0].trim();
198
+
199
+ // Return first line or statement type
200
+ if (firstLine.length > 0) {
201
+ return firstLine.length > 50 ? firstLine.substring(0, 47) + '...' : firstLine;
202
+ }
203
+
204
+ // Fallback to node kind
205
+ const kind = node.getKind();
206
+ return SyntaxKind[kind] || 'Unknown';
207
+ }
208
+
209
+ /**
210
+ * Get detailed information about nesting violation
211
+ */
212
+ getNestingPath(node) {
213
+ const path = [];
214
+ let current = node.getParent();
215
+
216
+ while (current && path.length < 10) { // Limit to prevent infinite loops
217
+ if (this.isNestingStatement(current)) {
218
+ const kind = current.getKind();
219
+ const kindName = SyntaxKind[kind] || 'Unknown';
220
+ path.unshift(kindName);
221
+ } else if (this.isFunctionBoundary(current)) {
222
+ break; // Stop at function boundary
223
+ }
224
+ current = current.getParent();
225
+ }
226
+
227
+ return path;
228
+ }
229
+ }
230
+
231
+ module.exports = C010SymbolBasedAnalyzer;