@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.
- package/CHANGELOG.md +115 -1
- package/CONTRIBUTING.md +249 -605
- package/README.md +3 -4
- package/config/ci-cd.json +54 -0
- package/config/development.json +56 -0
- package/config/large-project.json +143 -0
- package/config/presets/all.json +0 -1
- package/config/release.json +70 -0
- package/config/rule-analysis-strategies.js +38 -3
- package/config/rules/enhanced-rules-registry.json +474 -1179
- package/config/rules/rules-registry-generated.json +3 -3
- package/core/cli-action-handler.js +24 -30
- package/core/cli-program.js +11 -3
- package/core/config-merger.js +29 -2
- package/core/enhanced-rules-registry.js +3 -2
- package/core/semantic-engine.js +129 -19
- package/core/semantic-rule-base.js +4 -2
- package/core/unified-rule-registry.js +1 -1
- package/docs/COMMAND-EXAMPLES.md +134 -0
- package/docs/LARGE-PROJECT-GUIDE.md +324 -0
- package/engines/heuristic-engine.js +135 -16
- package/integrations/eslint/plugin/index.js +0 -2
- package/integrations/eslint/plugin/rules/common/c003-no-vague-abbreviations.js +59 -1
- package/integrations/eslint/plugin/rules/common/c006-function-name-verb-noun.js +26 -1
- package/integrations/eslint/plugin/rules/common/c030-use-custom-error-classes.js +54 -19
- package/origin-rules/common-en.md +19 -15
- package/package.json +1 -1
- package/rules/common/C002_no_duplicate_code/analyzer.js +334 -36
- package/rules/common/C003_no_vague_abbreviations/analyzer.js +220 -35
- package/rules/common/C006_function_naming/analyzer.js +29 -3
- package/rules/common/C010_limit_block_nesting/analyzer.js +181 -337
- package/rules/common/C010_limit_block_nesting/config.json +64 -0
- package/rules/common/C010_limit_block_nesting/regex-based-analyzer.js +379 -0
- package/rules/common/C010_limit_block_nesting/symbol-based-analyzer.js +231 -0
- package/rules/common/C013_no_dead_code/analyzer.js +75 -177
- package/rules/common/C013_no_dead_code/config.json +61 -0
- package/rules/common/C013_no_dead_code/regex-based-analyzer.js +345 -0
- package/rules/common/C013_no_dead_code/symbol-based-analyzer.js +640 -0
- package/rules/common/C014_dependency_injection/analyzer.js +48 -313
- package/rules/common/C014_dependency_injection/config.json +26 -0
- package/rules/common/C014_dependency_injection/symbol-based-analyzer.js +751 -0
- package/rules/common/C017_constructor_logic/analyzer.js +254 -17
- package/rules/common/C017_constructor_logic/semantic-analyzer.js +340 -0
- package/rules/common/C018_no_throw_generic_error/analyzer.js +232 -0
- package/rules/common/C018_no_throw_generic_error/config.json +50 -0
- package/rules/common/C018_no_throw_generic_error/regex-based-analyzer.js +387 -0
- package/rules/common/C018_no_throw_generic_error/symbol-based-analyzer.js +314 -0
- package/rules/common/C019_log_level_usage/analyzer.js +110 -317
- package/rules/common/C019_log_level_usage/pattern-analyzer.js +88 -0
- package/rules/common/C019_log_level_usage/system-log-analyzer.js +1267 -0
- package/rules/common/C023_no_duplicate_variable/analyzer.js +180 -0
- package/rules/common/C023_no_duplicate_variable/config.json +50 -0
- package/rules/common/C023_no_duplicate_variable/symbol-based-analyzer.js +158 -0
- package/rules/common/C024_no_scatter_hardcoded_constants/analyzer.js +180 -0
- package/rules/common/C024_no_scatter_hardcoded_constants/config.json +50 -0
- package/rules/common/C024_no_scatter_hardcoded_constants/symbol-based-analyzer.js +181 -0
- package/rules/common/C030_use_custom_error_classes/analyzer.js +200 -0
- package/rules/common/C033_separate_service_repository/README.md +78 -0
- package/rules/common/C033_separate_service_repository/analyzer.js +160 -0
- package/rules/common/C033_separate_service_repository/config.json +50 -0
- package/rules/common/C033_separate_service_repository/regex-based-analyzer.js +585 -0
- package/rules/common/C033_separate_service_repository/symbol-based-analyzer.js +368 -0
- package/rules/common/C035_error_logging_context/STRATEGY.md +99 -0
- package/rules/common/C035_error_logging_context/analyzer.js +232 -0
- package/rules/common/C035_error_logging_context/config.json +54 -0
- package/rules/common/C035_error_logging_context/regex-based-analyzer.js +299 -0
- package/rules/common/C035_error_logging_context/symbol-based-analyzer.js +454 -0
- package/rules/common/C040_centralized_validation/analyzer.js +165 -0
- package/rules/common/C040_centralized_validation/config.json +46 -0
- package/rules/common/C040_centralized_validation/regex-based-analyzer.js +243 -0
- package/rules/common/C040_centralized_validation/symbol-based-analyzer.js +416 -0
- package/rules/common/{C076_single_test_behavior → C072_single_test_behavior}/analyzer.js +6 -6
- package/rules/common/C076_explicit_function_types/README.md +30 -0
- package/rules/common/C076_explicit_function_types/analyzer.js +172 -0
- package/rules/common/C076_explicit_function_types/config.json +15 -0
- package/rules/common/C076_explicit_function_types/semantic-analyzer.js +341 -0
- package/rules/index.js +6 -1
- package/rules/parser/rule-parser.js +13 -2
- package/rules/security/S005_no_origin_auth/README.md +226 -0
- package/rules/security/S005_no_origin_auth/analyzer.js +184 -0
- package/rules/security/S005_no_origin_auth/ast-analyzer.js +406 -0
- package/rules/security/S005_no_origin_auth/config.json +85 -0
- package/rules/security/S006_no_plaintext_recovery_codes/README.md +139 -0
- package/rules/security/S006_no_plaintext_recovery_codes/analyzer.js +306 -0
- package/rules/security/S006_no_plaintext_recovery_codes/config.json +48 -0
- package/rules/security/S007_no_plaintext_otp/README.md +198 -0
- package/rules/security/S007_no_plaintext_otp/analyzer.js +406 -0
- package/rules/security/S007_no_plaintext_otp/config.json +79 -0
- package/rules/security/S007_no_plaintext_otp/semantic-analyzer.js +609 -0
- package/rules/security/S007_no_plaintext_otp/semantic-config.json +195 -0
- package/rules/security/S007_no_plaintext_otp/semantic-wrapper.js +280 -0
- package/rules/security/S009_no_insecure_encryption/README.md +158 -0
- package/rules/security/S009_no_insecure_encryption/analyzer.js +319 -0
- package/rules/security/S009_no_insecure_encryption/config.json +55 -0
- package/rules/security/S010_no_insecure_encryption/README.md +224 -0
- package/rules/security/S010_no_insecure_encryption/analyzer.js +493 -0
- package/rules/security/S010_no_insecure_encryption/config.json +48 -0
- package/rules/security/S016_no_sensitive_querystring/STRATEGY.md +149 -0
- package/rules/security/S016_no_sensitive_querystring/analyzer.js +276 -0
- package/rules/security/S016_no_sensitive_querystring/config.json +127 -0
- package/rules/security/S016_no_sensitive_querystring/regex-based-analyzer.js +258 -0
- package/rules/security/S016_no_sensitive_querystring/symbol-based-analyzer.js +495 -0
- package/rules/security/S027_no_hardcoded_secrets/analyzer.js +180 -366
- package/rules/security/S027_no_hardcoded_secrets/categories.json +153 -0
- package/rules/security/S027_no_hardcoded_secrets/categorized-analyzer.js +250 -0
- package/rules/security/S048_no_current_password_in_reset/README.md +222 -0
- package/rules/security/S048_no_current_password_in_reset/analyzer.js +366 -0
- package/rules/security/S048_no_current_password_in_reset/config.json +48 -0
- package/rules/security/S055_content_type_validation/README.md +176 -0
- package/rules/security/S055_content_type_validation/analyzer.js +312 -0
- package/rules/security/S055_content_type_validation/config.json +48 -0
- package/rules/utils/rule-helpers.js +140 -1
- package/scripts/consolidate-config.js +116 -0
- package/scripts/prepare-release.sh +1 -1
- package/config/rules/rules-registry.json +0 -765
- package/docs/ESLINT-INTEGRATION-STRATEGY.md +0 -392
- package/docs/FUTURE_PACKAGES.md +0 -83
- package/docs/HEURISTIC_VS_AI.md +0 -113
- package/docs/PRODUCTION_DEPLOYMENT_ANALYSIS.md +0 -112
- package/docs/PRODUCTION_SIZE_IMPACT.md +0 -183
- package/docs/RELEASE_GUIDE.md +0 -230
- package/docs/STANDARDIZED-CATEGORY-FILTERING.md +0 -156
- package/integrations/eslint/plugin/rules/common/c076-single-behavior-per-test.js +0 -254
- package/rules/common/C006_function_naming/smart-analyzer.js +0 -503
|
@@ -1,389 +1,233 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* C010
|
|
3
|
-
*
|
|
4
|
-
*
|
|
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
|
|
10
|
-
const
|
|
7
|
+
const C010SymbolBasedAnalyzer = require('./symbol-based-analyzer.js');
|
|
8
|
+
const C010RegexBasedAnalyzer = require('./regex-based-analyzer.js');
|
|
11
9
|
|
|
12
|
-
class
|
|
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 = '
|
|
17
|
-
this.
|
|
18
|
-
this.
|
|
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
|
-
//
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
73
|
-
|
|
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 (
|
|
76
|
-
|
|
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 (
|
|
82
|
-
|
|
86
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
87
|
+
console.log(`🔧 [C010] Processing file: ${filePath}`);
|
|
83
88
|
}
|
|
84
89
|
|
|
85
|
-
const
|
|
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(
|
|
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,
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
//
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
176
|
-
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
-
|
|
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
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
-
|
|
280
|
-
|
|
281
|
-
if (
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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
|
-
|
|
375
|
-
|
|
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 =
|
|
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
|
+
}
|