@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
@@ -1,389 +1,233 @@
1
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
2
+ * C010 Main Analyzer - Block Nesting Detection
3
+ * Primary: Symbol-based analysis (when available)
4
+ * Fallback: Regex-based for all other cases
7
5
  */
8
6
 
9
- const fs = require('fs');
10
- const path = require('path');
7
+ const C010SymbolBasedAnalyzer = require('./symbol-based-analyzer.js');
8
+ const C010RegexBasedAnalyzer = require('./regex-based-analyzer.js');
11
9
 
12
- class C010LimitBlockNestingAnalyzer {
13
- constructor() {
10
+ class C010Analyzer {
11
+ constructor(options = {}) {
12
+ if (process.env.SUNLINT_DEBUG) {
13
+ console.log(`🔧 [C010] Constructor called with options:`, !!options);
14
+ console.log(`🔧 [C010] Options type:`, typeof options, Object.keys(options || {}));
15
+ }
16
+
14
17
  this.ruleId = 'C010';
15
18
  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
+ this.description = 'Block nesting depth should not exceed maximum allowed levels for better readability';
20
+ this.semanticEngine = options.semanticEngine || null;
21
+ this.verbose = options.verbose || false;
22
+
23
+ // Configuration
24
+ this.config = {
25
+ useSymbolBased: true, // Primary approach
26
+ fallbackToRegex: true, // Only when symbol fails completely
27
+ symbolBasedOnly: false // Can be set to true for pure mode
28
+ };
19
29
 
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
- ];
30
+ // Initialize both analyzers
31
+ try {
32
+ this.symbolAnalyzer = new C010SymbolBasedAnalyzer(this.semanticEngine);
33
+ if (process.env.SUNLINT_DEBUG) {
34
+ console.log(`🔧 [C010] Symbol analyzer created successfully`);
35
+ }
36
+ } catch (error) {
37
+ console.error(`🔧 [C010] Error creating symbol analyzer:`, error);
38
+ }
39
+
40
+ try {
41
+ this.regexAnalyzer = new C010RegexBasedAnalyzer(this.semanticEngine);
42
+ if (process.env.SUNLINT_DEBUG) {
43
+ console.log(`🔧 [C010] Regex analyzer created successfully`);
44
+ }
45
+ } catch (error) {
46
+ console.error(`🔧 [C010] Error creating regex analyzer:`, error);
47
+ }
48
+
49
+ if (process.env.SUNLINT_DEBUG) {
50
+ console.log(`🔧 [C010] Constructor completed`);
51
+ }
70
52
  }
71
53
 
72
- async analyze(files, language, config = {}) {
73
- const violations = [];
54
+ /**
55
+ * Initialize with semantic engine
56
+ */
57
+ async initialize(semanticEngine = null) {
58
+ if (semanticEngine) {
59
+ this.semanticEngine = semanticEngine;
60
+ }
61
+ this.verbose = semanticEngine?.verbose || false;
62
+
63
+ // Initialize both analyzers
64
+ await this.symbolAnalyzer.initialize(semanticEngine);
65
+ await this.regexAnalyzer.initialize(semanticEngine);
66
+
67
+ // Ensure verbose flag is propagated
68
+ this.regexAnalyzer.verbose = this.verbose;
69
+ this.symbolAnalyzer.verbose = this.verbose;
74
70
 
75
- if (config?.rules?.C010?.maxDepth) {
76
- this.maxDepth = config.rules.C010.maxDepth;
71
+ if (this.verbose) {
72
+ console.log(`🔧 [C010 Hybrid] Analyzer initialized - verbose: ${this.verbose}`);
77
73
  }
74
+ }
78
75
 
76
+ async analyze(files, language, options = {}) {
77
+ if (process.env.SUNLINT_DEBUG) {
78
+ console.log(`🔧 [C010] *** NEW HYBRID ANALYZE METHOD CALLED ***`);
79
+ console.log(`🔧 [C010] analyze() method called with ${files.length} files, language: ${language}`);
80
+ }
81
+
82
+ const violations = [];
83
+
79
84
  for (const filePath of files) {
80
85
  try {
81
- if (this.isTestFile(filePath)) {
82
- continue;
86
+ if (process.env.SUNLINT_DEBUG) {
87
+ console.log(`🔧 [C010] Processing file: ${filePath}`);
83
88
  }
84
89
 
85
- const fileContent = fs.readFileSync(filePath, 'utf8');
86
- const fileViolations = await this.analyzeFile(filePath, fileContent, language, config);
90
+ const fileViolations = await this.analyzeFile(filePath, options);
87
91
  violations.push(...fileViolations);
92
+
93
+ if (process.env.SUNLINT_DEBUG) {
94
+ console.log(`🔧 [C010] File ${filePath}: Found ${fileViolations.length} violations`);
95
+ }
88
96
  } catch (error) {
89
- console.warn(`C010 analysis error for ${filePath}:`, error.message);
97
+ console.warn(`❌ [C010] Analysis failed for ${filePath}:`, error.message);
90
98
  }
91
99
  }
92
100
 
101
+ if (process.env.SUNLINT_DEBUG) {
102
+ console.log(`🔧 [C010] Total violations found: ${violations.length}`);
103
+ }
104
+
93
105
  return violations;
94
106
  }
95
107
 
96
- async analyzeFile(filePath, fileContent, language, config) {
97
- const violations = [];
98
-
99
- // Reset pending blocks for each file
100
- this.pendingBlocks = [];
108
+ async analyzeFile(filePath, options = {}) {
109
+ if (process.env.SUNLINT_DEBUG) {
110
+ console.log(`🔧 [C010] analyzeFile() called for: ${filePath}`);
111
+ }
101
112
 
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;
113
+ // 1. Try Symbol-based analysis first (primary)
114
+ if (this.config.useSymbolBased &&
115
+ this.semanticEngine?.project &&
116
+ this.semanticEngine?.initialized) {
117
+ try {
118
+ if (process.env.SUNLINT_DEBUG) {
119
+ console.log(`🔧 [C010] Trying symbol-based analysis...`);
115
120
  }
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
- }
121
+ const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
122
+ if (sourceFile) {
123
+ if (process.env.SUNLINT_DEBUG) {
124
+ console.log(`🔧 [C010] Source file found, analyzing with symbol-based...`);
125
+ }
126
+ const violations = await this.symbolAnalyzer.analyzeFileWithSymbols(filePath, { ...options, verbose: options.verbose });
127
+
128
+ // Mark violations with analysis strategy
129
+ violations.forEach(v => v.analysisStrategy = 'symbol-based');
130
+
131
+ if (process.env.SUNLINT_DEBUG) {
132
+ console.log(`✅ [C010] Symbol-based analysis: ${violations.length} violations`);
133
+ }
134
+ return violations; // Return even if 0 violations - symbol analysis completed successfully
135
+ } else {
136
+ if (process.env.SUNLINT_DEBUG) {
137
+ console.log(`⚠️ [C010] Source file not found in project`);
172
138
  }
173
139
  }
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
- }
140
+ } catch (error) {
141
+ console.warn(`⚠️ [C010] Symbol analysis failed: ${error.message}`);
142
+ // Continue to fallback
180
143
  }
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 };
144
+ } else {
145
+ if (process.env.SUNLINT_DEBUG) {
146
+ console.log(`🔄 [C010] Symbol analysis conditions check:`);
147
+ console.log(` - useSymbolBased: ${this.config.useSymbolBased}`);
148
+ console.log(` - semanticEngine: ${!!this.semanticEngine}`);
149
+ console.log(` - semanticEngine.project: ${!!this.semanticEngine?.project}`);
150
+ console.log(` - semanticEngine.initialized: ${this.semanticEngine?.initialized}`);
151
+ console.log(`🔄 [C010] Symbol analysis unavailable, using regex fallback`);
213
152
  }
214
153
  }
215
154
 
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;
155
+ // 2. Fallback to regex-based analysis
156
+ if (this.config.fallbackToRegex) {
157
+ try {
158
+ if (process.env.SUNLINT_DEBUG) {
159
+ console.log(`🔧 [C010] Trying regex-based analysis...`);
234
160
  }
235
- } else {
236
- // Check for multi-line comment end
237
- if (line.substr(pos, 2) === '*/') {
238
- inMultiLine = false;
239
- pos += 2;
240
- continue;
161
+ const violations = await this.regexAnalyzer.analyzeFileBasic(filePath, options);
162
+
163
+ // Mark violations with analysis strategy
164
+ violations.forEach(v => v.analysisStrategy = 'regex-fallback');
165
+
166
+ if (process.env.SUNLINT_DEBUG) {
167
+ console.log(`🔄 [C010] Regex-based analysis: ${violations.length} violations`);
241
168
  }
169
+ return violations;
170
+ } catch (error) {
171
+ console.error(`❌ [C010] Regex analysis failed: ${error.message}`);
242
172
  }
243
- pos++;
244
173
  }
245
174
 
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('*/');
175
+ if (options?.verbose) {
176
+ console.log(`🔧 [C010] No analysis methods succeeded, returning empty`);
262
177
  }
263
-
264
- return false;
178
+ return [];
265
179
  }
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
- }
180
+
181
+ async analyzeFileBasic(filePath, options = {}) {
182
+ console.log(`🔧 [C010] analyzeFileBasic() called for: ${filePath}`);
183
+ console.log(`🔧 [C010] semanticEngine exists: ${!!this.semanticEngine}`);
184
+ console.log(`🔧 [C010] symbolAnalyzer exists: ${!!this.symbolAnalyzer}`);
185
+ console.log(`🔧 [C010] regexAnalyzer exists: ${!!this.regexAnalyzer}`);
278
186
 
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
- };
187
+ try {
188
+ // Try symbol-based analysis first
189
+ if (this.semanticEngine?.isSymbolEngineReady?.() &&
190
+ this.semanticEngine.project) {
191
+
192
+ if (this.verbose) {
193
+ console.log(`🔍 [C010] Using symbol-based analysis for ${filePath}`);
296
194
  }
195
+
196
+ const violations = await this.symbolAnalyzer.analyzeFileBasic(filePath, options);
197
+ return violations;
297
198
  }
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 };
199
+ } catch (error) {
200
+ if (this.verbose) {
201
+ console.warn(`⚠️ [C010] Symbol analysis failed: ${error.message}`);
312
202
  }
313
203
  }
314
204
 
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();
205
+ // Fallback to regex-based analysis
206
+ if (this.verbose) {
207
+ console.log(`🔄 [C010] Using regex-based analysis (fallback) for ${filePath}`);
327
208
  }
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
209
 
353
- return testPatterns.some(pattern => pattern.test(filePath));
210
+ console.log(`🔧 [C010] About to call regexAnalyzer.analyzeFileBasic()`);
211
+ try {
212
+ const result = await this.regexAnalyzer.analyzeFileBasic(filePath, options);
213
+ console.log(`🔧 [C010] Regex analyzer returned: ${result.length} violations`);
214
+ return result;
215
+ } catch (error) {
216
+ console.error(`🔧 [C010] Error in regex analyzer:`, error);
217
+ return [];
218
+ }
354
219
  }
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
- };
220
+
221
+ /**
222
+ * Methods for compatibility with different engine invocation patterns
223
+ */
224
+ async analyzeFileWithSymbols(filePath, options = {}) {
225
+ return this.analyzeFile(filePath, options);
372
226
  }
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)];
227
+
228
+ async analyzeWithSemantics(filePath, options = {}) {
229
+ return this.analyzeFile(filePath, options);
386
230
  }
