@sun-asterisk/sunlint 1.2.1 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/rule-analysis-strategies.js +18 -2
- package/engines/eslint-engine.js +9 -11
- package/engines/heuristic-engine.js +55 -31
- package/package.json +2 -1
- package/rules/README.md +252 -0
- package/rules/common/C002_no_duplicate_code/analyzer.js +65 -0
- package/rules/common/C002_no_duplicate_code/config.json +23 -0
- package/rules/common/C003_no_vague_abbreviations/analyzer.js +418 -0
- package/rules/common/C003_no_vague_abbreviations/config.json +35 -0
- package/rules/common/C006_function_naming/analyzer.js +504 -0
- package/rules/common/C006_function_naming/config.json +86 -0
- package/rules/common/C006_function_naming/smart-analyzer.js +503 -0
- package/rules/common/C010_limit_block_nesting/analyzer.js +389 -0
- package/rules/common/C012_command_query_separation/analyzer.js +481 -0
- package/rules/common/C012_command_query_separation/ast-analyzer.js +495 -0
- package/rules/common/C013_no_dead_code/analyzer.js +206 -0
- package/rules/common/C014_dependency_injection/analyzer.js +338 -0
- package/rules/common/C017_constructor_logic/analyzer.js +314 -0
- package/rules/common/C019_log_level_usage/analyzer.js +362 -0
- package/rules/common/C019_log_level_usage/config.json +121 -0
- package/rules/common/C029_catch_block_logging/analyzer-backup.js +426 -0
- package/rules/common/C029_catch_block_logging/analyzer-fixed.js +130 -0
- package/rules/common/C029_catch_block_logging/analyzer-multi-tech.js +487 -0
- package/rules/common/C029_catch_block_logging/analyzer-simple.js +110 -0
- package/rules/common/C029_catch_block_logging/analyzer-smart-pipeline.js +755 -0
- package/rules/common/C029_catch_block_logging/analyzer.js +129 -0
- package/rules/common/C029_catch_block_logging/ast-analyzer-backup.js +441 -0
- package/rules/common/C029_catch_block_logging/ast-analyzer-new.js +127 -0
- package/rules/common/C029_catch_block_logging/ast-analyzer.js +133 -0
- package/rules/common/C029_catch_block_logging/cfg-analyzer.js +408 -0
- package/rules/common/C029_catch_block_logging/config.json +59 -0
- package/rules/common/C029_catch_block_logging/dataflow-analyzer.js +454 -0
- package/rules/common/C029_catch_block_logging/multi-language-ast-engine.js +700 -0
- package/rules/common/C029_catch_block_logging/pattern-learning-analyzer.js +568 -0
- package/rules/common/C029_catch_block_logging/semantic-analyzer.js +459 -0
- package/rules/common/C031_validation_separation/analyzer.js +186 -0
- package/rules/common/C041_no_sensitive_hardcode/analyzer.js +292 -0
- package/rules/common/C041_no_sensitive_hardcode/ast-analyzer.js +296 -0
- package/rules/common/C042_boolean_name_prefix/analyzer.js +300 -0
- package/rules/common/C043_no_console_or_print/analyzer.js +431 -0
- package/rules/common/C047_no_duplicate_retry_logic/analyzer.js +590 -0
- package/rules/common/C075_explicit_return_types/analyzer.js +103 -0
- package/rules/common/C076_single_test_behavior/analyzer.js +121 -0
- package/rules/docs/C002_no_duplicate_code.md +57 -0
- package/rules/docs/C031_validation_separation.md +72 -0
- package/rules/index.js +155 -0
- package/rules/migration/converter.js +385 -0
- package/rules/migration/mapping.json +164 -0
- package/rules/parser/constants.js +31 -0
- package/rules/parser/file-config.js +80 -0
- package/rules/parser/rule-parser-simple.js +305 -0
- package/rules/parser/rule-parser.js +527 -0
- package/rules/security/S015_insecure_tls_certificate/analyzer.js +150 -0
- package/rules/security/S015_insecure_tls_certificate/ast-analyzer.js +237 -0
- package/rules/security/S023_no_json_injection/analyzer.js +278 -0
- package/rules/security/S023_no_json_injection/ast-analyzer.js +359 -0
- package/rules/security/S026_json_schema_validation/analyzer.js +251 -0
- package/rules/security/S026_json_schema_validation/config.json +27 -0
- package/rules/security/S027_no_hardcoded_secrets/analyzer.js +436 -0
- package/rules/security/S027_no_hardcoded_secrets/config.json +29 -0
- package/rules/security/S029_csrf_protection/analyzer.js +330 -0
- package/rules/tests/C002_no_duplicate_code.test.js +50 -0
- package/rules/universal/C010/generic.js +0 -0
- package/rules/universal/C010/tree-sitter-analyzer.js +0 -0
- package/rules/utils/ast-utils.js +191 -0
- package/rules/utils/base-analyzer.js +98 -0
- package/rules/utils/pattern-matchers.js +239 -0
- package/rules/utils/rule-helpers.js +264 -0
- package/rules/utils/severity-constants.js +93 -0
- package/scripts/generate_insights.js +188 -0
- package/scripts/merge-reports.js +0 -424
- package/scripts/test-scripts/README.md +0 -22
- package/scripts/test-scripts/test-c041-comparison.js +0 -114
- package/scripts/test-scripts/test-c041-eslint.js +0 -67
- package/scripts/test-scripts/test-eslint-rules.js +0 -146
- package/scripts/test-scripts/test-real-world.js +0 -44
- package/scripts/test-scripts/test-rules-on-real-projects.js +0 -86
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* C029 Analyzer - Smart Pipeline Integration
|
|
3
|
+
*
|
|
4
|
+
* This analyzer forwards to the Smart Pipeline for superior accuracy and performance
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
class C029Analyzer {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.ruleId = 'C029';
|
|
13
|
+
this.ruleName = 'Enhanced Catch Block Error Logging';
|
|
14
|
+
this.description = 'Mọi catch block phải log nguyên nhân lỗi đầy đủ và bảo toàn context (Smart Pipeline 3-stage analysis)';
|
|
15
|
+
|
|
16
|
+
// Load Smart Pipeline as primary analyzer
|
|
17
|
+
this.smartPipeline = null;
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
this.smartPipeline = require('./analyzer-smart-pipeline.js');
|
|
21
|
+
console.log('🎯 C029: Smart Pipeline loaded (3-stage: Regex → AST → Data Flow)');
|
|
22
|
+
} catch (error) {
|
|
23
|
+
console.warn('⚠️ C029: Smart Pipeline failed, using fallback:', error.message);
|
|
24
|
+
this.smartPipeline = null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async analyze(files, language, options = {}) {
|
|
29
|
+
// Use Smart Pipeline as primary choice
|
|
30
|
+
if (this.smartPipeline) {
|
|
31
|
+
console.log('🎯 C029: Using Smart Pipeline (3-stage analysis)...');
|
|
32
|
+
return await this.smartPipeline.analyze(files, language, options);
|
|
33
|
+
} else {
|
|
34
|
+
console.log('🔍 C029: Using fallback regex analysis...');
|
|
35
|
+
return await this.analyzeWithRegex(files, language, options);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async analyzeWithRegex(files, language, options = {}) {
|
|
40
|
+
const violations = [];
|
|
41
|
+
|
|
42
|
+
for (const filePath of files) {
|
|
43
|
+
if (options.verbose) {
|
|
44
|
+
console.log(`🔍 C029 Regex: Processing ${path.basename(filePath)}...`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
49
|
+
const fileViolations = await this.analyzeFile(filePath, content, language);
|
|
50
|
+
violations.push(...fileViolations);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.warn(`⚠️ C029: Error processing ${filePath}:`, error.message);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return violations;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async analyzeFile(filePath, content, language) {
|
|
60
|
+
const violations = [];
|
|
61
|
+
const lines = content.split('\n');
|
|
62
|
+
|
|
63
|
+
for (let i = 0; i < lines.length; i++) {
|
|
64
|
+
const line = lines[i];
|
|
65
|
+
|
|
66
|
+
// Simple catch block detection
|
|
67
|
+
if (line.includes('catch') && line.includes('(')) {
|
|
68
|
+
const catchBlock = this.extractCatchBlock(lines, i);
|
|
69
|
+
|
|
70
|
+
if (this.isCatchBlockEmpty(catchBlock.content)) {
|
|
71
|
+
violations.push({
|
|
72
|
+
file: filePath,
|
|
73
|
+
line: i + 1,
|
|
74
|
+
column: line.indexOf('catch') + 1,
|
|
75
|
+
message: 'Empty catch block detected',
|
|
76
|
+
severity: 'error',
|
|
77
|
+
ruleId: this.ruleId,
|
|
78
|
+
type: 'empty_catch'
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return violations;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
extractCatchBlock(lines, startIndex) {
|
|
88
|
+
const content = [];
|
|
89
|
+
let braceCount = 0;
|
|
90
|
+
let inBlock = false;
|
|
91
|
+
|
|
92
|
+
for (let i = startIndex; i < lines.length; i++) {
|
|
93
|
+
const line = lines[i];
|
|
94
|
+
content.push(line);
|
|
95
|
+
|
|
96
|
+
for (const char of line) {
|
|
97
|
+
if (char === '{') {
|
|
98
|
+
braceCount++;
|
|
99
|
+
inBlock = true;
|
|
100
|
+
} else if (char === '}') {
|
|
101
|
+
braceCount--;
|
|
102
|
+
if (braceCount === 0 && inBlock) {
|
|
103
|
+
return { content, endIndex: i };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return { content, endIndex: startIndex };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
isCatchBlockEmpty(content) {
|
|
113
|
+
const blockContent = content.join('\n');
|
|
114
|
+
|
|
115
|
+
// Remove comments and whitespace
|
|
116
|
+
const cleanContent = blockContent
|
|
117
|
+
.replace(/\/\*[\s\S]*?\*\//g, '') // Remove multi-line comments
|
|
118
|
+
.replace(/\/\/.*$/gm, '') // Remove single-line comments
|
|
119
|
+
.replace(/\s+/g, ' ') // Normalize whitespace
|
|
120
|
+
.trim();
|
|
121
|
+
|
|
122
|
+
// Check if only contains catch declaration and braces
|
|
123
|
+
const hasOnlyStructure = /^catch\s*\([^)]*\)\s*\{\s*\}$/.test(cleanContent);
|
|
124
|
+
|
|
125
|
+
return hasOnlyStructure;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
module.exports = C029Analyzer;
|
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AST-based C029 Analyzer - Smart Pipeline Integration
|
|
3
|
+
*
|
|
4
|
+
* This analyzer forwards to the Smart Pipeline for superior accuracy and performance
|
|
5
|
+
* Maintains compatibility with the heuristic engine's AST expectations
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
class C029ASTAnalyzer {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.ruleId = 'C029';
|
|
11
|
+
this.ruleName = 'AST-Enhanced Catch Block Error Logging (Smart Pipeline)';
|
|
12
|
+
this.description = 'Catch blocks must log errors - using Smart Pipeline 3-stage analysis';
|
|
13
|
+
|
|
14
|
+
// Load Smart Pipeline
|
|
15
|
+
this.smartPipeline = null;
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
this.smartPipeline = require('./analyzer-smart-pipeline.js');
|
|
19
|
+
console.log('🎯 C029 AST: Smart Pipeline loaded (Regex → AST → Data Flow)');
|
|
20
|
+
} catch (error) {
|
|
21
|
+
console.warn('⚠️ C029 AST: Smart Pipeline failed:', error.message);
|
|
22
|
+
this.smartPipeline = null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async analyze(files, language, options = {}) {
|
|
27
|
+
// Use Smart Pipeline if available
|
|
28
|
+
if (this.smartPipeline) {
|
|
29
|
+
console.log('🎯 C029 AST: Using Smart Pipeline (3-stage analysis)...');
|
|
30
|
+
return await this.smartPipeline.analyze(files, language, options);
|
|
31
|
+
} else {
|
|
32
|
+
console.log('🔍 C029 AST: Using fallback analysis...');
|
|
33
|
+
return await this.fallbackAnalysis(files, language, options);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async fallbackAnalysis(files, language, options = {}) {
|
|
38
|
+
const violations = [];
|
|
39
|
+
const fs = require('fs');
|
|
40
|
+
const path = require('path');
|
|
41
|
+
|
|
42
|
+
console.log(`🔍 C029 AST: Processing ${files.length} files with fallback analysis...`);
|
|
43
|
+
|
|
44
|
+
for (const filePath of files) {
|
|
45
|
+
try {
|
|
46
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
47
|
+
const fileViolations = await this.analyzeFile(filePath, content, language);
|
|
48
|
+
violations.push(...fileViolations);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.warn(`⚠️ C029 AST: Error processing ${filePath}:`, error.message);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return violations;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async analyzeFile(filePath, content, language) {
|
|
58
|
+
const fileLanguage = this.getLanguageFromPath(filePath);
|
|
59
|
+
const ast = await this.parseAST(content, fileLanguage, filePath);
|
|
60
|
+
|
|
61
|
+
if (ast) {
|
|
62
|
+
const astViolations = this.analyzeCatchBlocksWithAST(ast, catchBlocks, filePath, content);
|
|
63
|
+
violations.push(...astViolations);
|
|
64
|
+
} else {
|
|
65
|
+
// Fallback to regex if AST fails
|
|
66
|
+
const regexViolations = this.analyzeCatchBlocksWithRegex(catchBlocks, filePath, content);
|
|
67
|
+
violations.push(...regexViolations);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.warn(`C029 AST skipping ${filePath}: ${error.message}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Give Node.js a chance to breathe between batches
|
|
76
|
+
if (i + batchSize < totalFiles) {
|
|
77
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return violations;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Step 1: Fast regex-based catch block detection
|
|
86
|
+
*/
|
|
87
|
+
findCatchBlocksWithRegex(content, filePath) {
|
|
88
|
+
const catchBlocks = [];
|
|
89
|
+
const lines = content.split('\n');
|
|
90
|
+
|
|
91
|
+
lines.forEach((line, index) => {
|
|
92
|
+
const trimmedLine = line.trim();
|
|
93
|
+
if (this.isCatchBlockStart(trimmedLine)) {
|
|
94
|
+
// Extract catch block info using existing logic
|
|
95
|
+
const catchBlockInfo = this.extractCatchBlockInfo(lines, index);
|
|
96
|
+
catchBlocks.push({
|
|
97
|
+
startLine: index + 1,
|
|
98
|
+
content: catchBlockInfo.content,
|
|
99
|
+
parameter: this.extractCatchParameter(trimmedLine)
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
return catchBlocks;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Step 2: AST-based analysis for precise content understanding
|
|
109
|
+
*/
|
|
110
|
+
async parseAST(content, language, filePath) {
|
|
111
|
+
try {
|
|
112
|
+
if (!this.astRegistry || !this.astRegistry.parseCode) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return await this.astRegistry.parseCode(content, language, filePath);
|
|
117
|
+
} catch (error) {
|
|
118
|
+
// AST parsing failed, fall back to regex
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
analyzeCatchBlocksWithAST(ast, catchBlocks, filePath, content) {
|
|
124
|
+
const violations = [];
|
|
125
|
+
const isTestFile = this.isTestFile(filePath);
|
|
126
|
+
|
|
127
|
+
for (const catchBlock of catchBlocks) {
|
|
128
|
+
const analysis = this.analyzeSingleCatchBlock(ast, catchBlock, filePath, content, isTestFile);
|
|
129
|
+
|
|
130
|
+
if (analysis.isViolation) {
|
|
131
|
+
violations.push({
|
|
132
|
+
ruleId: this.ruleId,
|
|
133
|
+
file: filePath,
|
|
134
|
+
line: catchBlock.startLine,
|
|
135
|
+
column: 1,
|
|
136
|
+
message: analysis.message,
|
|
137
|
+
severity: analysis.severity,
|
|
138
|
+
code: catchBlock.content.split('\n')[0].trim(),
|
|
139
|
+
type: analysis.type,
|
|
140
|
+
confidence: analysis.confidence,
|
|
141
|
+
suggestion: analysis.suggestion
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return violations;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
analyzeSingleCatchBlock(ast, catchBlock, filePath, content, isTestFile) {
|
|
150
|
+
const parameter = catchBlock.parameter;
|
|
151
|
+
const catchContent = catchBlock.content.toLowerCase();
|
|
152
|
+
const originalContent = catchBlock.content;
|
|
153
|
+
|
|
154
|
+
// 1. Check for explicit ignore patterns
|
|
155
|
+
if (this.config.allowExplicitIgnore && this.isExplicitlyIgnored(parameter, catchContent)) {
|
|
156
|
+
return { isViolation: false, reason: 'explicitly_ignored' };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// 2. Empty catch block - always violation
|
|
160
|
+
if (this.isCatchBlockEmpty(catchContent)) {
|
|
161
|
+
return {
|
|
162
|
+
isViolation: true,
|
|
163
|
+
type: 'empty_catch_block',
|
|
164
|
+
message: 'Empty catch block - error is silently ignored',
|
|
165
|
+
severity: 'error',
|
|
166
|
+
confidence: 1.0,
|
|
167
|
+
suggestion: 'Add error logging or explicit ignore comment'
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// 3. Test file context - allow test assertions
|
|
172
|
+
if (isTestFile && this.config.allowInTests && this.hasTestAssertions(catchContent, originalContent)) {
|
|
173
|
+
return { isViolation: false, reason: 'test_assertions' };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// 4. Redux/async patterns - allow thunk error handling
|
|
177
|
+
if (this.config.allowReduxPatterns && this.hasReduxErrorHandling(catchContent, originalContent)) {
|
|
178
|
+
return { isViolation: false, reason: 'redux_patterns' };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// 5. Check for logging or rethrowing
|
|
182
|
+
if (this.hasLoggingOrRethrow(catchContent, originalContent)) {
|
|
183
|
+
return { isViolation: false, reason: 'has_logging' };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// 6. Silent catch block - violation
|
|
187
|
+
return {
|
|
188
|
+
isViolation: true,
|
|
189
|
+
type: 'silent_catch_block',
|
|
190
|
+
message: 'Catch block must log error or rethrow - silent error handling hides bugs',
|
|
191
|
+
severity: 'error',
|
|
192
|
+
confidence: 0.9,
|
|
193
|
+
suggestion: 'Add error logging (console.error, logger.error) or rethrow the error'
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Language-specific explicit ignore patterns
|
|
199
|
+
*/
|
|
200
|
+
isExplicitlyIgnored(parameter, content) {
|
|
201
|
+
// Java style: catch(Exception ignored)
|
|
202
|
+
if (parameter && parameter.includes('ignored')) {
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Dart style: catch(_)
|
|
207
|
+
if (parameter && parameter.trim() === '_') {
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Explicit ignore comments
|
|
212
|
+
const ignorePatterns = [
|
|
213
|
+
'// ignore',
|
|
214
|
+
'/* ignore',
|
|
215
|
+
'// todo',
|
|
216
|
+
'// intentionally',
|
|
217
|
+
'// suppress'
|
|
218
|
+
];
|
|
219
|
+
|
|
220
|
+
return ignorePatterns.some(pattern => content.includes(pattern));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Test file detection
|
|
225
|
+
*/
|
|
226
|
+
isTestFile(filePath) {
|
|
227
|
+
const testPatterns = [
|
|
228
|
+
'__tests__',
|
|
229
|
+
'.test.',
|
|
230
|
+
'.spec.',
|
|
231
|
+
'/test/',
|
|
232
|
+
'/tests/',
|
|
233
|
+
'test-',
|
|
234
|
+
'spec-'
|
|
235
|
+
];
|
|
236
|
+
|
|
237
|
+
return testPatterns.some(pattern => filePath.includes(pattern));
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Enhanced test assertion detection
|
|
242
|
+
*/
|
|
243
|
+
hasTestAssertions(content, originalContent) {
|
|
244
|
+
const testPatterns = [
|
|
245
|
+
'expect(',
|
|
246
|
+
'assert(',
|
|
247
|
+
'should.',
|
|
248
|
+
'toequal(',
|
|
249
|
+
'tobecalled',
|
|
250
|
+
'tocontain',
|
|
251
|
+
'tohavebeencalled',
|
|
252
|
+
'tobe(',
|
|
253
|
+
'chai.',
|
|
254
|
+
'sinon.',
|
|
255
|
+
'jest.'
|
|
256
|
+
];
|
|
257
|
+
|
|
258
|
+
return testPatterns.some(pattern =>
|
|
259
|
+
content.includes(pattern.toLowerCase()) ||
|
|
260
|
+
originalContent.toLowerCase().includes(pattern.toLowerCase())
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Redux/async error handling patterns
|
|
266
|
+
*/
|
|
267
|
+
hasReduxErrorHandling(content, originalContent) {
|
|
268
|
+
const reduxPatterns = [
|
|
269
|
+
'rejectwithvalue',
|
|
270
|
+
'dispatch(',
|
|
271
|
+
'handleaxioserror',
|
|
272
|
+
'seterror(',
|
|
273
|
+
'return value',
|
|
274
|
+
'thunk'
|
|
275
|
+
];
|
|
276
|
+
|
|
277
|
+
return reduxPatterns.some(pattern =>
|
|
278
|
+
content.includes(pattern.toLowerCase()) ||
|
|
279
|
+
originalContent.toLowerCase().includes(pattern.toLowerCase())
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Enhanced logging detection
|
|
285
|
+
*/
|
|
286
|
+
hasLoggingOrRethrow(content, originalContent) {
|
|
287
|
+
// Throw statements
|
|
288
|
+
if (content.includes('throw ') || content.includes('throw;')) {
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Function calls with error parameter - likely error handlers
|
|
293
|
+
const errorHandlerPatterns = [
|
|
294
|
+
'handle',
|
|
295
|
+
'process',
|
|
296
|
+
'report',
|
|
297
|
+
'track',
|
|
298
|
+
'capture',
|
|
299
|
+
'send',
|
|
300
|
+
'notify'
|
|
301
|
+
];
|
|
302
|
+
|
|
303
|
+
// Check for function calls that take error as parameter
|
|
304
|
+
if (originalContent.match(/\w+\s*\(\s*error?\s*\)/i)) {
|
|
305
|
+
return true;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Check for error handler function calls
|
|
309
|
+
if (errorHandlerPatterns.some(pattern =>
|
|
310
|
+
originalContent.toLowerCase().includes(pattern + 'error') ||
|
|
311
|
+
originalContent.toLowerCase().includes(pattern + '(error') ||
|
|
312
|
+
originalContent.toLowerCase().includes(pattern + '(err')
|
|
313
|
+
)) {
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Logging patterns
|
|
318
|
+
const loggingPatterns = [
|
|
319
|
+
'console.error',
|
|
320
|
+
'console.log',
|
|
321
|
+
'console.warn',
|
|
322
|
+
'logger.error',
|
|
323
|
+
'log.error',
|
|
324
|
+
'logger.warn',
|
|
325
|
+
'winston.error',
|
|
326
|
+
'bunyan.error',
|
|
327
|
+
'pino.error',
|
|
328
|
+
'.error(',
|
|
329
|
+
'.warn(',
|
|
330
|
+
'.log(',
|
|
331
|
+
'handleerror',
|
|
332
|
+
'logerror'
|
|
333
|
+
];
|
|
334
|
+
|
|
335
|
+
return loggingPatterns.some(pattern =>
|
|
336
|
+
content.includes(pattern) || originalContent.includes(pattern)
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Utility methods (reuse existing logic)
|
|
341
|
+
isCatchBlockStart(line) {
|
|
342
|
+
return line.includes('catch (') || line.includes('catch(');
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
extractCatchParameter(line) {
|
|
346
|
+
const match = line.match(/catch\s*\(\s*([^)]+)\s*\)/);
|
|
347
|
+
return match ? match[1].trim() : null;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
extractCatchBlockInfo(lines, startIndex) {
|
|
351
|
+
const catchBlockLines = [];
|
|
352
|
+
let braceDepth = 0;
|
|
353
|
+
let foundCatchBrace = false;
|
|
354
|
+
|
|
355
|
+
for (let i = startIndex; i < lines.length; i++) {
|
|
356
|
+
const line = lines[i];
|
|
357
|
+
catchBlockLines.push(line);
|
|
358
|
+
|
|
359
|
+
if (this.isCatchBlockStart(line.trim())) {
|
|
360
|
+
for (const char of line) {
|
|
361
|
+
if (char === '{') {
|
|
362
|
+
foundCatchBrace = true;
|
|
363
|
+
braceDepth = 1;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
} else if (foundCatchBrace) {
|
|
367
|
+
for (const char of line) {
|
|
368
|
+
if (char === '{') braceDepth++;
|
|
369
|
+
else if (char === '}') braceDepth--;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (braceDepth === 0) break;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return {
|
|
377
|
+
content: catchBlockLines.join('\n')
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
isCatchBlockEmpty(content) {
|
|
382
|
+
const cleanedContent = content
|
|
383
|
+
.replace(/\/\*[\s\S]*?\*\//g, '') // Remove block comments
|
|
384
|
+
.replace(/\/\/.*$/gm, '') // Remove line comments
|
|
385
|
+
.replace(/\s+/g, ' ') // Normalize whitespace
|
|
386
|
+
.trim();
|
|
387
|
+
|
|
388
|
+
// Check if only braces remain
|
|
389
|
+
const bodyMatch = cleanedContent.match(/\{(.*)\}/s);
|
|
390
|
+
return !bodyMatch || bodyMatch[1].trim().length === 0;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
getLanguageFromPath(filePath) {
|
|
394
|
+
const ext = filePath.split('.').pop().toLowerCase();
|
|
395
|
+
|
|
396
|
+
const languageMap = {
|
|
397
|
+
'js': 'javascript',
|
|
398
|
+
'jsx': 'javascript',
|
|
399
|
+
'ts': 'typescript',
|
|
400
|
+
'tsx': 'typescript',
|
|
401
|
+
'mjs': 'javascript',
|
|
402
|
+
'cjs': 'javascript',
|
|
403
|
+
'dart': 'dart',
|
|
404
|
+
'java': 'java',
|
|
405
|
+
'kt': 'kotlin'
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
return languageMap[ext] || 'javascript';
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Fallback regex analysis if AST fails
|
|
413
|
+
*/
|
|
414
|
+
analyzeCatchBlocksWithRegex(catchBlocks, filePath, content) {
|
|
415
|
+
const violations = [];
|
|
416
|
+
const isTestFile = this.isTestFile(filePath);
|
|
417
|
+
|
|
418
|
+
for (const catchBlock of catchBlocks) {
|
|
419
|
+
const analysis = this.analyzeSingleCatchBlock(null, catchBlock, filePath, content, isTestFile);
|
|
420
|
+
|
|
421
|
+
if (analysis.isViolation) {
|
|
422
|
+
violations.push({
|
|
423
|
+
ruleId: this.ruleId,
|
|
424
|
+
file: filePath,
|
|
425
|
+
line: catchBlock.startLine,
|
|
426
|
+
column: 1,
|
|
427
|
+
message: analysis.message,
|
|
428
|
+
severity: analysis.severity,
|
|
429
|
+
code: catchBlock.content.split('\n')[0].trim(),
|
|
430
|
+
type: analysis.type,
|
|
431
|
+
confidence: analysis.confidence * 0.8, // Lower confidence for regex fallback
|
|
432
|
+
suggestion: analysis.suggestion
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
return violations;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
module.exports = new C029ASTAnalyzer();
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AST-based C029 Analyzer - Smart Pipeline Integration
|
|
3
|
+
*
|
|
4
|
+
* This analyzer forwards to the Smart Pipeline for superior accuracy and performance
|
|
5
|
+
* Maintains compatibility with the heuristic engine's AST expectations
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
class C029ASTAnalyzer {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.ruleId = 'C029';
|
|
11
|
+
this.ruleName = 'AST-Enhanced Catch Block Error Logging (Smart Pipeline)';
|
|
12
|
+
this.description = 'Catch blocks must log errors - using Smart Pipeline 3-stage analysis';
|
|
13
|
+
|
|
14
|
+
// Load Smart Pipeline
|
|
15
|
+
this.smartPipeline = null;
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
this.smartPipeline = require('./analyzer-smart-pipeline.js');
|
|
19
|
+
console.log('🎯 C029 AST: Smart Pipeline loaded (Regex → AST → Data Flow)');
|
|
20
|
+
} catch (error) {
|
|
21
|
+
console.warn('⚠️ C029 AST: Smart Pipeline failed:', error.message);
|
|
22
|
+
this.smartPipeline = null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async analyze(files, language, options = {}) {
|
|
27
|
+
// Use Smart Pipeline if available
|
|
28
|
+
if (this.smartPipeline) {
|
|
29
|
+
console.log('🎯 C029 AST: Using Smart Pipeline (3-stage analysis)...');
|
|
30
|
+
return await this.smartPipeline.analyze(files, language, options);
|
|
31
|
+
} else {
|
|
32
|
+
console.log('🔍 C029 AST: Using fallback analysis...');
|
|
33
|
+
return await this.fallbackAnalysis(files, language, options);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async fallbackAnalysis(files, language, options = {}) {
|
|
38
|
+
const violations = [];
|
|
39
|
+
const fs = require('fs');
|
|
40
|
+
const path = require('path');
|
|
41
|
+
|
|
42
|
+
console.log(`🔍 C029 AST: Processing ${files.length} files with fallback analysis...`);
|
|
43
|
+
|
|
44
|
+
for (const filePath of files) {
|
|
45
|
+
try {
|
|
46
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
47
|
+
const fileViolations = await this.analyzeFile(filePath, content, language);
|
|
48
|
+
violations.push(...fileViolations);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.warn(`⚠️ C029 AST: Error processing ${filePath}:`, error.message);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return violations;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async analyzeFile(filePath, content, language) {
|
|
58
|
+
const violations = [];
|
|
59
|
+
const lines = content.split('\n');
|
|
60
|
+
|
|
61
|
+
for (let i = 0; i < lines.length; i++) {
|
|
62
|
+
const line = lines[i];
|
|
63
|
+
|
|
64
|
+
// Simple catch block detection for fallback
|
|
65
|
+
if (line.includes('catch') && line.includes('(')) {
|
|
66
|
+
const catchBlock = this.extractCatchBlock(lines, i);
|
|
67
|
+
|
|
68
|
+
if (this.isCatchBlockEmpty(catchBlock.content)) {
|
|
69
|
+
violations.push({
|
|
70
|
+
file: filePath,
|
|
71
|
+
line: i + 1,
|
|
72
|
+
column: line.indexOf('catch') + 1,
|
|
73
|
+
message: 'Empty catch block detected (fallback analysis)',
|
|
74
|
+
severity: 'error',
|
|
75
|
+
ruleId: this.ruleId,
|
|
76
|
+
type: 'empty_catch_fallback'
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return violations;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
extractCatchBlock(lines, startIndex) {
|
|
86
|
+
const content = [];
|
|
87
|
+
let braceCount = 0;
|
|
88
|
+
let inBlock = false;
|
|
89
|
+
|
|
90
|
+
for (let i = startIndex; i < lines.length; i++) {
|
|
91
|
+
const line = lines[i];
|
|
92
|
+
content.push(line);
|
|
93
|
+
|
|
94
|
+
for (const char of line) {
|
|
95
|
+
if (char === '{') {
|
|
96
|
+
braceCount++;
|
|
97
|
+
inBlock = true;
|
|
98
|
+
} else if (char === '}') {
|
|
99
|
+
braceCount--;
|
|
100
|
+
if (braceCount === 0 && inBlock) {
|
|
101
|
+
return { content, endIndex: i };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return { content, endIndex: startIndex };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
isCatchBlockEmpty(content) {
|
|
111
|
+
const blockContent = content.join('\n');
|
|
112
|
+
|
|
113
|
+
// Remove comments and whitespace
|
|
114
|
+
const cleanContent = blockContent
|
|
115
|
+
.replace(/\/\*[\s\S]*?\*\//g, '') // Remove multi-line comments
|
|
116
|
+
.replace(/\/\/.*$/gm, '') // Remove single-line comments
|
|
117
|
+
.replace(/\s+/g, ' ') // Normalize whitespace
|
|
118
|
+
.trim();
|
|
119
|
+
|
|
120
|
+
// Check if only contains catch declaration and braces
|
|
121
|
+
const hasOnlyStructure = /^catch\s*\([^)]*\)\s*\{\s*\}$/.test(cleanContent);
|
|
122
|
+
|
|
123
|
+
return hasOnlyStructure;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
module.exports = C029ASTAnalyzer;
|