@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.
Files changed (77) hide show
  1. package/config/rule-analysis-strategies.js +18 -2
  2. package/engines/eslint-engine.js +9 -11
  3. package/engines/heuristic-engine.js +55 -31
  4. package/package.json +2 -1
  5. package/rules/README.md +252 -0
  6. package/rules/common/C002_no_duplicate_code/analyzer.js +65 -0
  7. package/rules/common/C002_no_duplicate_code/config.json +23 -0
  8. package/rules/common/C003_no_vague_abbreviations/analyzer.js +418 -0
  9. package/rules/common/C003_no_vague_abbreviations/config.json +35 -0
  10. package/rules/common/C006_function_naming/analyzer.js +504 -0
  11. package/rules/common/C006_function_naming/config.json +86 -0
  12. package/rules/common/C006_function_naming/smart-analyzer.js +503 -0
  13. package/rules/common/C010_limit_block_nesting/analyzer.js +389 -0
  14. package/rules/common/C012_command_query_separation/analyzer.js +481 -0
  15. package/rules/common/C012_command_query_separation/ast-analyzer.js +495 -0
  16. package/rules/common/C013_no_dead_code/analyzer.js +206 -0
  17. package/rules/common/C014_dependency_injection/analyzer.js +338 -0
  18. package/rules/common/C017_constructor_logic/analyzer.js +314 -0
  19. package/rules/common/C019_log_level_usage/analyzer.js +362 -0
  20. package/rules/common/C019_log_level_usage/config.json +121 -0
  21. package/rules/common/C029_catch_block_logging/analyzer-backup.js +426 -0
  22. package/rules/common/C029_catch_block_logging/analyzer-fixed.js +130 -0
  23. package/rules/common/C029_catch_block_logging/analyzer-multi-tech.js +487 -0
  24. package/rules/common/C029_catch_block_logging/analyzer-simple.js +110 -0
  25. package/rules/common/C029_catch_block_logging/analyzer-smart-pipeline.js +755 -0
  26. package/rules/common/C029_catch_block_logging/analyzer.js +129 -0
  27. package/rules/common/C029_catch_block_logging/ast-analyzer-backup.js +441 -0
  28. package/rules/common/C029_catch_block_logging/ast-analyzer-new.js +127 -0
  29. package/rules/common/C029_catch_block_logging/ast-analyzer.js +133 -0
  30. package/rules/common/C029_catch_block_logging/cfg-analyzer.js +408 -0
  31. package/rules/common/C029_catch_block_logging/config.json +59 -0
  32. package/rules/common/C029_catch_block_logging/dataflow-analyzer.js +454 -0
  33. package/rules/common/C029_catch_block_logging/multi-language-ast-engine.js +700 -0
  34. package/rules/common/C029_catch_block_logging/pattern-learning-analyzer.js +568 -0
  35. package/rules/common/C029_catch_block_logging/semantic-analyzer.js +459 -0
  36. package/rules/common/C031_validation_separation/analyzer.js +186 -0
  37. package/rules/common/C041_no_sensitive_hardcode/analyzer.js +292 -0
  38. package/rules/common/C041_no_sensitive_hardcode/ast-analyzer.js +296 -0
  39. package/rules/common/C042_boolean_name_prefix/analyzer.js +300 -0
  40. package/rules/common/C043_no_console_or_print/analyzer.js +431 -0
  41. package/rules/common/C047_no_duplicate_retry_logic/analyzer.js +590 -0
  42. package/rules/common/C075_explicit_return_types/analyzer.js +103 -0
  43. package/rules/common/C076_single_test_behavior/analyzer.js +121 -0
  44. package/rules/docs/C002_no_duplicate_code.md +57 -0
  45. package/rules/docs/C031_validation_separation.md +72 -0
  46. package/rules/index.js +155 -0
  47. package/rules/migration/converter.js +385 -0
  48. package/rules/migration/mapping.json +164 -0
  49. package/rules/parser/constants.js +31 -0
  50. package/rules/parser/file-config.js +80 -0
  51. package/rules/parser/rule-parser-simple.js +305 -0
  52. package/rules/parser/rule-parser.js +527 -0
  53. package/rules/security/S015_insecure_tls_certificate/analyzer.js +150 -0
  54. package/rules/security/S015_insecure_tls_certificate/ast-analyzer.js +237 -0
  55. package/rules/security/S023_no_json_injection/analyzer.js +278 -0
  56. package/rules/security/S023_no_json_injection/ast-analyzer.js +359 -0
  57. package/rules/security/S026_json_schema_validation/analyzer.js +251 -0
  58. package/rules/security/S026_json_schema_validation/config.json +27 -0
  59. package/rules/security/S027_no_hardcoded_secrets/analyzer.js +436 -0
  60. package/rules/security/S027_no_hardcoded_secrets/config.json +29 -0
  61. package/rules/security/S029_csrf_protection/analyzer.js +330 -0
  62. package/rules/tests/C002_no_duplicate_code.test.js +50 -0
  63. package/rules/universal/C010/generic.js +0 -0
  64. package/rules/universal/C010/tree-sitter-analyzer.js +0 -0
  65. package/rules/utils/ast-utils.js +191 -0
  66. package/rules/utils/base-analyzer.js +98 -0
  67. package/rules/utils/pattern-matchers.js +239 -0
  68. package/rules/utils/rule-helpers.js +264 -0
  69. package/rules/utils/severity-constants.js +93 -0
  70. package/scripts/generate_insights.js +188 -0
  71. package/scripts/merge-reports.js +0 -424
  72. package/scripts/test-scripts/README.md +0 -22
  73. package/scripts/test-scripts/test-c041-comparison.js +0 -114
  74. package/scripts/test-scripts/test-c041-eslint.js +0 -67
  75. package/scripts/test-scripts/test-eslint-rules.js +0 -146
  76. package/scripts/test-scripts/test-real-world.js +0 -44
  77. 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();