@sun-asterisk/sunlint 1.3.16 → 1.3.18

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 (59) hide show
  1. package/config/rule-analysis-strategies.js +3 -3
  2. package/config/rules/enhanced-rules-registry.json +40 -20
  3. package/core/analysis-orchestrator.js +11 -3
  4. package/core/cli-action-handler.js +2 -2
  5. package/core/config-merger.js +28 -6
  6. package/core/constants/defaults.js +1 -1
  7. package/core/file-targeting-service.js +72 -4
  8. package/core/output-service.js +48 -13
  9. package/core/summary-report-service.js +21 -3
  10. package/engines/heuristic-engine.js +5 -0
  11. package/package.json +1 -1
  12. package/rules/common/C002_no_duplicate_code/README.md +115 -0
  13. package/rules/common/C002_no_duplicate_code/analyzer.js +615 -219
  14. package/rules/common/C002_no_duplicate_code/test-cases/api-handlers.ts +64 -0
  15. package/rules/common/C002_no_duplicate_code/test-cases/data-processor.ts +46 -0
  16. package/rules/common/C002_no_duplicate_code/test-cases/good-example.tsx +40 -0
  17. package/rules/common/C002_no_duplicate_code/test-cases/product-service.ts +57 -0
  18. package/rules/common/C002_no_duplicate_code/test-cases/user-service.ts +49 -0
  19. package/rules/common/C008/analyzer.js +40 -0
  20. package/rules/common/C008/config.json +20 -0
  21. package/rules/common/C008/ts-morph-analyzer.js +1067 -0
  22. package/rules/common/C018_no_throw_generic_error/analyzer.js +1 -1
  23. package/rules/common/C018_no_throw_generic_error/symbol-based-analyzer.js +27 -3
  24. package/rules/common/C024_no_scatter_hardcoded_constants/symbol-based-analyzer.js +504 -162
  25. package/rules/common/C029_catch_block_logging/analyzer.js +499 -89
  26. package/rules/common/C033_separate_service_repository/README.md +131 -20
  27. package/rules/common/C033_separate_service_repository/analyzer.js +1 -1
  28. package/rules/common/C033_separate_service_repository/symbol-based-analyzer.js +417 -274
  29. package/rules/common/C041_no_sensitive_hardcode/analyzer.js +144 -254
  30. package/rules/common/C041_no_sensitive_hardcode/config.json +50 -0
  31. package/rules/common/C041_no_sensitive_hardcode/symbol-based-analyzer.js +575 -0
  32. package/rules/common/C047_no_duplicate_retry_logic/analyzer.js +96 -40
  33. package/rules/common/C047_no_duplicate_retry_logic/symbol-analyzer-enhanced.js +17 -2
  34. package/rules/common/C067_no_hardcoded_config/analyzer.js +17 -16
  35. package/rules/common/C067_no_hardcoded_config/symbol-based-analyzer.js +3477 -659
  36. package/rules/docs/C002_no_duplicate_code.md +276 -11
  37. package/rules/index.js +5 -1
  38. package/rules/security/S006_no_plaintext_recovery_codes/analyzer.js +266 -88
  39. package/rules/security/S006_no_plaintext_recovery_codes/symbol-based-analyzer.js +805 -0
  40. package/rules/security/S010_no_insecure_encryption/README.md +78 -0
  41. package/rules/security/S010_no_insecure_encryption/analyzer.js +463 -398
  42. package/rules/security/S013_tls_enforcement/README.md +51 -0
  43. package/rules/security/S013_tls_enforcement/analyzer.js +99 -0
  44. package/rules/security/S013_tls_enforcement/config.json +41 -0
  45. package/rules/security/S013_tls_enforcement/symbol-based-analyzer.js +339 -0
  46. package/rules/security/S014_tls_version_enforcement/README.md +354 -0
  47. package/rules/security/S014_tls_version_enforcement/analyzer.js +118 -0
  48. package/rules/security/S014_tls_version_enforcement/config.json +56 -0
  49. package/rules/security/S014_tls_version_enforcement/symbol-based-analyzer.js +194 -0
  50. package/rules/security/S055_content_type_validation/analyzer.js +121 -279
  51. package/rules/security/S055_content_type_validation/symbol-based-analyzer.js +346 -0
  52. package/rules/tests/C002_no_duplicate_code.test.js +111 -22
  53. package/docs/CONSTANTS-ARCHITECTURE.md +0 -288
  54. package/docs/DEPLOYMENT-STRATEGIES.md +0 -270
  55. package/docs/ESLINT_INTEGRATION.md +0 -238
  56. package/docs/PERFORMANCE_MIGRATION_GUIDE.md +0 -368
  57. package/docs/PERFORMANCE_OPTIMIZATION_PLAN.md +0 -255
  58. package/rules/common/C029_catch_block_logging/analyzer-smart-pipeline.js +0 -755
  59. package/rules/common/C041_no_sensitive_hardcode/ast-analyzer.js +0 -296
