@sun-asterisk/sunlint 1.3.16 → 1.3.17

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 (50) hide show
  1. package/config/rule-analysis-strategies.js +3 -3
  2. package/config/rules/enhanced-rules-registry.json +40 -20
  3. package/core/cli-action-handler.js +2 -2
  4. package/core/config-merger.js +28 -6
  5. package/core/constants/defaults.js +1 -1
  6. package/core/file-targeting-service.js +72 -4
  7. package/core/output-service.js +21 -4
  8. package/engines/heuristic-engine.js +5 -0
  9. package/package.json +1 -1
  10. package/rules/common/C002_no_duplicate_code/README.md +115 -0
  11. package/rules/common/C002_no_duplicate_code/analyzer.js +615 -219
  12. package/rules/common/C002_no_duplicate_code/test-cases/api-handlers.ts +64 -0
  13. package/rules/common/C002_no_duplicate_code/test-cases/data-processor.ts +46 -0
  14. package/rules/common/C002_no_duplicate_code/test-cases/good-example.tsx +40 -0
  15. package/rules/common/C002_no_duplicate_code/test-cases/product-service.ts +57 -0
  16. package/rules/common/C002_no_duplicate_code/test-cases/user-service.ts +49 -0
  17. package/rules/common/C008/analyzer.js +40 -0
  18. package/rules/common/C008/config.json +20 -0
  19. package/rules/common/C008/ts-morph-analyzer.js +1067 -0
  20. package/rules/common/C018_no_throw_generic_error/analyzer.js +1 -1
  21. package/rules/common/C018_no_throw_generic_error/symbol-based-analyzer.js +27 -3
  22. package/rules/common/C024_no_scatter_hardcoded_constants/symbol-based-analyzer.js +504 -162
  23. package/rules/common/C029_catch_block_logging/analyzer.js +499 -89
  24. package/rules/common/C033_separate_service_repository/README.md +131 -20
  25. package/rules/common/C033_separate_service_repository/analyzer.js +1 -1
  26. package/rules/common/C033_separate_service_repository/symbol-based-analyzer.js +417 -274
  27. package/rules/common/C041_no_sensitive_hardcode/analyzer.js +144 -254
  28. package/rules/common/C041_no_sensitive_hardcode/config.json +50 -0
  29. package/rules/common/C041_no_sensitive_hardcode/symbol-based-analyzer.js +575 -0
  30. package/rules/common/C067_no_hardcoded_config/analyzer.js +17 -16
  31. package/rules/common/C067_no_hardcoded_config/symbol-based-analyzer.js +3477 -659
  32. package/rules/docs/C002_no_duplicate_code.md +276 -11
  33. package/rules/index.js +5 -1
  34. package/rules/security/S006_no_plaintext_recovery_codes/analyzer.js +266 -88
  35. package/rules/security/S006_no_plaintext_recovery_codes/symbol-based-analyzer.js +805 -0
  36. package/rules/security/S010_no_insecure_encryption/README.md +78 -0
  37. package/rules/security/S010_no_insecure_encryption/analyzer.js +463 -398
  38. package/rules/security/S013_tls_enforcement/README.md +51 -0
  39. package/rules/security/S013_tls_enforcement/analyzer.js +99 -0
  40. package/rules/security/S013_tls_enforcement/config.json +41 -0
  41. package/rules/security/S013_tls_enforcement/symbol-based-analyzer.js +339 -0
  42. package/rules/security/S014_tls_version_enforcement/README.md +354 -0
  43. package/rules/security/S014_tls_version_enforcement/analyzer.js +118 -0
  44. package/rules/security/S014_tls_version_enforcement/config.json +56 -0
  45. package/rules/security/S014_tls_version_enforcement/symbol-based-analyzer.js +194 -0
  46. package/rules/security/S055_content_type_validation/analyzer.js +121 -279
  47. package/rules/security/S055_content_type_validation/symbol-based-analyzer.js +346 -0
  48. package/rules/tests/C002_no_duplicate_code.test.js +111 -22
  49. package/rules/common/C029_catch_block_logging/analyzer-smart-pipeline.js +0 -755
  50. package/rules/common/C041_no_sensitive_hardcode/ast-analyzer.js +0 -296