387
231
  }
388
232
 
389
- module.exports = C010LimitBlockNestingAnalyzer;
233
+ module.exports = C010Analyzer;
@@ -0,0 +1,64 @@
1
+ {
2
+ "id": "C010_limit_block_nesting",
3
+ "name": "C010_limit_block_nesting",
4
+ "category": "complexity",
5
+ "description": "C010 - Limit nested blocks (if/for/while/switch/try) to maximum 3 levels for readability",
6
+ "severity": "warning",
7
+ "enabled": true,
8
+ "semantic": {
9
+ "enabled": true,
10
+ "priority": "high",
11
+ "fallback": "heuristic"
12
+ },
13
+ "patterns": {
14
+ "include": [
15
+ "**/*.js",
16
+ "**/*.ts",
17
+ "**/*.jsx",
18
+ "**/*.tsx"
19
+ ],
20
+ "exclude": [
21
+ "**/*.test.js",
22
+ "**/*.test.ts",
23
+ "**/*.spec.js",
24
+ "**/*.spec.ts",
25
+ "**/node_modules/**",
26
+ "**/dist/**",
27
+ "**/build/**"
28
+ ]
29
+ },
30
+ "analysis": {
31
+ "approach": "symbol-based-primary",
32
+ "fallback": "regex-based",
33
+ "depth": 3,
34
+ "timeout": 5000
35
+ },
36
+ "nesting": {
37
+ "maxDepth": 3,
38
+ "countingStatements": [
39
+ "IfStatement",
40
+ "ForStatement",
41
+ "ForInStatement",
42
+ "ForOfStatement",
43
+ "WhileStatement",
44
+ "DoStatement",
45
+ "SwitchStatement",
46
+ "TryStatement",
47
+ "CatchClause"
48
+ ],
49
+ "ignoredStatements": [
50
+ "FunctionDeclaration",
51
+ "MethodDeclaration",
52
+ "ArrowFunction",
53
+ "FunctionExpression",
54
+ "Constructor",
55
+ "ClassDeclaration",
56
+ "InterfaceDeclaration"
57
+ ]
58
+ },
59
+ "options": {
60
+ "ignoreComments": true,
61
+ "countEmptyBlocks": false,
62
+ "treatCatchAsBlock": true
63
+ }
64
+ }