@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.
- package/config/rule-analysis-strategies.js +3 -3
- package/config/rules/enhanced-rules-registry.json +40 -20
- package/core/analysis-orchestrator.js +11 -3
- package/core/cli-action-handler.js +2 -2
- package/core/config-merger.js +28 -6
- package/core/constants/defaults.js +1 -1
- package/core/file-targeting-service.js +72 -4
- package/core/output-service.js +48 -13
- package/core/summary-report-service.js +21 -3
- package/engines/heuristic-engine.js +5 -0
- package/package.json +1 -1
- package/rules/common/C002_no_duplicate_code/README.md +115 -0
- package/rules/common/C002_no_duplicate_code/analyzer.js +615 -219
- package/rules/common/C002_no_duplicate_code/test-cases/api-handlers.ts +64 -0
- package/rules/common/C002_no_duplicate_code/test-cases/data-processor.ts +46 -0
- package/rules/common/C002_no_duplicate_code/test-cases/good-example.tsx +40 -0
- package/rules/common/C002_no_duplicate_code/test-cases/product-service.ts +57 -0
- package/rules/common/C002_no_duplicate_code/test-cases/user-service.ts +49 -0
- package/rules/common/C008/analyzer.js +40 -0
- package/rules/common/C008/config.json +20 -0
- package/rules/common/C008/ts-morph-analyzer.js +1067 -0
- package/rules/common/C018_no_throw_generic_error/analyzer.js +1 -1
- package/rules/common/C018_no_throw_generic_error/symbol-based-analyzer.js +27 -3
- package/rules/common/C024_no_scatter_hardcoded_constants/symbol-based-analyzer.js +504 -162
- package/rules/common/C029_catch_block_logging/analyzer.js +499 -89
- package/rules/common/C033_separate_service_repository/README.md +131 -20
- package/rules/common/C033_separate_service_repository/analyzer.js +1 -1
- package/rules/common/C033_separate_service_repository/symbol-based-analyzer.js +417 -274
- package/rules/common/C041_no_sensitive_hardcode/analyzer.js +144 -254
- package/rules/common/C041_no_sensitive_hardcode/config.json +50 -0
- package/rules/common/C041_no_sensitive_hardcode/symbol-based-analyzer.js +575 -0
- package/rules/common/C047_no_duplicate_retry_logic/analyzer.js +96 -40
- package/rules/common/C047_no_duplicate_retry_logic/symbol-analyzer-enhanced.js +17 -2
- package/rules/common/C067_no_hardcoded_config/analyzer.js +17 -16
- package/rules/common/C067_no_hardcoded_config/symbol-based-analyzer.js +3477 -659
- package/rules/docs/C002_no_duplicate_code.md +276 -11
- package/rules/index.js +5 -1
- package/rules/security/S006_no_plaintext_recovery_codes/analyzer.js +266 -88
- package/rules/security/S006_no_plaintext_recovery_codes/symbol-based-analyzer.js +805 -0
- package/rules/security/S010_no_insecure_encryption/README.md +78 -0
- package/rules/security/S010_no_insecure_encryption/analyzer.js +463 -398
- package/rules/security/S013_tls_enforcement/README.md +51 -0
- package/rules/security/S013_tls_enforcement/analyzer.js +99 -0
- package/rules/security/S013_tls_enforcement/config.json +41 -0
- package/rules/security/S013_tls_enforcement/symbol-based-analyzer.js +339 -0
- package/rules/security/S014_tls_version_enforcement/README.md +354 -0
- package/rules/security/S014_tls_version_enforcement/analyzer.js +118 -0
- package/rules/security/S014_tls_version_enforcement/config.json +56 -0
- package/rules/security/S014_tls_version_enforcement/symbol-based-analyzer.js +194 -0
- package/rules/security/S055_content_type_validation/analyzer.js +121 -279
- package/rules/security/S055_content_type_validation/symbol-based-analyzer.js +346 -0
- package/rules/tests/C002_no_duplicate_code.test.js +111 -22
- package/docs/CONSTANTS-ARCHITECTURE.md +0 -288
- package/docs/DEPLOYMENT-STRATEGIES.md +0 -270
- package/docs/ESLINT_INTEGRATION.md +0 -238
- package/docs/PERFORMANCE_MIGRATION_GUIDE.md +0 -368
- package/docs/PERFORMANCE_OPTIMIZATION_PLAN.md +0 -255
- package/rules/common/C029_catch_block_logging/analyzer-smart-pipeline.js +0 -755
- 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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
86
|
-
|
|
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
|
-
|
|
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,
|
|
615
|
+
generateEnhancedViolations(duplicateGroups, currentFilePath) {
|
|
564
616
|
const violations = [];
|
|
565
617
|
|
|
566
618
|
duplicateGroups.forEach(group => {
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
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(
|
|
2
|
+
const C067SymbolBasedAnalyzer = require("./symbol-based-analyzer.js");
|
|
3
3
|
|
|
4
4
|
class C067Analyzer {
|
|
5
5
|
constructor(semanticEngine = null) {
|
|
6
|
-
this.ruleId =
|
|
7
|
-
this.ruleName =
|
|
8
|
-
this.description =
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
}
|