@sun-asterisk/sunlint 1.3.1 → 1.3.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/CHANGELOG.md +47 -0
- package/CONTRIBUTING.md +210 -1691
- package/config/rule-analysis-strategies.js +17 -1
- package/config/rules/enhanced-rules-registry.json +369 -1135
- package/config/rules/rules-registry-generated.json +1 -1
- package/core/enhanced-rules-registry.js +2 -1
- package/core/semantic-engine.js +15 -3
- package/core/semantic-rule-base.js +4 -2
- package/engines/heuristic-engine.js +65 -4
- package/integrations/eslint/plugin/rules/common/c003-no-vague-abbreviations.js +59 -1
- package/integrations/eslint/plugin/rules/common/c006-function-name-verb-noun.js +26 -1
- package/integrations/eslint/plugin/rules/common/c030-use-custom-error-classes.js +54 -19
- package/origin-rules/common-en.md +11 -7
- package/package.json +1 -1
- package/rules/common/C002_no_duplicate_code/analyzer.js +334 -36
- package/rules/common/C003_no_vague_abbreviations/analyzer.js +220 -35
- package/rules/common/C006_function_naming/analyzer.js +29 -3
- package/rules/common/C010_limit_block_nesting/analyzer.js +181 -337
- package/rules/common/C010_limit_block_nesting/config.json +64 -0
- package/rules/common/C010_limit_block_nesting/regex-based-analyzer.js +379 -0
- package/rules/common/C010_limit_block_nesting/symbol-based-analyzer.js +231 -0
- package/rules/common/C013_no_dead_code/analyzer.js +75 -177
- package/rules/common/C013_no_dead_code/config.json +61 -0
- package/rules/common/C013_no_dead_code/regex-based-analyzer.js +345 -0
- package/rules/common/C013_no_dead_code/symbol-based-analyzer.js +640 -0
- package/rules/common/C014_dependency_injection/analyzer.js +48 -313
- package/rules/common/C014_dependency_injection/config.json +26 -0
- package/rules/common/C014_dependency_injection/symbol-based-analyzer.js +751 -0
- package/rules/common/C018_no_throw_generic_error/analyzer.js +232 -0
- package/rules/common/C018_no_throw_generic_error/config.json +50 -0
- package/rules/common/C018_no_throw_generic_error/regex-based-analyzer.js +387 -0
- package/rules/common/C018_no_throw_generic_error/symbol-based-analyzer.js +314 -0
- package/rules/common/C019_log_level_usage/analyzer.js +110 -317
- package/rules/common/C019_log_level_usage/pattern-analyzer.js +88 -0
- package/rules/common/C019_log_level_usage/system-log-analyzer.js +1267 -0
- package/rules/common/C023_no_duplicate_variable/analyzer.js +180 -0
- package/rules/common/C023_no_duplicate_variable/config.json +50 -0
- package/rules/common/C023_no_duplicate_variable/symbol-based-analyzer.js +158 -0
- package/rules/common/C024_no_scatter_hardcoded_constants/analyzer.js +180 -0
- package/rules/common/C024_no_scatter_hardcoded_constants/config.json +50 -0
- package/rules/common/C024_no_scatter_hardcoded_constants/symbol-based-analyzer.js +181 -0
- package/rules/common/C030_use_custom_error_classes/analyzer.js +200 -0
- package/rules/common/C035_error_logging_context/analyzer.js +3 -1
- package/rules/index.js +5 -1
- package/rules/security/S009_no_insecure_encryption/README.md +158 -0
- package/rules/security/S009_no_insecure_encryption/analyzer.js +319 -0
- package/rules/security/S009_no_insecure_encryption/config.json +55 -0
- package/rules/security/S010_no_insecure_encryption/README.md +224 -0
- package/rules/security/S010_no_insecure_encryption/analyzer.js +493 -0
- package/rules/security/S010_no_insecure_encryption/config.json +48 -0
- package/rules/security/S016_no_sensitive_querystring/STRATEGY.md +149 -0
- package/rules/security/S016_no_sensitive_querystring/analyzer.js +276 -0
- package/rules/security/S016_no_sensitive_querystring/config.json +127 -0
- package/rules/security/S016_no_sensitive_querystring/regex-based-analyzer.js +258 -0
- package/rules/security/S016_no_sensitive_querystring/symbol-based-analyzer.js +495 -0
- package/rules/security/S048_no_current_password_in_reset/README.md +222 -0
- package/rules/security/S048_no_current_password_in_reset/analyzer.js +366 -0
- package/rules/security/S048_no_current_password_in_reset/config.json +48 -0
- package/rules/security/S055_content_type_validation/README.md +176 -0
- package/rules/security/S055_content_type_validation/analyzer.js +312 -0
- package/rules/security/S055_content_type_validation/config.json +48 -0
- package/rules/utils/rule-helpers.js +140 -1
- package/scripts/consolidate-config.js +116 -0
- package/config/rules/S027-categories.json +0 -122
- package/config/rules/rules-registry.json +0 -777
- package/rules/common/C006_function_naming/smart-analyzer.js +0 -503
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* C018 Main Analyzer - Do not throw generic errors
|
|
3
|
+
* Primary: Always provide detailed messages and context.
|
|
4
|
+
* Fallback: Regex-based for all other cases
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const C018SymbolBasedAnalyzer = require('./symbol-based-analyzer');
|
|
8
|
+
const C018RegexBasedAnalyzer = require('./regex-based-analyzer');
|
|
9
|
+
|
|
10
|
+
class C018Analyzer {
|
|
11
|
+
constructor(options = {}) {
|
|
12
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
13
|
+
console.log(`🔧 [C018] Constructor called with options:`, !!options);
|
|
14
|
+
console.log(`🔧 [C018] Options type:`, typeof options, Object.keys(options || {}));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
this.ruleId = 'C018';
|
|
18
|
+
this.ruleName = 'Do not throw generic errors';
|
|
19
|
+
this.description = 'Do not throw generic errors; always provide detailed messages and context.';
|
|
20
|
+
this.semanticEngine = options.semanticEngine || null;
|
|
21
|
+
this.verbose = options.verbose || false;
|
|
22
|
+
|
|
23
|
+
// Configuration
|
|
24
|
+
this.config = {
|
|
25
|
+
useSymbolBased: true, // Primary approach
|
|
26
|
+
fallbackToRegex: true, // Only when symbol fails completely
|
|
27
|
+
symbolBasedOnly: false // Can be set to true for pure mode
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Initialize both analyzers
|
|
31
|
+
try {
|
|
32
|
+
this.symbolAnalyzer = new C018SymbolBasedAnalyzer(this.semanticEngine);
|
|
33
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
34
|
+
console.log(`🔧 [C018] Symbol analyzer created successfully`);
|
|
35
|
+
}
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.error(`🔧 [C018] Error creating symbol analyzer:`, error);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
this.regexAnalyzer = new C018RegexBasedAnalyzer(this.semanticEngine);
|
|
42
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
43
|
+
console.log(`🔧 [C018] Regex analyzer created successfully`);
|
|
44
|
+
}
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error(`🔧 [C018] Error creating regex analyzer:`, error);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
50
|
+
console.log(`🔧 [C018] Constructor completed`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Initialize with semantic engine
|
|
56
|
+
*/
|
|
57
|
+
async initialize(semanticEngine = null) {
|
|
58
|
+
if (semanticEngine) {
|
|
59
|
+
this.semanticEngine = semanticEngine;
|
|
60
|
+
}
|
|
61
|
+
this.verbose = semanticEngine?.verbose || false;
|
|
62
|
+
|
|
63
|
+
// Initialize both analyzers
|
|
64
|
+
await this.symbolAnalyzer.initialize(semanticEngine);
|
|
65
|
+
await this.regexAnalyzer.initialize(semanticEngine);
|
|
66
|
+
|
|
67
|
+
// Ensure verbose flag is propagated
|
|
68
|
+
this.regexAnalyzer.verbose = this.verbose;
|
|
69
|
+
this.symbolAnalyzer.verbose = this.verbose;
|
|
70
|
+
|
|
71
|
+
if (this.verbose) {
|
|
72
|
+
console.log(`🔧 [C018 Hybrid] Analyzer initialized - verbose: ${this.verbose}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async analyze(files, language, options = {}) {
|
|
77
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
78
|
+
console.log(`🔧 [C018] analyze() method called with ${files.length} files, language: ${language}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const violations = [];
|
|
82
|
+
|
|
83
|
+
for (const filePath of files) {
|
|
84
|
+
try {
|
|
85
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
86
|
+
console.log(`🔧 [C018] Processing file: ${filePath}`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const fileViolations = await this.analyzeFile(filePath, options);
|
|
90
|
+
violations.push(...fileViolations);
|
|
91
|
+
|
|
92
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
93
|
+
console.log(`🔧 [C018] File ${filePath}: Found ${fileViolations.length} violations`);
|
|
94
|
+
}
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.warn(`❌ [C018] Analysis failed for ${filePath}:`, error.message);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
101
|
+
console.log(`🔧 [C018] Total violations found: ${violations.length}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return violations;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async analyzeFile(filePath, options = {}) {
|
|
108
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
109
|
+
console.log(`🔧 [C018] analyzeFile() called for: ${filePath}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 1. Try Symbol-based analysis first (primary)
|
|
113
|
+
if (this.config.useSymbolBased &&
|
|
114
|
+
this.semanticEngine?.project &&
|
|
115
|
+
this.semanticEngine?.initialized) {
|
|
116
|
+
try {
|
|
117
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
118
|
+
console.log(`🔧 [C018] Trying symbol-based analysis...`);
|
|
119
|
+
}
|
|
120
|
+
const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
|
|
121
|
+
if (sourceFile) {
|
|
122
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
123
|
+
console.log(`🔧 [C018] Source file found, analyzing with symbol-based...`);
|
|
124
|
+
}
|
|
125
|
+
const violations = await this.symbolAnalyzer.analyzeFileWithSymbols(filePath, { ...options, verbose: options.verbose });
|
|
126
|
+
|
|
127
|
+
// Mark violations with analysis strategy
|
|
128
|
+
violations.forEach(v => v.analysisStrategy = 'symbol-based');
|
|
129
|
+
|
|
130
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
131
|
+
console.log(`✅ [C018] Symbol-based analysis: ${violations.length} violations`);
|
|
132
|
+
}
|
|
133
|
+
return violations; // Return even if 0 violations - symbol analysis completed successfully
|
|
134
|
+
} else {
|
|
135
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
136
|
+
console.log(`⚠️ [C018] Source file not found in project`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.warn(`⚠️ [C018] Symbol analysis failed: ${error.message}`);
|
|
141
|
+
// Continue to fallback
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
145
|
+
console.log(`🔄 [C018] Symbol analysis conditions check:`);
|
|
146
|
+
console.log(` - useSymbolBased: ${this.config.useSymbolBased}`);
|
|
147
|
+
console.log(` - semanticEngine: ${!!this.semanticEngine}`);
|
|
148
|
+
console.log(` - semanticEngine.project: ${!!this.semanticEngine?.project}`);
|
|
149
|
+
console.log(` - semanticEngine.initialized: ${this.semanticEngine?.initialized}`);
|
|
150
|
+
console.log(`🔄 [C018] Symbol analysis unavailable, using regex fallback`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// 2. Fallback to regex-based analysis
|
|
155
|
+
if (this.config.fallbackToRegex) {
|
|
156
|
+
try {
|
|
157
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
158
|
+
console.log(`🔧 [C018] Trying regex-based analysis...`);
|
|
159
|
+
}
|
|
160
|
+
const violations = await this.regexAnalyzer.analyzeFileBasic(filePath, options);
|
|
161
|
+
|
|
162
|
+
// Mark violations with analysis strategy
|
|
163
|
+
violations.forEach(v => v.analysisStrategy = 'regex-fallback');
|
|
164
|
+
|
|
165
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
166
|
+
console.log(`🔄 [C018] Regex-based analysis: ${violations.length} violations`);
|
|
167
|
+
}
|
|
168
|
+
return violations;
|
|
169
|
+
} catch (error) {
|
|
170
|
+
console.error(`❌ [C018] Regex analysis failed: ${error.message}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (options?.verbose) {
|
|
175
|
+
console.log(`🔧 [C018] No analysis methods succeeded, returning empty`);
|
|
176
|
+
}
|
|
177
|
+
return [];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async analyzeFileBasic(filePath, options = {}) {
|
|
181
|
+
console.log(`🔧 [C018] analyzeFileBasic() called for: ${filePath}`);
|
|
182
|
+
console.log(`🔧 [C018] semanticEngine exists: ${!!this.semanticEngine}`);
|
|
183
|
+
console.log(`🔧 [C018] symbolAnalyzer exists: ${!!this.symbolAnalyzer}`);
|
|
184
|
+
console.log(`🔧 [C018] regexAnalyzer exists: ${!!this.regexAnalyzer}`);
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
// Try symbol-based analysis first
|
|
188
|
+
if (this.semanticEngine?.isSymbolEngineReady?.() &&
|
|
189
|
+
this.semanticEngine.project) {
|
|
190
|
+
|
|
191
|
+
if (this.verbose) {
|
|
192
|
+
console.log(`🔍 [C018] Using symbol-based analysis for ${filePath}`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const violations = await this.symbolAnalyzer.analyzeFileBasic(filePath, options);
|
|
196
|
+
return violations;
|
|
197
|
+
}
|
|
198
|
+
} catch (error) {
|
|
199
|
+
if (this.verbose) {
|
|
200
|
+
console.warn(`⚠️ [C018] Symbol analysis failed: ${error.message}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Fallback to regex-based analysis
|
|
205
|
+
if (this.verbose) {
|
|
206
|
+
console.log(`🔄 [C018] Using regex-based analysis (fallback) for ${filePath}`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
console.log(`🔧 [C018] About to call regexAnalyzer.analyzeFileBasic()`);
|
|
210
|
+
try {
|
|
211
|
+
const result = await this.regexAnalyzer.analyzeFileBasic(filePath, options);
|
|
212
|
+
console.log(`🔧 [C018] Regex analyzer returned: ${result.length} violations`);
|
|
213
|
+
return result;
|
|
214
|
+
} catch (error) {
|
|
215
|
+
console.error(`🔧 [C018] Error in regex analyzer:`, error);
|
|
216
|
+
return [];
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Methods for compatibility with different engine invocation patterns
|
|
222
|
+
*/
|
|
223
|
+
async analyzeFileWithSymbols(filePath, options = {}) {
|
|
224
|
+
return this.analyzeFile(filePath, options);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async analyzeWithSemantics(filePath, options = {}) {
|
|
228
|
+
return this.analyzeFile(filePath, options);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
module.exports = C018Analyzer;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "C018",
|
|
3
|
+
"name": "C018_do_not_throw_generic_errors",
|
|
4
|
+
"category": "architecture",
|
|
5
|
+
"description": "C018 - Always provide detailed messages and context.",
|
|
6
|
+
"severity": "warning",
|
|
7
|
+
"enabled": true,
|
|
8
|
+
"semantic": {
|
|
9
|
+
"enabled": true,
|
|
10
|
+
"priority": "high",
|
|
11
|
+
"fallback": "heuristic"
|
|
12
|
+
},
|
|
13
|
+
"patterns": {
|
|
14
|
+
"include": [
|
|
15
|
+
"**/*.js",
|
|
16
|
+
"**/*.ts",
|
|
17
|
+
"**/*.jsx",
|
|
18
|
+
"**/*.tsx"
|
|
19
|
+
],
|
|
20
|
+
"exclude": [
|
|
21
|
+
"**/*.test.*",
|
|
22
|
+
"**/*.spec.*",
|
|
23
|
+
"**/*.mock.*",
|
|
24
|
+
"**/test/**",
|
|
25
|
+
"**/tests/**",
|
|
26
|
+
"**/spec/**"
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
"options": {
|
|
30
|
+
"strictMode": false,
|
|
31
|
+
"allowedDbMethods": [],
|
|
32
|
+
"repositoryPatterns": [
|
|
33
|
+
"*Repository*",
|
|
34
|
+
"*Repo*",
|
|
35
|
+
"*DAO*",
|
|
36
|
+
"*Store*"
|
|
37
|
+
],
|
|
38
|
+
"servicePatterns": [
|
|
39
|
+
"*Service*",
|
|
40
|
+
"*UseCase*",
|
|
41
|
+
"*Handler*",
|
|
42
|
+
"*Manager*"
|
|
43
|
+
],
|
|
44
|
+
"complexityThreshold": {
|
|
45
|
+
"methodLength": 200,
|
|
46
|
+
"cyclomaticComplexity": 5,
|
|
47
|
+
"nestedDepth": 3
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* C018 Regex-based Analyzer - Do not throw generic errors
|
|
3
|
+
* Purpose: Fallback pattern matching when symbol analysis fails
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class C018RegexBasedAnalyzer {
|
|
7
|
+
constructor(semanticEngine = null) {
|
|
8
|
+
this.ruleId = 'C018';
|
|
9
|
+
this.ruleName = 'Do not throw generic errors (Regex-Based)';
|
|
10
|
+
this.semanticEngine = semanticEngine;
|
|
11
|
+
this.verbose = false;
|
|
12
|
+
|
|
13
|
+
// Patterns for identifying catch blocks (supports TypeScript type annotations)
|
|
14
|
+
this.catchPattern = /catch\s*\(\s*(\w+)(?:\s*:\s*\w+(?:\s*\|\s*\w+)*)?\s*\)\s*\{([^{}]*(?:\{[^{}]*\}[^{}]*)*)\}/gs;
|
|
15
|
+
|
|
16
|
+
// throw error patterns
|
|
17
|
+
this.exceptionPatterns = [
|
|
18
|
+
/throw\s+\w+/g,
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
// Sensitive data patterns
|
|
22
|
+
this.sensitivePatterns = [
|
|
23
|
+
/password|passwd|pwd/gi,
|
|
24
|
+
/token|jwt|auth|secret|key/gi,
|
|
25
|
+
/ssn|social|credit|card|cvv/gi
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
// String concatenation patterns (non-structured)
|
|
29
|
+
this.stringConcatPatterns = [
|
|
30
|
+
/\+\s*["'`]/g, // string concatenation
|
|
31
|
+
/["'`]\s*\+/g, // string concatenation
|
|
32
|
+
/\$\{.*\}/g // template literals (basic)
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
this.structuredPatterns = [
|
|
36
|
+
/\{\s*[a-zA-Z_$][a-zA-Z0-9_$]*\s*:\s*[^}]+\}/g, // structured object format
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
// Ensure error messages should explain what happened, why, and in what context
|
|
40
|
+
this.explanationPatterns = [
|
|
41
|
+
/\b(because|due to|failed to|cannot|invalid|missing|not found)\b/i,
|
|
42
|
+
];
|
|
43
|
+
this.guidancePatterns = [
|
|
44
|
+
/\b(please|ensure|make sure|check|try|use)\b/i,
|
|
45
|
+
];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async initialize(semanticEngine = null) {
|
|
49
|
+
if (semanticEngine) {
|
|
50
|
+
this.semanticEngine = semanticEngine;
|
|
51
|
+
}
|
|
52
|
+
this.verbose = semanticEngine?.verbose || false;
|
|
53
|
+
|
|
54
|
+
if (this.verbose) {
|
|
55
|
+
console.log(`🔧 [C018 Regex-Based] Analyzer initialized`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async analyzeFileBasic(filePath, options = {}) {
|
|
60
|
+
const fs = require('fs');
|
|
61
|
+
const path = require('path');
|
|
62
|
+
const violations = [];
|
|
63
|
+
|
|
64
|
+
if (this.verbose) {
|
|
65
|
+
console.log(`🔧 [C018 Regex] Starting analysis for: ${filePath}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
70
|
+
|
|
71
|
+
if (this.verbose) {
|
|
72
|
+
console.log(`🔧 [C018 Regex] File content length: ${content.length}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const lines = content.split('\n');
|
|
76
|
+
const catchBlocks = this.findCatchBlocks(content);
|
|
77
|
+
|
|
78
|
+
if (this.verbose) {
|
|
79
|
+
console.log(`🔧 [C018 Regex] Found ${catchBlocks.length} catch blocks`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
for (const block of catchBlocks) {
|
|
83
|
+
const blockViolations = this.analyzeCatchBlockContent(block, lines, filePath);
|
|
84
|
+
if (this.verbose && blockViolations.length > 0) {
|
|
85
|
+
console.log(`🔧 [C018 Regex] Block violations: ${blockViolations.length}`);
|
|
86
|
+
}
|
|
87
|
+
violations.push(...blockViolations);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (this.verbose) {
|
|
91
|
+
console.log(`🔧 [C018 Regex] Total violations found: ${violations.length}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return violations;
|
|
95
|
+
} catch (error) {
|
|
96
|
+
if (this.verbose) {
|
|
97
|
+
console.error(`🔧 [C018 Regex] Error analyzing ${filePath}:`, error);
|
|
98
|
+
}
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
101
|
+
} /**
|
|
102
|
+
* Find catch blocks in content using regex
|
|
103
|
+
*/
|
|
104
|
+
findCatchBlocks(content) {
|
|
105
|
+
const catchBlocks = [];
|
|
106
|
+
let match;
|
|
107
|
+
|
|
108
|
+
// Reset regex
|
|
109
|
+
this.catchPattern.lastIndex = 0;
|
|
110
|
+
|
|
111
|
+
while ((match = this.catchPattern.exec(content)) !== null) {
|
|
112
|
+
const fullMatch = match[0];
|
|
113
|
+
const errorVar = match[1];
|
|
114
|
+
const blockContent = match[2];
|
|
115
|
+
|
|
116
|
+
// Calculate line number
|
|
117
|
+
const beforeMatch = content.substring(0, match.index);
|
|
118
|
+
const lineNumber = beforeMatch.split('\n').length;
|
|
119
|
+
|
|
120
|
+
catchBlocks.push({
|
|
121
|
+
fullMatch,
|
|
122
|
+
errorVar,
|
|
123
|
+
blockContent,
|
|
124
|
+
lineNumber,
|
|
125
|
+
startIndex: match.index
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return catchBlocks;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Analyze catch block content for logging violations
|
|
134
|
+
*/
|
|
135
|
+
analyzeCatchBlockContent(catchBlock, lines, filePath) {
|
|
136
|
+
const violations = [];
|
|
137
|
+
const { blockContent, lineNumber, errorVar } = catchBlock;
|
|
138
|
+
|
|
139
|
+
// Find log calls in catch block
|
|
140
|
+
const ErrorCalls = this.findExceptionCallsInContent(blockContent);
|
|
141
|
+
|
|
142
|
+
if (ErrorCalls.length === 0) {
|
|
143
|
+
// No logging - C018's concern, not ours
|
|
144
|
+
return violations;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Analyze each log call
|
|
148
|
+
for (const errCall of ErrorCalls) {
|
|
149
|
+
const logLineNumber = lineNumber + errCall.relativeLineNumber;
|
|
150
|
+
// Check for generic error throws without context
|
|
151
|
+
if (errCall.content.includes('new Error(')) {
|
|
152
|
+
violations.push({
|
|
153
|
+
ruleId: this.ruleId,
|
|
154
|
+
severity: 'error',
|
|
155
|
+
message: 'Throwing generic Error without context',
|
|
156
|
+
source: this.ruleId,
|
|
157
|
+
file: filePath,
|
|
158
|
+
line: logLineNumber,
|
|
159
|
+
column: errCall.column,
|
|
160
|
+
description: `[REGEX-FALLBACK] Generic error thrown without context. Use structured error objects.`,
|
|
161
|
+
suggestion: 'Use specific error types or structured error objects with context',
|
|
162
|
+
category: 'error-handling'
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
// Check for generic error direct ex: throw error
|
|
166
|
+
if (errCall.content.includes(`throw ${errorVar}`)) {
|
|
167
|
+
violations.push({
|
|
168
|
+
ruleId: this.ruleId,
|
|
169
|
+
severity: 'error',
|
|
170
|
+
message: 'Throwing caught error directly without context',
|
|
171
|
+
source: this.ruleId,
|
|
172
|
+
file: filePath,
|
|
173
|
+
line: logLineNumber,
|
|
174
|
+
column: errCall.column,
|
|
175
|
+
description: `[REGEX-FALLBACK] Caught error thrown directly without additional context. Use structured error objects.`,
|
|
176
|
+
suggestion: 'Use structured error objects with context instead of throwing caught errors directly',
|
|
177
|
+
category: 'error-handling'
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
// Check for non-structured logging (string concatenation)
|
|
183
|
+
if (this.hasStringConcatenation(errCall.content)) {
|
|
184
|
+
violations.push({
|
|
185
|
+
ruleId: this.ruleId,
|
|
186
|
+
severity: 'warning',
|
|
187
|
+
message: 'Error logging should use structured format instead of string concatenation',
|
|
188
|
+
source: this.ruleId,
|
|
189
|
+
file: filePath,
|
|
190
|
+
line: logLineNumber,
|
|
191
|
+
column: errCall.column,
|
|
192
|
+
description: `[REGEX-FALLBACK] String concatenation detected in error logging. Use structured object format.`,
|
|
193
|
+
suggestion: 'Use logger.error("message", { error: e.message, context: {...} }) instead of string concatenation',
|
|
194
|
+
category: 'error-handling'
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Check for sensitive data
|
|
199
|
+
const sensitiveData = this.findSensitiveData(errCall.content);
|
|
200
|
+
if (sensitiveData.length > 0) {
|
|
201
|
+
violations.push({
|
|
202
|
+
ruleId: this.ruleId,
|
|
203
|
+
severity: 'error',
|
|
204
|
+
message: 'Error logging contains potentially sensitive data',
|
|
205
|
+
source: this.ruleId,
|
|
206
|
+
file: filePath,
|
|
207
|
+
line: logLineNumber,
|
|
208
|
+
column: errCall.column,
|
|
209
|
+
description: `[REGEX-FALLBACK] Sensitive patterns detected: ${sensitiveData.join(', ')}. Mask or exclude sensitive data.`,
|
|
210
|
+
suggestion: 'Mask sensitive data or exclude entirely from logs',
|
|
211
|
+
category: 'security'
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Check messages should explain what happened
|
|
216
|
+
const explaintionData = this.findExplanationData(errCall.content);
|
|
217
|
+
if (!explaintionData.length) {
|
|
218
|
+
violations.push({
|
|
219
|
+
ruleId: this.ruleId,
|
|
220
|
+
severity: 'warning',
|
|
221
|
+
message: 'Error logging should explain what happened',
|
|
222
|
+
source: this.ruleId,
|
|
223
|
+
file: filePath,
|
|
224
|
+
line: logLineNumber,
|
|
225
|
+
column: errCall.column,
|
|
226
|
+
description: `[SYMBOL-BASED] Error message should explain what happened, why, and in what context.`,
|
|
227
|
+
suggestion: 'Use structured error objects with context: { message: "Error occurred", context: "Request failed because todo something." } }',
|
|
228
|
+
category: 'error-handling'
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Check messages should provide guidance
|
|
233
|
+
const guidanceData = this.findGuidanceData(errCall.content);
|
|
234
|
+
if (!guidanceData.length) {
|
|
235
|
+
violations.push({
|
|
236
|
+
ruleId: this.ruleId,
|
|
237
|
+
severity: 'warning',
|
|
238
|
+
message: 'Error logging should provide guidance on next steps',
|
|
239
|
+
source: this.ruleId,
|
|
240
|
+
file: filePath,
|
|
241
|
+
line: logLineNumber,
|
|
242
|
+
column: errCall.column,
|
|
243
|
+
description: `[SYMBOL-BASED] Error message should provide guidance on next steps.`,
|
|
244
|
+
suggestion: 'Use structured error objects with guidance: { message: "Error occurred", guidance: "Please check the input and try again." } }',
|
|
245
|
+
category: 'error-handling'
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return violations;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Find log calls within catch block content
|
|
255
|
+
*/
|
|
256
|
+
findExceptionCallsInContent(content) {
|
|
257
|
+
const ErrorCalls = [];
|
|
258
|
+
const lines = content.split('\n');
|
|
259
|
+
|
|
260
|
+
for (let i = 0; i < lines.length; i++) {
|
|
261
|
+
const line = lines[i];
|
|
262
|
+
|
|
263
|
+
for (const pattern of this.exceptionPatterns) {
|
|
264
|
+
pattern.lastIndex = 0; // Reset regex
|
|
265
|
+
const match = pattern.exec(line);
|
|
266
|
+
|
|
267
|
+
if (match) {
|
|
268
|
+
ErrorCalls.push({
|
|
269
|
+
content: line.trim(),
|
|
270
|
+
relativeLineNumber: i,
|
|
271
|
+
column: match.index + 1,
|
|
272
|
+
method: match[1] || 'unknown'
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return ErrorCalls;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Check if log call uses string concatenation
|
|
283
|
+
*/
|
|
284
|
+
hasStringConcatenation(content) {
|
|
285
|
+
return this.stringConcatPatterns.some(pattern => {
|
|
286
|
+
pattern.lastIndex = 0;
|
|
287
|
+
return pattern.test(content);
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Find sensitive data patterns in content
|
|
293
|
+
*/
|
|
294
|
+
findSensitiveData(content) {
|
|
295
|
+
const found = [];
|
|
296
|
+
|
|
297
|
+
for (const pattern of this.sensitivePatterns) {
|
|
298
|
+
pattern.lastIndex = 0;
|
|
299
|
+
const matches = content.match(pattern);
|
|
300
|
+
if (matches) {
|
|
301
|
+
found.push(...matches.map(m => m.toLowerCase()));
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return [...new Set(found)]; // Remove duplicates
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Validate structure patterns
|
|
309
|
+
validateStructuredPatterns(content) {
|
|
310
|
+
for (const pattern of this.structuredPatterns) {
|
|
311
|
+
pattern.lastIndex = 0;
|
|
312
|
+
if (pattern.test(content)) {
|
|
313
|
+
return true; // Structured format found
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return false; // No structured format found
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Find explanation data patterns in content
|
|
321
|
+
*/
|
|
322
|
+
findExplanationData(content) {
|
|
323
|
+
const found = [];
|
|
324
|
+
|
|
325
|
+
for (const pattern of this.explanationPatterns) {
|
|
326
|
+
pattern.lastIndex = 0;
|
|
327
|
+
const matches = content.match(pattern);
|
|
328
|
+
if (matches) {
|
|
329
|
+
found.push(...matches.map(m => m.toLowerCase()));
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return [...new Set(found)]; // Remove duplicates
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Find guidance data patterns in content
|
|
338
|
+
*/
|
|
339
|
+
findGuidanceData(content) {
|
|
340
|
+
const found = [];
|
|
341
|
+
|
|
342
|
+
for (const pattern of this.guidancePatterns) {
|
|
343
|
+
pattern.lastIndex = 0;
|
|
344
|
+
const matches = content.match(pattern);
|
|
345
|
+
if (matches) {
|
|
346
|
+
found.push(...matches.map(m => m.toLowerCase()));
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return [...new Set(found)]; // Remove duplicates
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
async analyze(files, language, options = {}) {
|
|
354
|
+
if (this.verbose) {
|
|
355
|
+
console.log(`🔧 [C018 Regex] analyze() called with ${files.length} files, language: ${language}`);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const violations = [];
|
|
359
|
+
|
|
360
|
+
for (const filePath of files) {
|
|
361
|
+
try {
|
|
362
|
+
if (this.verbose) {
|
|
363
|
+
console.log(`🔧 [C018 Regex] Processing file: ${filePath}`);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const fileViolations = await this.analyzeFileBasic(filePath, options);
|
|
367
|
+
violations.push(...fileViolations);
|
|
368
|
+
|
|
369
|
+
if (this.verbose) {
|
|
370
|
+
console.log(`🔧 [C018 Regex] File ${filePath}: Found ${fileViolations.length} violations`);
|
|
371
|
+
}
|
|
372
|
+
} catch (error) {
|
|
373
|
+
if (this.verbose) {
|
|
374
|
+
console.warn(`❌ [C018 Regex] Analysis failed for ${filePath}:`, error.message);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (this.verbose) {
|
|
380
|
+
console.log(`🔧 [C018 Regex] Total violations found: ${violations.length}`);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return violations;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
module.exports = C018RegexBasedAnalyzer;
|