@@ -1,140 +1,550 @@
1
1
  /**
2
- * C029 Analyzer - Smart Pipeline Integration
2
+ * C029 Analyzer - ts-morph based
3
3
  *
4
- * This analyzer forwards to the Smart Pipeline for superior accuracy and performance
4
+ * Validates catch block logging with AST-based analysis:
5
+ * 1. Detects empty catch blocks
6
+ * 2. Validates logging presence and quality
7
+ * 3. Checks log levels (error vs warn vs log)
8
+ * 4. Validates error variable usage
9
+ * 5. Checks stack trace inclusion
10
+ * 6. Validates context data
11
+ * 7. Supports delegation pattern (calling helper functions)
5
12
  */
6
13
 
7
- const fs = require('fs');
14
+ const { Project, SyntaxKind, Node } = require('ts-morph');
8
15
  const path = require('path');
16
+ const fs = require('fs');
9
17
 
10
18
  class C029Analyzer {
11
- constructor(options = {}) {
19
+ constructor(semanticEngine = null) {
12
20
  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
- this.verbose = options.verbose || false;
16
-
17
- // Load Smart Pipeline as primary analyzer
18
- this.smartPipeline = null;
19
-
20
- try {
21
- this.smartPipeline = require('./analyzer-smart-pipeline.js');
21
+ this.ruleName = 'Catch Block Logging Validation';
22
+ this.description = 'Validates comprehensive error logging in catch blocks';
23
+ this.semanticEngine = semanticEngine;
24
+ this.project = null;
25
+ this.verbose = false;
26
+
27
+ // Logging patterns configuration
28
+ this.config = {
29
+ // Valid logging methods (console, logger, etc.)
30
+ loggingMethods: [
31
+ 'console.error', 'console.warn', 'console.log', 'console.info', 'console.debug',
32
+ 'logger.error', 'logger.warn', 'logger.info', 'logger.debug', 'logger.log',
33
+ 'log.error', 'log.warn', 'log.info', 'log.debug',
34
+ 'this.logger.error', 'this.logger.warn', 'this.logger.info',
35
+ 'this.logError', 'this.logErrors', 'this.couponLogErrors',
36
+ 'Logger.error', 'Logger.warn', 'Logger.info'
37
+ ],
38
+
39
+ // Appropriate log levels for catch blocks (should use error/warn)
40
+ appropriateLevels: ['error', 'warn'],
41
+
42
+ // Inappropriate log levels (should avoid log/info/debug in catch)
43
+ inappropriateLevels: ['log', 'info', 'debug'],
44
+
45
+ // Error handling methods (throw, rethrow)
46
+ errorHandlingMethods: [
47
+ 'throw', 'rethrow', 'handleError', 'processError',
48
+ 'reportError', 'trackError', 'sendError'
49
+ ],
50
+
51
+ // Test file patterns (more lenient checking)
52
+ testPatterns: ['__tests__', '.test.', '.spec.', '/test/', '/tests/', '.stories.']
53
+ };
54
+ }
55
+
56
+ async initialize(semanticEngine = null) {
57
+ if (semanticEngine) {
58
+ this.semanticEngine = semanticEngine;
59
+ }
60
+ this.verbose = semanticEngine?.verbose || false;
61
+
62
+ // Use semantic engine's project if available, otherwise create standalone
63
+ if (this.semanticEngine?.project) {
64
+ this.project = this.semanticEngine.project;
22
65
  if (this.verbose) {
23
- console.log('[DEBUG] 🎯 C029: Smart Pipeline loaded (3-stage: Regex → AST → Data Flow)');
66
+ console.log('[DEBUG] 🎯 C029: Using semantic engine project');
24
67
  }
25
- } catch (error) {
68
+ } else {
69
+ this.project = new Project({
70
+ compilerOptions: {
71
+ target: 99, // Latest
72
+ module: 99, // ESNext
73
+ allowJs: true,
74
+ checkJs: false,
75
+ },
76
+ });
26
77
  if (this.verbose) {
27
- console.warn('[DEBUG] ⚠️ C029: Smart Pipeline failed, using fallback:', error.message);
78
+ console.log('[DEBUG] 🎯 C029: Created standalone ts-morph project');
28
79
  }
29
- this.smartPipeline = null;
30
80
  }
31
81
  }
32
82
 
33
83
  async analyze(files, language, options = {}) {
34
- // Store verbose option for this analysis
35
- this.verbose = options.verbose || this.verbose || false;
84
+ this.verbose = options.verbose || this.verbose;
36
85
 
37
- // Use Smart Pipeline as primary choice
38
- if (this.smartPipeline) {
39
- if (this.verbose) {
40
- console.log('[DEBUG] 🎯 C029: Using Smart Pipeline (3-stage analysis)...');
41
- }
42
- return await this.smartPipeline.analyze(files, language, options);
43
- } else {
44
- if (this.verbose) {
45
- console.log('[DEBUG] 🔍 C029: Using fallback regex analysis...');
46
- }
47
- return await this.analyzeWithRegex(files, language, options);
86
+ if (!this.project) {
87
+ await this.initialize();
48
88
  }
49
- }
50
89
 
51
- async analyzeWithRegex(files, language, options = {}) {
52
90
  const violations = [];
53
-
91
+
54
92
  for (const filePath of files) {
55
- if (options.verbose) {
56
- console.log(`🔍 C029 Regex: Processing ${path.basename(filePath)}...`);
57
- }
58
-
59
93
  try {
60
- const content = fs.readFileSync(filePath, 'utf8');
61
- const fileViolations = await this.analyzeFile(filePath, content, language);
94
+ const fileViolations = await this.analyzeFile(filePath, options);
62
95
  violations.push(...fileViolations);
63
96
  } catch (error) {
64
- console.warn(`⚠️ C029: Error processing ${filePath}:`, error.message);
97
+ if (this.verbose) {
98
+ console.warn(`[C029] Error analyzing ${filePath}:`, error.message);
99
+ }
65
100
  }
66
101
  }
67
-
102
+
103
+ if (this.verbose) {
104
+ console.log(`[C029] Total violations found: ${violations.length}`);
105
+ }
106
+
68
107
  return violations;
69
108
  }
70
109
 
71
- async analyzeFile(filePath, content, language) {
110
+ async analyzeFile(filePath, options = {}) {
72
111
  const violations = [];
73
- const lines = content.split('\n');
74
-
75
- for (let i = 0; i < lines.length; i++) {
76
- const line = lines[i];
112
+
113
+ // Get or add source file
114
+ let sourceFile = this.project.getSourceFile(filePath);
115
+ if (!sourceFile) {
116
+ sourceFile = this.project.addSourceFileAtPath(filePath);
117
+ }
118
+
119
+ if (this.verbose) {
120
+ console.log(`[C029] Analyzing ${path.basename(filePath)}...`);
121
+ }
122
+
123
+ // Find all try statements
124
+ const tryStatements = sourceFile.getDescendantsOfKind(SyntaxKind.TryStatement);
125
+
126
+ if (this.verbose) {
127
+ console.log(`[C029] Found ${tryStatements.length} try statements`);
128
+ }
129
+
130
+ for (const tryStatement of tryStatements) {
131
+ const catchClause = tryStatement.getCatchClause();
77
132
 
78
- // Simple catch block detection
79
- if (line.includes('catch') && line.includes('(')) {
80
- const catchBlock = this.extractCatchBlock(lines, i);
81
-
82
- if (this.isCatchBlockEmpty(catchBlock.content)) {
83
- violations.push({
84
- file: filePath,
85
- line: i + 1,
86
- column: line.indexOf('catch') + 1,
87
- message: 'Empty catch block detected',
88
- severity: 'error',
89
- ruleId: this.ruleId,
90
- type: 'empty_catch'
91
- });
92
- }
133
+ if (catchClause) {
134
+ const catchViolations = this.analyzeCatchClause(catchClause, filePath, sourceFile);
135
+ violations.push(...catchViolations);
93
136
  }
94
137
  }
138
+
139
+ return violations;
140
+ }
141
+
142
+ analyzeCatchClause(catchClause, filePath, sourceFile) {
143
+ const violations = [];
144
+ const block = catchClause.getBlock();
145
+ const variableDeclaration = catchClause.getVariableDeclaration();
95
146
 
147
+ // Get exception variable name (e, error, err, etc.)
148
+ const exceptionVar = variableDeclaration
149
+ ? variableDeclaration.getName()
150
+ : null;
151
+
152
+ const startLine = catchClause.getStartLineNumber();
153
+ const startColumn = catchClause.getStart() - catchClause.getStartLinePos() + 1;
154
+
155
+ // STAGE 1: Check if catch block is empty
156
+ if (this.isEmptyCatchBlock(block)) {
157
+ violations.push(this.createViolation(
158
+ filePath,
159
+ startLine,
160
+ startColumn,
161
+ 'empty_catch',
162
+ 'Empty catch block - errors are silently ignored',
163
+ 'Add error logging or explicit comment explaining why error is ignored',
164
+ 0.95
165
+ ));
166
+ return violations; // No need to check further
167
+ }
168
+
169
+ // STAGE 2: Find logging calls in catch block
170
+ const loggingInfo = this.findLoggingCalls(block, exceptionVar);
171
+
172
+ if (loggingInfo.hasLogging) {
173
+ // STAGE 3: Validate logging quality
174
+ const qualityIssues = this.validateLoggingQuality(loggingInfo, exceptionVar, filePath);
175
+ violations.push(...qualityIssues.map(issue =>
176
+ this.createViolation(
177
+ filePath,
178
+ startLine,
179
+ startColumn,
180
+ issue.type,
181
+ issue.message,
182
+ issue.suggestion,
183
+ issue.confidence
184
+ )
185
+ ));
186
+ } else {
187
+ // No logging found - check if there's valid error handling
188
+ const hasErrorHandling = this.hasValidErrorHandling(block, exceptionVar);
189
+
190
+ if (!hasErrorHandling && !this.isTestFile(filePath)) {
191
+ violations.push(this.createViolation(
192
+ filePath,
193
+ startLine,
194
+ startColumn,
195
+ 'no_logging',
196
+ exceptionVar
197
+ ? `Catch block does not log exception '${exceptionVar}'`
198
+ : 'Catch block does not log exception',
199
+ 'Add console.error() or logger.error() with error details',
200
+ 0.85
201
+ ));
202
+ }
203
+ }
204
+
205
+ // STAGE 4: Check for unused exception variable
206
+ if (exceptionVar && !this.isExceptionUsed(block, exceptionVar) && !this.hasExplicitIgnoreComment(catchClause)) {
207
+ violations.push(this.createViolation(
208
+ filePath,
209
+ startLine,
210
+ startColumn,
211
+ 'unused_exception',
212
+ `Exception variable '${exceptionVar}' is declared but never used`,
213
+ `Use '${exceptionVar}' in error logging or add comment explaining why it's ignored`,
214
+ 0.80
215
+ ));
216
+ }
217
+
96
218
  return violations;
97
219
  }
98
220
 
99
- extractCatchBlock(lines, startIndex) {
100
- const content = [];
101
- let braceCount = 0;
102
- let inBlock = false;
221
+ /**
222
+ * Check if catch block is empty
223
+ */
224
+ isEmptyCatchBlock(block) {
225
+ const statements = block.getStatements();
103
226
 
104
- for (let i = startIndex; i < lines.length; i++) {
105
- const line = lines[i];
106
- content.push(line);
227
+ if (statements.length === 0) {
228
+ return true;
229
+ }
230
+
231
+ // Check if only contains comments
232
+ const text = block.getText()
233
+ .replace(/\/\*[\s\S]*?\*\//g, '') // Remove block comments
234
+ .replace(/\/\/.*$/gm, '') // Remove line comments
235
+ .replace(/\{|\}/g, '') // Remove braces
236
+ .trim();
237
+
238
+ return text.length === 0;
239
+ }
240
+
241
+ /**
242
+ * Find all logging calls in catch block
243
+ */
244
+ findLoggingCalls(block, exceptionVar) {
245
+ const loggingInfo = {
246
+ hasLogging: false,
247
+ calls: [],
248
+ usesExceptionVar: false,
249
+ logLevels: [],
250
+ hasStackTrace: false,
251
+ hasContextData: false
252
+ };
253
+
254
+ // Find all call expressions
255
+ const callExpressions = block.getDescendantsOfKind(SyntaxKind.CallExpression);
256
+
257
+ for (const call of callExpressions) {
258
+ const expression = call.getExpression();
259
+ const expressionText = expression.getText();
260
+
261
+ // Check if it's a logging call
262
+ const isLogging = this.config.loggingMethods.some(method =>
263
+ expressionText.includes(method.split('.')[0]) &&
264
+ (expressionText.endsWith('.error') ||
265
+ expressionText.endsWith('.warn') ||
266
+ expressionText.endsWith('.log') ||
267
+ expressionText.endsWith('.info') ||
268
+ expressionText.endsWith('.debug') ||
269
+ expressionText.includes('logError') ||
270
+ expressionText.includes('logErrors'))
271
+ );
272
+
273
+ if (isLogging) {
274
+ loggingInfo.hasLogging = true;
275
+
276
+ // Extract log level
277
+ const logLevel = this.extractLogLevel(expressionText);
278
+ if (logLevel) {
279
+ loggingInfo.logLevels.push(logLevel);
280
+ }
281
+
282
+ // Check if exception variable is used in arguments
283
+ const args = call.getArguments();
284
+ const argsText = args.map(arg => arg.getText()).join(' ');
285
+
286
+ if (exceptionVar && argsText.includes(exceptionVar)) {
287
+ loggingInfo.usesExceptionVar = true;
288
+
289
+ // Check for stack trace
290
+ if (argsText.includes(`${exceptionVar}.stack`) ||
291
+ argsText.includes(`${exceptionVar}.message`)) {
292
+ loggingInfo.hasStackTrace = true;
293
+ }
294
+ }
295
+
296
+ // Check for context data (objects, multiple arguments)
297
+ if (args.length > 1 || this.hasObjectLiteral(args)) {
298
+ loggingInfo.hasContextData = true;
299
+ }
300
+
301
+ loggingInfo.calls.push({
302
+ expression: expressionText,
303
+ level: logLevel,
304
+ arguments: argsText,
305
+ line: call.getStartLineNumber()
306
+ });
307
+ }
308
+ }
309
+
310
+ return loggingInfo;
311
+ }
312
+
313
+ /**
314
+ * Extract log level from expression (error, warn, log, info, debug)
315
+ */
316
+ extractLogLevel(expressionText) {
317
+ if (expressionText.includes('.error') || expressionText.includes('logError')) return 'error';
318
+ if (expressionText.includes('.warn')) return 'warn';
319
+ if (expressionText.includes('.log')) return 'log';
320
+ if (expressionText.includes('.info')) return 'info';
321
+ if (expressionText.includes('.debug')) return 'debug';
322
+ return null;
323
+ }
324
+
325
+ /**
326
+ * Check if arguments contain object literals (context data)
327
+ */
328
+ hasObjectLiteral(args) {
329
+ return args.some(arg =>
330
+ arg.getKind() === SyntaxKind.ObjectLiteralExpression
331
+ );
332
+ }
333
+
334
+ /**
335
+ * Validate logging quality
336
+ */
337
+ validateLoggingQuality(loggingInfo, exceptionVar, filePath) {
338
+ const issues = [];
339
+
340
+ // Issue 1: Using inappropriate log level (log/info/debug instead of error/warn)
341
+ const hasInappropriateLevel = loggingInfo.logLevels.some(level =>
342
+ this.config.inappropriateLevels.includes(level)
343
+ );
344
+
345
+ if (hasInappropriateLevel && !this.isTestFile(filePath)) {
346
+ issues.push({
347
+ type: 'inappropriate_log_level',
348
+ message: `Catch block uses console.${loggingInfo.logLevels.join('/')} instead of console.error or console.warn`,
349
+ suggestion: 'Use console.error() or console.warn() for error logging in catch blocks',
350
+ confidence: 0.75
351
+ });
352
+ }
353
+
354
+ // Issue 2: Exception variable not included in logging
355
+ if (exceptionVar && !loggingInfo.usesExceptionVar) {
356
+ issues.push({
357
+ type: 'exception_not_logged',
358
+ message: `Exception variable '${exceptionVar}' is not included in logging`,
359
+ suggestion: `Include '${exceptionVar}' in console.error() to preserve error details`,
360
+ confidence: 0.85
361
+ });
362
+ }
363
+
364
+ // Issue 3: No stack trace (optional but recommended)
365
+ if (exceptionVar && loggingInfo.usesExceptionVar && !loggingInfo.hasStackTrace) {
366
+ // This is a warning, not critical
367
+ issues.push({
368
+ type: 'missing_stack_trace',
369
+ message: `Logging does not include stack trace (${exceptionVar}.stack)`,
370
+ suggestion: `Consider logging ${exceptionVar}.stack for better debugging`,
371
+ confidence: 0.60
372
+ });
373
+ }
374
+
375
+ // Issue 4: No context data (optional but recommended)
376
+ if (!loggingInfo.hasContextData && !this.isTestFile(filePath)) {
377
+ issues.push({
378
+ type: 'missing_context_data',
379
+ message: 'Error logging lacks context information (user ID, request ID, parameters)',
380
+ suggestion: 'Add context object with relevant data: { userId, requestId, ...params, error }',
381
+ confidence: 0.55
382
+ });
383
+ }
384
+
385
+ return issues;
386
+ }
387
+
388
+ /**
389
+ * Check if catch block has valid error handling
390
+ * Accepts: throw, return with error, error handlers, delegation to any function with exception
391
+ */
392
+ hasValidErrorHandling(block, exceptionVar) {
393
+ const text = block.getText();
394
+
395
+ // Pattern 1: Re-throwing error
396
+ if (/\bthrow\b/.test(text)) {
397
+ return true;
398
+ }
399
+
400
+ // Pattern 2: Return statement with exception variable
401
+ // Accept: return error, return handleError(error), return {error}
402
+ // Reject: return null, return false (no error info)
403
+ if (exceptionVar && /\breturn\b/.test(text)) {
404
+ const returnStatements = block.getDescendantsOfKind(SyntaxKind.ReturnStatement);
405
+ for (const returnStmt of returnStatements) {
406
+ const returnExpr = returnStmt.getExpression();
407
+ if (returnExpr) {
408
+ const returnText = returnExpr.getText();
409
+ // Check if return expression includes exception variable
410
+ if (new RegExp(`\\b${exceptionVar}\\b`).test(returnText)) {
411
+ return true;
412
+ }
413
+ }
414
+ }
415
+ }
416
+
417
+ // Pattern 3: Common error handler functions
418
+ const errorHandlerPatterns = [
419
+ /handleError\s*\(/,
420
+ /processError\s*\(/,
421
+ /reportError\s*\(/,
422
+ /trackError\s*\(/,
423
+ /sendError\s*\(/,
424
+ /errorHandler\s*\(/,
425
+ /externalErrorHandler\s*\(/,
426
+ /logError\s*\(/,
427
+ /captureError\s*\(/,
428
+ /recordError\s*\(/
429
+ ];
430
+
431
+ if (errorHandlerPatterns.some(pattern => pattern.test(text))) {
432
+ return true;
433
+ }
434
+
435
+ // Pattern 4: Delegation - calling ANY function/helper with exception variable
436
+ // Examples: handleApiError(error), utils.logException(err), sendToSentry(e)
437
+ if (exceptionVar) {
438
+ const callExpressions = block.getDescendantsOfKind(SyntaxKind.CallExpression);
107
439
 
108
- for (const char of line) {
109
- if (char === '{') {
110
- braceCount++;
111
- inBlock = true;
112
- } else if (char === '}') {
113
- braceCount--;
114
- if (braceCount === 0 && inBlock) {
115
- return { content, endIndex: i };
440
+ for (const call of callExpressions) {
441
+ const args = call.getArguments();
442
+
443
+ // Check if exception variable is passed to any function
444
+ for (const arg of args) {
445
+ const argText = arg.getText().trim();
446
+
447
+ // Direct usage: someFunction(error)
448
+ if (argText === exceptionVar) {
449
+ return true;
450
+ }
451
+
452
+ // Property access: someFunction(error.message)
453
+ if (argText.startsWith(exceptionVar + '.')) {
454
+ return true;
455
+ }
456
+
457
+ // In object/array: someFunction({error: error})
458
+ if (new RegExp(`\\b${exceptionVar}\\b`).test(argText)) {
459
+ return true;
116
460
  }
117
461
  }
118
462
  }
119
463
  }
120
-
121
- return { content, endIndex: startIndex };
464
+
465
+ return false;
122
466
  }
123
467
 
124
- isCatchBlockEmpty(content) {
125
- const blockContent = content.join('\n');
468
+ /**
469
+ * Check if exception variable is used anywhere in catch block
470
+ */
471
+ isExceptionUsed(block, exceptionVar) {
472
+ const text = block.getText();
126
473
 
127
- // Remove comments and whitespace
128
- const cleanContent = blockContent
129
- .replace(/\/\*[\s\S]*?\*\//g, '') // Remove multi-line comments
130
- .replace(/\/\/.*$/gm, '') // Remove single-line comments
131
- .replace(/\s+/g, ' ') // Normalize whitespace
132
- .trim();
474
+ // Simple regex to find usage of exception variable
475
+ const usageRegex = new RegExp(`\\b${exceptionVar}\\b`, 'g');
476
+ const matches = text.match(usageRegex) || [];
133
477
 
134
- // Check if only contains catch declaration and braces
135
- const hasOnlyStructure = /^catch\s*\([^)]*\)\s*\{\s*\}$/.test(cleanContent);
478
+ // Exclude the catch declaration itself
479
+ const catchDeclaration = new RegExp(`catch\\s*\\(\\s*${exceptionVar}\\s*\\)`, 'g');
480
+ const declarationMatches = text.match(catchDeclaration) || [];
481
+
482
+ return (matches.length - declarationMatches.length) > 0;
483
+ }
484
+
485
+ /**
486
+ * Check if catch clause has explicit ignore comment
487
+ */
488
+ hasExplicitIgnoreComment(catchClause) {
489
+ const text = catchClause.getText();
136
490
 
137
- return hasOnlyStructure;
491
+ const ignorePatterns = [
492
+ /\/\/\s*ignore/i,
493
+ /\/\/\s*TODO/i,
494
+ /\/\/\s*FIXME/i,
495
+ /\/\/\s*eslint-disable/i,
496
+ /\/\*\s*ignore/i,
497
+ /\/\*\s*TODO/i,
498
+ /\/\*\s*FIXME/i,
499
+ /\/\*\s*eslint-disable/i
500
+ ];
501
+
502
+ return ignorePatterns.some(pattern => pattern.test(text));
503
+ }
504
+
505
+ /**
506
+ * Check if file is a test file (more lenient rules)
507
+ */
508
+ isTestFile(filePath) {
509
+ return this.config.testPatterns.some(pattern => filePath.includes(pattern));
510
+ }
511
+
512
+ /**
513
+ * Create violation object
514
+ */
515
+ createViolation(filePath, line, column, type, message, suggestion, confidence) {
516
+ return {
517
+ ruleId: this.ruleId,
518
+ file: filePath,
519
+ line: line,
520
+ column: column,
521
+ message: message,
522
+ severity: this.getSeverity(type, confidence),
523
+ type: type,
524
+ confidence: confidence,
525
+ suggestion: suggestion,
526
+ analyzer: 'ts-morph'
527
+ };
528
+ }
529
+
530
+ /**
531
+ * Determine severity based on type and confidence
532
+ */
533
+ getSeverity(type, confidence) {
534
+ // High severity violations
535
+ const highSeverityTypes = ['empty_catch', 'no_logging', 'unused_exception'];
536
+ if (highSeverityTypes.includes(type) && confidence > 0.8) {
537
+ return 'error';
538
+ }
539
+
540
+ // Medium severity violations
541
+ const mediumSeverityTypes = ['exception_not_logged', 'inappropriate_log_level'];
542
+ if (mediumSeverityTypes.includes(type) && confidence > 0.7) {
543
+ return 'warning';
544
+ }
545
+
546
+ // Low severity violations (recommendations)
547
+ return 'info';
138
548
  }
139
549
  }
140
550