@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,755 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* C029 Smart Pipeline Analyzer
|
|
3
|
+
*
|
|
4
|
+
* Intelligent 3-stage analysis pipeline:
|
|
5
|
+
* 1. REGEX: Fast catch block detection
|
|
6
|
+
* 2. AST: Structure analysis and nesting evaluation
|
|
7
|
+
* 3. DATA FLOW: Exception usage validation
|
|
8
|
+
*
|
|
9
|
+
* Goal: >= ESLint accuracy with superior performance
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
|
|
14
|
+
class C029SmartPipelineAnalyzer {
|
|
15
|
+
constructor() {
|
|
16
|
+
this.ruleId = 'C029';
|
|
17
|
+
this.ruleName = 'Smart Catch Block Analysis';
|
|
18
|
+
this.description = 'Intelligent 3-stage pipeline for catch block validation';
|
|
19
|
+
|
|
20
|
+
// Performance tracking
|
|
21
|
+
this.stats = {
|
|
22
|
+
totalFiles: 0,
|
|
23
|
+
regexCandidates: 0,
|
|
24
|
+
astAnalyzed: 0,
|
|
25
|
+
dataFlowChecked: 0,
|
|
26
|
+
finalViolations: 0,
|
|
27
|
+
executionTime: 0
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async analyze(files, language, options = {}) {
|
|
32
|
+
if (options.verbose) {
|
|
33
|
+
console.log(`🎯 C029 Smart Pipeline loaded (Regex → AST → Data Flow)`);
|
|
34
|
+
console.log(`🎯 C029 Smart Pipeline: Analyzing ${files.length} files with 3-stage approach...`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const startTime = Date.now();
|
|
38
|
+
const violations = [];
|
|
39
|
+
this.stats.totalFiles = files.length;
|
|
40
|
+
|
|
41
|
+
for (const filePath of files) {
|
|
42
|
+
try {
|
|
43
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
44
|
+
const fileViolations = await this.analyzeFileWithPipeline(content, filePath, language);
|
|
45
|
+
violations.push(...fileViolations);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.warn(`C029 Smart Pipeline skipping ${filePath}: ${error.message}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
this.stats.finalViolations = violations.length;
|
|
52
|
+
this.stats.executionTime = Date.now() - startTime;
|
|
53
|
+
|
|
54
|
+
if (options.verbose) {
|
|
55
|
+
this.printAnalysisStats();
|
|
56
|
+
}
|
|
57
|
+
return violations;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 3-STAGE SMART PIPELINE
|
|
62
|
+
*/
|
|
63
|
+
async analyzeFileWithPipeline(content, filePath, language) {
|
|
64
|
+
const violations = [];
|
|
65
|
+
|
|
66
|
+
// STAGE 1: REGEX - Fast catch detection
|
|
67
|
+
const catchCandidates = this.stage1_RegexDetection(content, filePath);
|
|
68
|
+
this.stats.regexCandidates += catchCandidates.length;
|
|
69
|
+
|
|
70
|
+
if (catchCandidates.length === 0) {
|
|
71
|
+
return violations; // No catch blocks found, skip expensive analysis
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// STAGE 2: AST - Structure analysis for each candidate
|
|
75
|
+
for (const candidate of catchCandidates) {
|
|
76
|
+
const astResult = this.stage2_ASTAnalysis(candidate, content, filePath);
|
|
77
|
+
this.stats.astAnalyzed++;
|
|
78
|
+
|
|
79
|
+
if (astResult.needsDataFlow) {
|
|
80
|
+
// STAGE 3: DATA FLOW - Deep exception usage analysis
|
|
81
|
+
const dataFlowResult = this.stage3_DataFlowAnalysis(candidate, astResult, content, filePath);
|
|
82
|
+
this.stats.dataFlowChecked++;
|
|
83
|
+
|
|
84
|
+
if (dataFlowResult.isViolation) {
|
|
85
|
+
violations.push(dataFlowResult.violation);
|
|
86
|
+
}
|
|
87
|
+
} else if (astResult.isViolation) {
|
|
88
|
+
// AST already determined it's a violation
|
|
89
|
+
violations.push(astResult.violation);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return violations;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* STAGE 1: REGEX DETECTION
|
|
98
|
+
* Fast identification of catch blocks
|
|
99
|
+
*/
|
|
100
|
+
stage1_RegexDetection(content, filePath) {
|
|
101
|
+
const candidates = [];
|
|
102
|
+
const lines = content.split('\n');
|
|
103
|
+
|
|
104
|
+
for (let i = 0; i < lines.length; i++) {
|
|
105
|
+
const line = lines[i];
|
|
106
|
+
|
|
107
|
+
// Detect catch blocks with different patterns
|
|
108
|
+
const catchMatches = [
|
|
109
|
+
/catch\s*\(\s*(\w+)\s*\)\s*\{/.exec(line), // catch(e) {
|
|
110
|
+
/catch\s*\(\s*\{\s*(\w+)\s*\}\s*\)\s*\{/.exec(line), // catch({error}) {
|
|
111
|
+
/\.catch\s*\(\s*(\w+)\s*=>\s*\{/.exec(line), // .catch(e => {
|
|
112
|
+
/\.catch\s*\(\s*function\s*\(\s*(\w+)\s*\)/.exec(line) // .catch(function(e)
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
for (const match of catchMatches) {
|
|
116
|
+
if (match) {
|
|
117
|
+
const catchBlock = this.extractCatchBlock(lines, i, match[0]);
|
|
118
|
+
if (catchBlock) {
|
|
119
|
+
candidates.push({
|
|
120
|
+
type: 'try-catch',
|
|
121
|
+
startLine: i + 1,
|
|
122
|
+
endLine: catchBlock.endLine,
|
|
123
|
+
errorVariable: match[1] || 'e',
|
|
124
|
+
content: catchBlock.content,
|
|
125
|
+
rawMatch: match[0],
|
|
126
|
+
context: this.getContext(lines, i)
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return candidates;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* STAGE 2: AST ANALYSIS
|
|
139
|
+
* Structure evaluation and nesting analysis
|
|
140
|
+
*/
|
|
141
|
+
stage2_ASTAnalysis(candidate, content, filePath) {
|
|
142
|
+
const astAnalysis = {
|
|
143
|
+
needsDataFlow: false,
|
|
144
|
+
isViolation: false,
|
|
145
|
+
violation: null,
|
|
146
|
+
structureInfo: {}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// 2.1: Basic emptiness check
|
|
150
|
+
if (this.isEmptyCatchBlock(candidate.content)) {
|
|
151
|
+
astAnalysis.isViolation = true;
|
|
152
|
+
astAnalysis.violation = this.createViolation(
|
|
153
|
+
candidate, filePath, 'empty_catch',
|
|
154
|
+
'Empty catch block',
|
|
155
|
+
'Add error handling or explicit documentation',
|
|
156
|
+
0.9
|
|
157
|
+
);
|
|
158
|
+
return astAnalysis;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// 2.2: Simple logging check (fast path)
|
|
162
|
+
if (this.hasObviousLogging(candidate.content)) {
|
|
163
|
+
// Has obvious logging, no violation
|
|
164
|
+
return astAnalysis;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// 2.3: Nesting and structure analysis
|
|
168
|
+
const structureInfo = this.analyzeStructure(candidate.content);
|
|
169
|
+
astAnalysis.structureInfo = structureInfo;
|
|
170
|
+
|
|
171
|
+
// 2.4: Context-aware decisions
|
|
172
|
+
if (this.isTestFile(filePath) && structureInfo.hasTestAssertions) {
|
|
173
|
+
// Test file with assertions, likely ok
|
|
174
|
+
return astAnalysis;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (structureInfo.hasControlFlow && !structureInfo.hasLogging) {
|
|
178
|
+
// Has control flow but no logging, might be intentional
|
|
179
|
+
astAnalysis.needsDataFlow = true;
|
|
180
|
+
return astAnalysis;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (structureInfo.complexity > 3 && !structureInfo.hasLogging) {
|
|
184
|
+
// Complex catch block without logging, likely violation
|
|
185
|
+
astAnalysis.isViolation = true;
|
|
186
|
+
astAnalysis.violation = this.createViolation(
|
|
187
|
+
candidate, filePath, 'complex_no_logging',
|
|
188
|
+
'Complex catch block without error logging',
|
|
189
|
+
'Add error logging for debugging and monitoring',
|
|
190
|
+
0.8
|
|
191
|
+
);
|
|
192
|
+
return astAnalysis;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Need deeper analysis
|
|
196
|
+
astAnalysis.needsDataFlow = true;
|
|
197
|
+
return astAnalysis;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* STAGE 3: DATA FLOW ANALYSIS
|
|
202
|
+
* Exception usage validation
|
|
203
|
+
*/
|
|
204
|
+
stage3_DataFlowAnalysis(candidate, astResult, content, filePath) {
|
|
205
|
+
const dataFlowResult = {
|
|
206
|
+
isViolation: false,
|
|
207
|
+
violation: null,
|
|
208
|
+
usageInfo: {}
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// 3.1: Track exception variable usage
|
|
212
|
+
const usageInfo = this.analyzeExceptionUsage(candidate, content);
|
|
213
|
+
dataFlowResult.usageInfo = usageInfo;
|
|
214
|
+
|
|
215
|
+
// 3.2: Decision logic based on usage patterns
|
|
216
|
+
if (usageInfo.isUnused && !usageInfo.hasExplicitIgnore) {
|
|
217
|
+
dataFlowResult.isViolation = true;
|
|
218
|
+
dataFlowResult.violation = this.createViolation(
|
|
219
|
+
candidate, filePath, 'unused_exception',
|
|
220
|
+
`Exception variable '${candidate.errorVariable}' is unused`,
|
|
221
|
+
`Use the exception for logging or add explicit ignore comment`,
|
|
222
|
+
0.85
|
|
223
|
+
);
|
|
224
|
+
return dataFlowResult;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (usageInfo.isUsedButNotLogged && !this.isTestFile(filePath)) {
|
|
228
|
+
dataFlowResult.isViolation = true;
|
|
229
|
+
dataFlowResult.violation = this.createViolation(
|
|
230
|
+
candidate, filePath, 'used_not_logged',
|
|
231
|
+
`Exception is used but not logged for debugging`,
|
|
232
|
+
`Add console.error() or logging framework call`,
|
|
233
|
+
0.7
|
|
234
|
+
);
|
|
235
|
+
return dataFlowResult;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (usageInfo.isSilentlyReturned && astResult.structureInfo.complexity > 1) {
|
|
239
|
+
dataFlowResult.isViolation = true;
|
|
240
|
+
dataFlowResult.violation = this.createViolation(
|
|
241
|
+
candidate, filePath, 'silent_return',
|
|
242
|
+
`Exception silently handled without logging`,
|
|
243
|
+
`Add error logging before returning fallback value`,
|
|
244
|
+
0.75
|
|
245
|
+
);
|
|
246
|
+
return dataFlowResult;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return dataFlowResult;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* HELPER METHODS
|
|
254
|
+
*/
|
|
255
|
+
|
|
256
|
+
extractCatchBlock(lines, startIndex, matchString) {
|
|
257
|
+
let braceCount = 0;
|
|
258
|
+
let content = '';
|
|
259
|
+
let inBlock = false;
|
|
260
|
+
let catchBraceCount = 0; // Track braces specifically for the catch block
|
|
261
|
+
|
|
262
|
+
for (let i = startIndex; i < lines.length; i++) {
|
|
263
|
+
const line = lines[i];
|
|
264
|
+
|
|
265
|
+
if (line.includes(matchString)) {
|
|
266
|
+
inBlock = true;
|
|
267
|
+
content += line + '\n';
|
|
268
|
+
|
|
269
|
+
// Count the opening brace of the catch block
|
|
270
|
+
for (const char of line) {
|
|
271
|
+
if (char === '{') {
|
|
272
|
+
catchBraceCount++;
|
|
273
|
+
break; // Only count the first brace on the catch line
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (inBlock) {
|
|
280
|
+
content += line + '\n';
|
|
281
|
+
|
|
282
|
+
// Count braces to find the end of the catch block
|
|
283
|
+
for (const char of line) {
|
|
284
|
+
if (char === '{') {
|
|
285
|
+
catchBraceCount++;
|
|
286
|
+
} else if (char === '}') {
|
|
287
|
+
catchBraceCount--;
|
|
288
|
+
|
|
289
|
+
// If we've closed the catch block
|
|
290
|
+
if (catchBraceCount === 0) {
|
|
291
|
+
return {
|
|
292
|
+
endLine: i + 1,
|
|
293
|
+
content: content.trim()
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// If we reach here, return what we have
|
|
302
|
+
return content ? {
|
|
303
|
+
endLine: lines.length,
|
|
304
|
+
content: content.trim()
|
|
305
|
+
} : null;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
isEmptyCatchBlock(content) {
|
|
309
|
+
// Don't just remove all comments - check if there's actual code first
|
|
310
|
+
if (this.hasObviousLogging(content)) {
|
|
311
|
+
return false; // Has logging, definitely not empty
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const cleaned = content
|
|
315
|
+
.replace(/\/\*[\s\S]*?\*\//g, '') // Remove block comments
|
|
316
|
+
.replace(/\/\/.*$/gm, '') // Remove line comments
|
|
317
|
+
.replace(/catch\s*\([^)]*\)\s*\{/, '') // Remove catch declaration
|
|
318
|
+
.replace(/\}/g, '') // Remove closing brace
|
|
319
|
+
.replace(/\s+/g, '') // Remove whitespace
|
|
320
|
+
.trim();
|
|
321
|
+
|
|
322
|
+
return cleaned.length === 0;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
hasObviousLogging(content) {
|
|
326
|
+
const loggingPatterns = [
|
|
327
|
+
/console\.(log|error|warn|info|debug)/,
|
|
328
|
+
/logger?\.(error|warn|info|debug)/,
|
|
329
|
+
/log\.(error|warn|info|debug)/,
|
|
330
|
+
/this\.logger?\.(error|warn|info|debug)/, // Added: this.logger.method()
|
|
331
|
+
/\w+\.logger?\.(error|warn|info|debug)/, // Added: obj.logger.method()
|
|
332
|
+
/Logger\.(error|warn|info|debug|log)/, // Added: Logger.method()
|
|
333
|
+
/this\.logErrors?\s*\(/, // Added: this.logError() and this.logErrors()
|
|
334
|
+
/this\.couponLogErrors\s*\(/, // Added: this.couponLogErrors()
|
|
335
|
+
/print\s*\(/,
|
|
336
|
+
/throw\s+/,
|
|
337
|
+
/rethrow/
|
|
338
|
+
];
|
|
339
|
+
|
|
340
|
+
return loggingPatterns.some(pattern => pattern.test(content));
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
analyzeStructure(content) {
|
|
344
|
+
const structure = {
|
|
345
|
+
complexity: 0,
|
|
346
|
+
hasLogging: false,
|
|
347
|
+
hasControlFlow: false,
|
|
348
|
+
hasTestAssertions: false,
|
|
349
|
+
nestingLevel: 0
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
// Count complexity indicators
|
|
353
|
+
structure.complexity += (content.match(/\bif\b/g) || []).length;
|
|
354
|
+
structure.complexity += (content.match(/\bfor\b/g) || []).length;
|
|
355
|
+
structure.complexity += (content.match(/\bwhile\b/g) || []).length;
|
|
356
|
+
structure.complexity += (content.match(/\btry\b/g) || []).length;
|
|
357
|
+
|
|
358
|
+
// Check for control flow
|
|
359
|
+
structure.hasControlFlow = /\b(return|throw|break|continue)\b/.test(content);
|
|
360
|
+
|
|
361
|
+
// Check for test assertions
|
|
362
|
+
structure.hasTestAssertions = /\b(expect|assert|should|toBe|toEqual)\b/.test(content);
|
|
363
|
+
|
|
364
|
+
// Calculate nesting level
|
|
365
|
+
const lines = content.split('\n');
|
|
366
|
+
let maxNesting = 0;
|
|
367
|
+
let currentNesting = 0;
|
|
368
|
+
|
|
369
|
+
for (const line of lines) {
|
|
370
|
+
currentNesting += (line.match(/\{/g) || []).length;
|
|
371
|
+
currentNesting -= (line.match(/\}/g) || []).length;
|
|
372
|
+
maxNesting = Math.max(maxNesting, currentNesting);
|
|
373
|
+
}
|
|
374
|
+
structure.nestingLevel = maxNesting;
|
|
375
|
+
|
|
376
|
+
// Re-check logging (more thorough)
|
|
377
|
+
structure.hasLogging = this.hasObviousLogging(content);
|
|
378
|
+
|
|
379
|
+
return structure;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
analyzeExceptionUsage(candidate, content) {
|
|
383
|
+
const errorVar = candidate.errorVariable;
|
|
384
|
+
const catchContent = candidate.content;
|
|
385
|
+
|
|
386
|
+
const usage = {
|
|
387
|
+
isUnused: false,
|
|
388
|
+
isUsedButNotLogged: false,
|
|
389
|
+
isSilentlyReturned: false,
|
|
390
|
+
hasExplicitIgnore: false,
|
|
391
|
+
usageCount: 0,
|
|
392
|
+
usageTypes: [],
|
|
393
|
+
dataFlowAnalysis: null
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
// Count usages of error variable
|
|
397
|
+
const usageRegex = new RegExp(`\\b${errorVar}\\b`, 'g');
|
|
398
|
+
const matches = catchContent.match(usageRegex) || [];
|
|
399
|
+
|
|
400
|
+
// More robust counting - exclude the catch declaration itself
|
|
401
|
+
const catchDeclarationRegex = new RegExp(`catch\\s*\\(\\s*${errorVar}\\s*\\)`, 'g');
|
|
402
|
+
const declarationMatches = catchContent.match(catchDeclarationRegex) || [];
|
|
403
|
+
|
|
404
|
+
usage.usageCount = matches.length - declarationMatches.length;
|
|
405
|
+
|
|
406
|
+
// Check for explicit ignore patterns
|
|
407
|
+
usage.hasExplicitIgnore = /\/\/\s*(ignore|TODO|FIXME|eslint-disable)/.test(catchContent);
|
|
408
|
+
|
|
409
|
+
if (usage.usageCount === 0) {
|
|
410
|
+
usage.isUnused = true;
|
|
411
|
+
return usage;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// ENHANCED DATA FLOW ANALYSIS - Track where exception flows
|
|
415
|
+
usage.dataFlowAnalysis = this.traceExceptionDataFlow(errorVar, catchContent, content);
|
|
416
|
+
|
|
417
|
+
// Direct logging patterns (immediate logging)
|
|
418
|
+
if (new RegExp(`console\\.\\w+\\s*\\([^)]*\\b${errorVar}\\b`).test(catchContent)) {
|
|
419
|
+
usage.usageTypes.push('direct_logging');
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Logger framework patterns (immediate logging)
|
|
423
|
+
if (new RegExp(`logger?\\.\\w+\\s*\\([^)]*\\b${errorVar}\\b`).test(catchContent)) {
|
|
424
|
+
usage.usageTypes.push('direct_logging');
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Extract all function calls that use the error variable
|
|
428
|
+
const functionCalls = this.extractFunctionCallsWithError(errorVar, catchContent);
|
|
429
|
+
|
|
430
|
+
for (const call of functionCalls) {
|
|
431
|
+
// DATA FLOW: Check if this function eventually leads to logging
|
|
432
|
+
const hasEventualLogging = this.doesFunctionEventuallyLog(call.functionName, call.fullCall, content);
|
|
433
|
+
|
|
434
|
+
if (hasEventualLogging) {
|
|
435
|
+
usage.usageTypes.push('delegated_logging');
|
|
436
|
+
} else {
|
|
437
|
+
usage.usageTypes.push('function_call_no_logging');
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (new RegExp(`throw\\s+\\b${errorVar}\\b`).test(catchContent)) {
|
|
442
|
+
usage.usageTypes.push('rethrowing');
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (new RegExp(`return\\s+[^;]*\\b${errorVar}\\b`).test(catchContent)) {
|
|
446
|
+
usage.usageTypes.push('returning');
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// ENHANCED LOGIC: Based on data flow analysis
|
|
450
|
+
const hasActualLogging = usage.usageTypes.some(type =>
|
|
451
|
+
['direct_logging', 'delegated_logging'].includes(type)
|
|
452
|
+
);
|
|
453
|
+
|
|
454
|
+
usage.isUsedButNotLogged = usage.usageCount > 0 &&
|
|
455
|
+
!hasActualLogging &&
|
|
456
|
+
!usage.usageTypes.includes('rethrowing');
|
|
457
|
+
|
|
458
|
+
usage.isSilentlyReturned = /return\s+(null|undefined|false|\[\]|\{\}|'')/.test(catchContent) &&
|
|
459
|
+
!hasActualLogging;
|
|
460
|
+
|
|
461
|
+
return usage;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* ENHANCED DATA FLOW ANALYSIS
|
|
466
|
+
* Trace where exception flows to determine if it eventually gets logged
|
|
467
|
+
*/
|
|
468
|
+
traceExceptionDataFlow(errorVar, catchContent, fullFileContent) {
|
|
469
|
+
const flow = {
|
|
470
|
+
directLogging: false,
|
|
471
|
+
functionCalls: [],
|
|
472
|
+
eventualLogging: false,
|
|
473
|
+
traceDepth: 0
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
// Check direct logging in catch block
|
|
477
|
+
flow.directLogging = this.hasDirectLogging(errorVar, catchContent);
|
|
478
|
+
|
|
479
|
+
// Extract function calls and trace them
|
|
480
|
+
flow.functionCalls = this.extractFunctionCallsWithError(errorVar, catchContent);
|
|
481
|
+
|
|
482
|
+
// For each function call, try to trace if it leads to logging
|
|
483
|
+
for (const call of flow.functionCalls) {
|
|
484
|
+
const hasLogging = this.doesFunctionEventuallyLog(call.functionName, call.fullCall, fullFileContent);
|
|
485
|
+
if (hasLogging) {
|
|
486
|
+
flow.eventualLogging = true;
|
|
487
|
+
break;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
return flow;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Check if exception is directly logged in catch block
|
|
496
|
+
*/
|
|
497
|
+
hasDirectLogging(errorVar, catchContent) {
|
|
498
|
+
const directLoggingPatterns = [
|
|
499
|
+
new RegExp(`console\\.(log|error|warn|info|debug)\\s*\\([^)]*\\b${errorVar}\\b`, 'i'),
|
|
500
|
+
new RegExp(`logger?\\.\\w+\\s*\\([^)]*\\b${errorVar}\\b`, 'i'),
|
|
501
|
+
new RegExp(`log\\.\\w+\\s*\\([^)]*\\b${errorVar}\\b`, 'i'),
|
|
502
|
+
new RegExp(`\\.(error|warn|info|debug|log)\\s*\\([^)]*\\b${errorVar}\\b`, 'i')
|
|
503
|
+
];
|
|
504
|
+
|
|
505
|
+
return directLoggingPatterns.some(pattern => pattern.test(catchContent));
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Extract all function calls that include the error variable
|
|
510
|
+
*/
|
|
511
|
+
extractFunctionCallsWithError(errorVar, catchContent) {
|
|
512
|
+
const calls = [];
|
|
513
|
+
|
|
514
|
+
// Match various function call patterns
|
|
515
|
+
const patterns = [
|
|
516
|
+
new RegExp(`(\\w+)\\s*\\([^)]*\\b${errorVar}\\b[^)]*\\)`, 'g'), // func(error)
|
|
517
|
+
new RegExp(`(\\w+\\.\\w+)\\s*\\([^)]*\\b${errorVar}\\b[^)]*\\)`, 'g'), // this.method(error)
|
|
518
|
+
new RegExp(`(\\w+\\.\\w+\\.\\w+)\\s*\\([^)]*\\b${errorVar}\\b[^)]*\\)`, 'g') // obj.service.method(error)
|
|
519
|
+
];
|
|
520
|
+
|
|
521
|
+
for (const pattern of patterns) {
|
|
522
|
+
let match;
|
|
523
|
+
while ((match = pattern.exec(catchContent)) !== null) {
|
|
524
|
+
calls.push({
|
|
525
|
+
functionName: match[1],
|
|
526
|
+
fullCall: match[0],
|
|
527
|
+
position: match.index
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
return calls;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* CORE DATA FLOW: Check if a function eventually leads to logging
|
|
537
|
+
* Enhanced with limited multi-level tracing
|
|
538
|
+
*/
|
|
539
|
+
doesFunctionEventuallyLog(functionName, functionCall, fullFileContent, depth = 0) {
|
|
540
|
+
try {
|
|
541
|
+
// Prevent infinite recursion and excessive tracing
|
|
542
|
+
if (depth > 2) {
|
|
543
|
+
return false;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// 1. Try to find the function definition in the current file
|
|
547
|
+
const functionDef = this.findFunctionDefinition(functionName, fullFileContent);
|
|
548
|
+
|
|
549
|
+
if (functionDef) {
|
|
550
|
+
// 2. Check if the function body contains logging
|
|
551
|
+
const hasDirectLogging = this.hasFunctionLogging(functionDef.body);
|
|
552
|
+
if (hasDirectLogging) {
|
|
553
|
+
return true;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// 3. MULTI-LEVEL TRACING: Check if this function calls other functions
|
|
557
|
+
if (depth < 2) {
|
|
558
|
+
const nestedCalls = this.extractFunctionCallsFromBody(functionDef.body);
|
|
559
|
+
for (const nestedCall of nestedCalls) {
|
|
560
|
+
const hasNestedLogging = this.doesFunctionEventuallyLog(
|
|
561
|
+
nestedCall.functionName,
|
|
562
|
+
nestedCall.fullCall,
|
|
563
|
+
fullFileContent,
|
|
564
|
+
depth + 1
|
|
565
|
+
);
|
|
566
|
+
if (hasNestedLogging) {
|
|
567
|
+
return true;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
return false;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// 4. If not found locally, check for common logging patterns
|
|
576
|
+
if (this.isKnownLoggingPattern(functionCall)) {
|
|
577
|
+
return true;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// 5. Default: assume no logging if we can't trace
|
|
581
|
+
return false;
|
|
582
|
+
|
|
583
|
+
} catch (error) {
|
|
584
|
+
// If analysis fails, be conservative
|
|
585
|
+
return false;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* Extract function calls from function body for multi-level tracing
|
|
591
|
+
*/
|
|
592
|
+
extractFunctionCallsFromBody(functionBody) {
|
|
593
|
+
const calls = [];
|
|
594
|
+
|
|
595
|
+
// Match function calls in the body
|
|
596
|
+
const patterns = [
|
|
597
|
+
/(\w+)\s*\([^)]*\)/g, // functionName()
|
|
598
|
+
/(\w+\.\w+)\s*\([^)]*\)/g, // this.method()
|
|
599
|
+
/(\w+\.\w+\.\w+)\s*\([^)]*\)/g, // obj.service.method()
|
|
600
|
+
/return\s+(\w+)\s*\([^)]*\)/g, // return functionName()
|
|
601
|
+
/return\s+(\w+\.\w+)\s*\([^)]*\)/g // return this.method()
|
|
602
|
+
];
|
|
603
|
+
|
|
604
|
+
for (const pattern of patterns) {
|
|
605
|
+
let match;
|
|
606
|
+
while ((match = pattern.exec(functionBody)) !== null) {
|
|
607
|
+
calls.push({
|
|
608
|
+
functionName: match[1],
|
|
609
|
+
fullCall: match[0],
|
|
610
|
+
position: match.index
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
return calls;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* Find function definition in file content
|
|
620
|
+
*/
|
|
621
|
+
findFunctionDefinition(functionName, content) {
|
|
622
|
+
// Handle method calls like this.methodName
|
|
623
|
+
const methodName = functionName.includes('.') ?
|
|
624
|
+
functionName.split('.').pop() : functionName;
|
|
625
|
+
|
|
626
|
+
// Patterns to match function definitions
|
|
627
|
+
const patterns = [
|
|
628
|
+
new RegExp(`${methodName}\\s*\\([^)]*\\)\\s*\\{([^{}]*(?:\\{[^{}]*\\}[^{}]*)*)\\}`, 's'), // method() { ... }
|
|
629
|
+
new RegExp(`function\\s+${methodName}\\s*\\([^)]*\\)\\s*\\{([^{}]*(?:\\{[^{}]*\\}[^{}]*)*)\\}`, 's'), // function name() { ... }
|
|
630
|
+
new RegExp(`${methodName}\\s*:\\s*function\\s*\\([^)]*\\)\\s*\\{([^{}]*(?:\\{[^{}]*\\}[^{}]*)*)\\}`, 's'), // name: function() { ... }
|
|
631
|
+
new RegExp(`${methodName}\\s*=\\s*\\([^)]*\\)\\s*=>\\s*\\{([^{}]*(?:\\{[^{}]*\\}[^{}]*)*)\\}`, 's') // name = () => { ... }
|
|
632
|
+
];
|
|
633
|
+
|
|
634
|
+
for (const pattern of patterns) {
|
|
635
|
+
const match = pattern.exec(content);
|
|
636
|
+
if (match) {
|
|
637
|
+
return {
|
|
638
|
+
name: methodName,
|
|
639
|
+
body: match[1],
|
|
640
|
+
fullMatch: match[0]
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
return null;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* Check if function body contains logging OR valid error handling
|
|
650
|
+
*/
|
|
651
|
+
hasFunctionLogging(functionBody) {
|
|
652
|
+
const loggingPatterns = [
|
|
653
|
+
/console\.(log|error|warn|info|debug)/i,
|
|
654
|
+
/logger?\.(error|warn|info|debug|log)/i,
|
|
655
|
+
/log\.(error|warn|info|debug)/i,
|
|
656
|
+
/\.error\s*\(/i,
|
|
657
|
+
/\.warn\s*\(/i,
|
|
658
|
+
/\.info\s*\(/i,
|
|
659
|
+
/\.debug\s*\(/i,
|
|
660
|
+
/print\s*\(/i
|
|
661
|
+
];
|
|
662
|
+
|
|
663
|
+
// ENHANCED: Also consider error propagation as valid handling
|
|
664
|
+
const errorHandlingPatterns = [
|
|
665
|
+
/throw\s+/i, // throw error/new Error
|
|
666
|
+
/rethrow/i, // explicit rethrow
|
|
667
|
+
/return.*Error/i, // return error object
|
|
668
|
+
/\.handle\s*\(/i, // error.handle()
|
|
669
|
+
/ErrorHandler\./i, // ErrorHandler.method()
|
|
670
|
+
/externalErrorHandler\s*\(/i, // specific handler functions
|
|
671
|
+
/errorHandler\s*\(/i, // generic error handlers
|
|
672
|
+
/handleError\s*\(/i // handle error functions
|
|
673
|
+
];
|
|
674
|
+
|
|
675
|
+
const hasLogging = loggingPatterns.some(pattern => pattern.test(functionBody));
|
|
676
|
+
const hasErrorHandling = errorHandlingPatterns.some(pattern => pattern.test(functionBody));
|
|
677
|
+
|
|
678
|
+
return hasLogging || hasErrorHandling;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* Check if function call matches known logging/error handling patterns
|
|
683
|
+
*/
|
|
684
|
+
isKnownLoggingPattern(functionCall) {
|
|
685
|
+
const knownPatterns = [
|
|
686
|
+
// Direct logging
|
|
687
|
+
/\.logger?\./i, // this.logger.anything, obj.log.anything
|
|
688
|
+
/console\./i, // console.anything
|
|
689
|
+
/Logger\./i, // Logger.anything (static)
|
|
690
|
+
/\.log\./i, // obj.log.anything
|
|
691
|
+
/\.error\s*\(/i, // anything.error()
|
|
692
|
+
/\.warn\s*\(/i, // anything.warn()
|
|
693
|
+
/\.info\s*\(/i, // anything.info()
|
|
694
|
+
/\.debug\s*\(/i, // anything.debug()
|
|
695
|
+
|
|
696
|
+
// ENHANCED: Error handling patterns
|
|
697
|
+
/externalErrorHandler\s*\(/i, // externalErrorHandler()
|
|
698
|
+
/errorHandler\s*\(/i, // anyErrorHandler()
|
|
699
|
+
/handleError\s*\(/i, // handleError()
|
|
700
|
+
/ErrorHandler\./i, // ErrorHandler.method()
|
|
701
|
+
/\.handle\s*\(/i, // obj.handle()
|
|
702
|
+
/processError\s*\(/i, // processError() - common pattern
|
|
703
|
+
/logError\s*\(/i, // logError() - common pattern
|
|
704
|
+
/logErrors\s*\(/i, // logErrors() - common pattern for base classes
|
|
705
|
+
/reportError\s*\(/i, // reportError()
|
|
706
|
+
/sendError\s*\(/i, // sendError()
|
|
707
|
+
/trackError\s*\(/i, // trackError()
|
|
708
|
+
/couponLogErrors\s*\(/i // couponLogErrors() - specific pattern
|
|
709
|
+
];
|
|
710
|
+
|
|
711
|
+
return knownPatterns.some(pattern => pattern.test(functionCall));
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
getContext(lines, lineIndex) {
|
|
715
|
+
const before = lines.slice(Math.max(0, lineIndex - 3), lineIndex).join('\n');
|
|
716
|
+
const after = lines.slice(lineIndex + 1, Math.min(lines.length, lineIndex + 4)).join('\n');
|
|
717
|
+
|
|
718
|
+
return { before, after };
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
isTestFile(filePath) {
|
|
722
|
+
const testPatterns = ['__tests__', '.test.', '.spec.', '/test/', '/tests/', '.stories.'];
|
|
723
|
+
return testPatterns.some(pattern => filePath.includes(pattern));
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
createViolation(candidate, filePath, type, message, suggestion, confidence) {
|
|
727
|
+
return {
|
|
728
|
+
ruleId: this.ruleId,
|
|
729
|
+
file: filePath,
|
|
730
|
+
line: candidate.startLine,
|
|
731
|
+
column: 1,
|
|
732
|
+
message: message,
|
|
733
|
+
severity: 'error',
|
|
734
|
+
code: candidate.content.split('\n')[0].trim(),
|
|
735
|
+
type: type,
|
|
736
|
+
confidence: confidence,
|
|
737
|
+
suggestion: suggestion,
|
|
738
|
+
errorVariable: candidate.errorVariable,
|
|
739
|
+
pipeline: 'smart_3_stage'
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
printAnalysisStats() {
|
|
744
|
+
console.log(`📊 C029 Smart Pipeline Stats:`);
|
|
745
|
+
console.log(` 📁 Files analyzed: ${this.stats.totalFiles}`);
|
|
746
|
+
console.log(` 🔍 Regex candidates: ${this.stats.regexCandidates}`);
|
|
747
|
+
console.log(` 🌳 AST analyzed: ${this.stats.astAnalyzed}`);
|
|
748
|
+
console.log(` 🧠 Data flow checked: ${this.stats.dataFlowChecked}`);
|
|
749
|
+
console.log(` ❌ Final violations: ${this.stats.finalViolations}`);
|
|
750
|
+
console.log(` ⚡ Execution time: ${this.stats.executionTime}ms`);
|
|
751
|
+
console.log(` 🎯 Efficiency: ${((this.stats.regexCandidates - this.stats.dataFlowChecked) / this.stats.regexCandidates * 100).toFixed(1)}% early exits`);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
module.exports = new C029SmartPipelineAnalyzer();
|