@@ -47,7 +47,9 @@ class C047Analyzer {
47
47
 
48
48
  async analyze(files, language, options = {}) {
49
49
  const violations = [];
50
+ this.retryPatterns = []; // ✅ Reset once for entire analysis, not per file
50
51
 
52
+ // Phase 1: Collect all retry patterns from all files
51
53
  for (const filePath of files) {
52
54
  if (options.verbose) {
53
55
  console.log(`🔍 Running C047 analysis on ${require('path').basename(filePath)}`);
@@ -55,41 +57,65 @@ class C047Analyzer {
55
57
 
56
58
  try {
57
59
  const content = require('fs').readFileSync(filePath, 'utf8');
58
- this.retryPatterns = []; // Reset for each file
59
- const fileViolations = this.analyzeFile(content, filePath);
60
- violations.push(...fileViolations);
60
+ this.collectRetryPatterns(content, filePath);
61
61
  } catch (error) {
62
62
  console.warn(`⚠️ Failed to analyze ${filePath}: ${error.message}`);
63
63
  }
64
64
  }
65
65
 
66
+ // ✅ Safety check: If no valid retry patterns found, return empty (no violations)
67
+ if (this.retryPatterns.length === 0) {
68
+ if (options.verbose) {
69
+ console.log('🔍 [C047] No retry patterns found - skipping');
70
+ }
71
+ return violations;
72
+ }
73
+
74
+ if (options.verbose) {
75
+ console.log(`🔍 [C047] Found ${this.retryPatterns.length} retry patterns:`);
76
+ this.retryPatterns.forEach((p, i) => {
77
+ console.log(` [${i}] ${p.filePath}:${p.line} - type: ${p.type}`);
78
+ });
79
+ }
80
+
81
+ // Phase 2: Detect duplicates across all collected patterns
82
+ const duplicateGroups = this.enhancedDuplicateDetection();
83
+
84
+ // ✅ Safety check: If no duplicate groups found, return empty (no violations)
85
+ if (duplicateGroups.length === 0) {
86
+ return violations;
87
+ }
88
+
89
+ // Phase 3: Generate violations for each file
90
+ for (const filePath of files) {
91
+ try {
92
+ const fileViolations = this.generateViolationsForFile(duplicateGroups, filePath);
93
+ violations.push(...fileViolations);
94
+ } catch (error) {
95
+ console.warn(`⚠️ Failed to generate violations for ${filePath}: ${error.message}`);
96
+ }
97
+ }
98
+
66
99
  return violations;
67
100
  }
68
-
69
- analyzeFile(content, filePath) {
70
- const violations = [];
101
+
102
+ collectRetryPatterns(content, filePath) {
71
103
  const lines = content.split('\n');
72
-
73
- // Find all retry patterns in the file
74
104
  this.findRetryPatterns(lines, filePath);
75
105
 
76
106
  // Add architectural context to patterns
77
107
  this.retryPatterns.forEach(pattern => {
78
- if (!pattern.context) {
79
- const contextLines = lines.slice(Math.max(0, pattern.line - 10), pattern.line + 10);
108
+ if (!pattern.context && pattern.filePath === filePath) {
109
+ const lineIndex = pattern.line - 1;
110
+ const contextLines = lines.slice(Math.max(0, lineIndex - 10), lineIndex + 10);
80
111
  const contextContent = contextLines.join('\n');
81
112
  pattern.context = this.analyzeArchitecturalContext(filePath, contextContent);
82
113
  }
83
114
  });
84
-
85
- // Enhanced duplicate detection with architectural intelligence
86
- const duplicateGroups = this.enhancedDuplicateDetection();
87
-
88
- // Generate violations with architectural context
89
- const enhancedViolations = this.generateEnhancedViolations(duplicateGroups, filePath);
90
- violations.push(...enhancedViolations);
91
-
92
- return violations;
115
+ }
116
+
117
+ generateViolationsForFile(duplicateGroups, filePath) {
118
+ return this.generateEnhancedViolations(duplicateGroups, filePath);
93
119
  }
94
120
 
95
121
  findRetryPatterns(lines, filePath) {
@@ -114,7 +140,8 @@ class C047Analyzer {
114
140
  this.retryPatterns.push({
115
141
  ...pattern,
116
142
  line: i + 1,
117
- column: line.indexOf(line.trim()) + 1
143
+ column: line.indexOf(line.trim()) + 1,
144
+ filePath: filePath // ✅ Store file path with pattern
118
145
  });
119
146
  }
120
147
  }
@@ -132,12 +159,26 @@ class C047Analyzer {
132
159
  continue;
133
160
  }
134
161
 
162
+ // Skip if it's just a data field assignment (not retry logic)
163
+ // e.g., "retryAttempt: payment.retryCount || 0" in logging context
164
+ if (line.includes(':') && !line.includes('const') && !line.includes('let') && !line.includes('var')) {
165
+ // This is likely an object property, not a variable declaration
166
+ continue;
167
+ }
168
+
169
+ // Skip if it's part of error logging object (common false positive)
170
+ if (contextText.includes('logger.error') || contextText.includes('console.error') ||
171
+ contextText.includes('log.error') || line.includes('error:') || line.includes('message:')) {
172
+ continue;
173
+ }
174
+
135
175
  const pattern = this.extractRetryPattern(lines, i, 'variable');
136
176
  if (pattern) {
137
177
  this.retryPatterns.push({
138
178
  ...pattern,
139
179
  line: i + 1,
140
- column: line.indexOf(line.trim()) + 1
180
+ column: line.indexOf(line.trim()) + 1,
181
+ filePath: filePath // ✅ Store file path with pattern
141
182
  });
142
183
  }
143
184
  }
@@ -159,7 +200,8 @@ class C047Analyzer {
159
200
  this.retryPatterns.push({
160
201
  ...pattern,
161
202
  line: i + 1,
162
- column: line.indexOf(line.trim()) + 1
203
+ column: line.indexOf(line.trim()) + 1,
204
+ filePath: filePath // ✅ Store file path with pattern
163
205
  });
164
206
  }
165
207
  }
@@ -205,10 +247,20 @@ class C047Analyzer {
205
247
  return false;
206
248
  }
207
249
 
250
+ // ✅ Skip object properties (data fields) - not variable declarations
251
+ // Pattern: "propertyName: value," or "propertyName: value }"
252
+ const trimmed = line.trim();
253
+ if (/^\w+\s*:\s*.+[,}]?\s*$/.test(trimmed) &&
254
+ !trimmed.startsWith('const') &&
255
+ !trimmed.startsWith('let') &&
256
+ !trimmed.startsWith('var')) {
257
+ return false;
258
+ }
259
+
208
260
  // Check for variable declarations with retry-related names that involve logic
209
261
  const declarationPatterns = [
210
- /(?:const|let|var)\s+.*(?:retry|attempt|tries|maxretries|maxattempts)/,
211
- /(?:retry|attempt|tries|maxretries|maxattempts)\s*[:=]/
262
+ /(?:const|let|var)\s+.*(?:retry|attempt|tries|maxretries|maxattempts)/
263
+ // ✅ Removed the object property pattern that causes false positives
212
264
  ];
213
265
 
214
266
  // Only consider it a retry pattern if it has logical complexity
@@ -560,25 +612,29 @@ class C047Analyzer {
560
612
  };
561
613
  }
562
614
 
563
- generateEnhancedViolations(duplicateGroups, filePath) {
615
+ generateEnhancedViolations(duplicateGroups, currentFilePath) {
564
616
  const violations = [];
565
617
 
566
618
  duplicateGroups.forEach(group => {
567
- const firstPattern = group.patterns[0];
568
-
569
- violations.push({
570
- file: filePath,
571
- line: firstPattern.line,
572
- column: firstPattern.column || 1,
573
- message: `${group.legitimacy.reason} (${group.patterns.length} similar patterns found). Consider using a centralized retry utility.`,
574
- severity: group.legitimacy.severity || 'warning',
575
- ruleId: this.ruleId,
576
- type: 'duplicate_retry_logic',
577
- duplicateCount: group.patterns.length,
578
- architecturalContext: {
579
- layers: [...new Set(group.patterns.map(p => p.context?.layer))],
580
- purposes: [...new Set(group.patterns.map(p => p.context?.purpose))],
581
- confidence: group.legitimacy.confidence
619
+ // Create violations for each pattern in the group
620
+ group.patterns.forEach(pattern => {
621
+ // Only create violation for patterns in the current file being analyzed
622
+ if (pattern.filePath === currentFilePath) {
623
+ violations.push({
624
+ file: pattern.filePath || currentFilePath,
625
+ line: pattern.line,
626
+ column: pattern.column || 1,
627
+ message: `${group.legitimacy.reason} (${group.patterns.length} similar patterns found). Consider using a centralized retry utility.`,
628
+ severity: group.legitimacy.severity || 'warning',
629
+ ruleId: this.ruleId,
630
+ type: 'duplicate_retry_logic',
631
+ duplicateCount: group.patterns.length,
632
+ architecturalContext: {
633
+ layers: [...new Set(group.patterns.map(p => p.context?.layer))],
634
+ purposes: [...new Set(group.patterns.map(p => p.context?.purpose))],
635
+ confidence: group.legitimacy.confidence
636
+ }
637
+ });
582
638
  }
583
639
  });
584
640
  });
@@ -600,6 +600,21 @@ class C047SymbolAnalyzerEnhanced {
600
600
  if (!catchClause) continue;
601
601
 
602
602
  const catchBlock = catchClause.getBlock();
603
+ const catchText = catchBlock.getText().toLowerCase();
604
+
605
+ // ✅ STRICT VALIDATION: Must have retry indicators (maxRetries, attempts, delay, backoff)
606
+ const hasRetryIndicators = /(?:maxretries|maxattempts|attempt|retry|backoff|delay|timeout)/i.test(catchText);
607
+ if (!hasRetryIndicators) {
608
+ // Skip try-catch without retry logic (e.g., just logging errors)
609
+ continue;
610
+ }
611
+
612
+ // ✅ Skip logging-only catch blocks (common false positive)
613
+ const isLoggingOnly = /(?:logger\.|console\.|log\.)(?:error|warn|info)/i.test(catchText) &&
614
+ !hasRetryIndicators;
615
+ if (isLoggingOnly) {
616
+ continue;
617
+ }
603
618
 
604
619
  // Look for retry calls in catch block
605
620
  const callExpressions = catchBlock.getDescendantsOfKind(require('ts-morph').SyntaxKind.CallExpression);
@@ -615,8 +630,8 @@ class C047SymbolAnalyzerEnhanced {
615
630
  );
616
631
  }
617
632
 
618
- // Pattern 2: Direct API retry
619
- if (this.isApiCall(callText)) {
633
+ // Pattern 2: Direct API retry (ONLY if has retry indicators)
634
+ if (this.isApiCall(callText) && hasRetryIndicators) {
620
635
  return this.createRetryPattern(
621
636
  functionName, filePath, 'exception_api_retry',
622
637
  func.getStartLineNumber(), 'try-catch with API re-call'
@@ -1,15 +1,16 @@
1
1
  // rules/common/C067_no_hardcoded_config/analyzer.js
2
- const C067SymbolBasedAnalyzer = require('./symbol-based-analyzer.js');
2
+ const C067SymbolBasedAnalyzer = require("./symbol-based-analyzer.js");
3
3
 
4
4
  class C067Analyzer {
5
5
  constructor(semanticEngine = null) {
6
- this.ruleId = 'C067';
7
- this.ruleName = 'No Hardcoded Configuration';
8
- this.description = 'Improve configurability, reduce risk when changing environments, and make configuration management flexible and maintainable.';
6
+ this.ruleId = "C067";
7
+ this.ruleName = "Do not hardcode configuration inside code";
8
+ this.description =
9
+ "Improve configurability, reduce risk when changing environments, and make configuration management flexible and maintainable. Avoid hardcoding API URLs, credentials, timeouts, retry intervals, batch sizes, and feature flags.";
9
10
  this.semanticEngine = semanticEngine;
10
11
  this.verbose = false;
11
-
12
- // Use symbol-based only for accuracy
12
+
13
+ // Use symbol-based analyzer with ts-morph for accurate AST analysis
13
14
  this.symbolAnalyzer = new C067SymbolBasedAnalyzer(semanticEngine);
14
15
  }
15
16
 
@@ -24,12 +25,12 @@ class C067Analyzer {
24
25
  // Main analyze method required by heuristic engine
25
26
  async analyze(files, language, options = {}) {
26
27
  const violations = [];
27
-
28
+
28
29
  for (const filePath of files) {
29
30
  if (options.verbose) {
30
- console.log(`[DEBUG] 🎯 C067: Analyzing ${filePath.split('/').pop()}`);
31
+ console.log(`[DEBUG] 🎯 C067: Analyzing ${filePath.split("/").pop()}`);
31
32
  }
32
-
33
+
33
34
  try {
34
35
  const fileViolations = await this.analyzeFileBasic(filePath, options);
35
36
  violations.push(...fileViolations);
@@ -37,7 +38,7 @@ class C067Analyzer {
37
38
  console.warn(`C067: Skipping ${filePath}: ${error.message}`);
38
39
  }
39
40
  }
40
-
41
+
41
42
  return violations;
42
43
  }
43
44
 
@@ -46,11 +47,11 @@ class C067Analyzer {
46
47
  // Try semantic engine first, fallback to standalone ts-morph
47
48
  if (this.semanticEngine?.isSymbolEngineReady?.() && this.semanticEngine.project) {
48
49
  if (this.verbose) {
49
- console.log(`[DEBUG] 🎯 C067: Using semantic engine for ${filePath.split('/').pop()}`);
50
+ console.log(`[DEBUG] 🎯 C067: Using semantic engine for ${filePath.split("/").pop()}`);
50
51
  }
51
-
52
+
52
53
  const violations = await this.symbolAnalyzer.analyzeFileBasic(filePath, options);
53
-
54
+
54
55
  if (this.verbose) {
55
56
  console.log(`[DEBUG] 🎯 C067: Symbol-based analysis found ${violations.length} violations`);
56
57
  }
@@ -59,11 +60,11 @@ class C067Analyzer {
59
60
  } else {
60
61
  // Fallback to standalone analysis
61
62
  if (this.verbose) {
62
- console.log(`[DEBUG] 🎯 C067: Using standalone analysis for ${filePath.split('/').pop()}`);
63
+ console.log(`[DEBUG] 🎯 C067: Using standalone analysis for ${filePath.split("/").pop()}`);
63
64
  }
64
-
65
+
65
66
  const violations = await this.symbolAnalyzer.analyzeFileStandalone(filePath, options);
66
-
67
+
67
68
  if (this.verbose) {
68
69
  console.log(`[DEBUG] 🎯 C067: Standalone analysis found ${violations.length} violations`);
69
70
  }