@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
|
@@ -1,37 +1,125 @@
|
|
|
1
|
-
const
|
|
2
|
-
const
|
|
3
|
-
const ts = require('typescript');
|
|
4
|
-
const { PatternMatcher } = require('../../utils/pattern-matchers');
|
|
5
|
-
const { RuleHelper } = require('../../utils/rule-helpers');
|
|
1
|
+
const C019SystemLogAnalyzer = require('./system-log-analyzer.js');
|
|
2
|
+
const C019PatternAnalyzer = require('./pattern-analyzer.js');
|
|
6
3
|
|
|
7
4
|
class C019Analyzer {
|
|
8
|
-
constructor() {
|
|
5
|
+
constructor(semanticEngine = null) {
|
|
9
6
|
this.ruleId = 'C019';
|
|
10
7
|
this.ruleName = 'Log Level Usage';
|
|
11
|
-
this.description = '
|
|
8
|
+
this.description = 'Comprehensive logging analysis: levels, patterns, performance, and system requirements';
|
|
9
|
+
this.semanticEngine = semanticEngine;
|
|
10
|
+
this.verbose = false;
|
|
11
|
+
|
|
12
|
+
// Initialize analyzers - consolidated architecture
|
|
13
|
+
this.systemAnalyzer = new C019SystemLogAnalyzer(semanticEngine);
|
|
14
|
+
this.patternAnalyzer = new C019PatternAnalyzer();
|
|
12
15
|
this.aiAnalyzer = null;
|
|
13
16
|
}
|
|
14
17
|
|
|
15
|
-
async
|
|
16
|
-
|
|
18
|
+
async initialize(semanticEngine = null) {
|
|
19
|
+
if (semanticEngine) {
|
|
20
|
+
this.semanticEngine = semanticEngine;
|
|
21
|
+
}
|
|
22
|
+
this.verbose = semanticEngine?.verbose || false;
|
|
23
|
+
|
|
24
|
+
await this.systemAnalyzer.initialize(semanticEngine);
|
|
25
|
+
await this.patternAnalyzer.initialize({ verbose: this.verbose });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async analyzeFileBasic(filePath, options = {}) {
|
|
29
|
+
const allViolations = [];
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
// Run comprehensive system-level analysis (Primary - AST)
|
|
33
|
+
if (this.semanticEngine?.isSymbolEngineReady?.() && this.semanticEngine.project) {
|
|
34
|
+
if (this.verbose) {
|
|
35
|
+
console.log(`[DEBUG] 🎯 C019: Using comprehensive system-level analysis for ${filePath.split('/').pop()}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const systemViolations = await this.systemAnalyzer.analyzeFileBasic(filePath, options);
|
|
40
|
+
allViolations.push(...systemViolations);
|
|
41
|
+
|
|
42
|
+
if (this.verbose) {
|
|
43
|
+
console.log(`[DEBUG] 🎯 C019: System analysis found ${systemViolations.length} violations`);
|
|
44
|
+
}
|
|
45
|
+
} catch (systemError) {
|
|
46
|
+
if (this.verbose) {
|
|
47
|
+
console.warn(`[DEBUG] ⚠️ C019: System analysis failed: ${systemError.message}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (allViolations.length > 0) {
|
|
52
|
+
return this.deduplicateViolations(allViolations);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Fall back to pattern-based analysis (Secondary - Regex)
|
|
57
|
+
if (this.verbose) {
|
|
58
|
+
console.log(`[DEBUG] 🔄 C019: Running pattern-based analysis for ${filePath.split('/').pop()}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const patternViolations = await this.patternAnalyzer.analyzeFileBasic(filePath, options);
|
|
62
|
+
allViolations.push(...patternViolations);
|
|
63
|
+
|
|
64
|
+
if (this.verbose) {
|
|
65
|
+
console.log(`[DEBUG] 🔄 C019: Pattern analysis found ${patternViolations.length} violations`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return this.deduplicateViolations(allViolations);
|
|
69
|
+
|
|
70
|
+
} catch (error) {
|
|
71
|
+
if (this.verbose) {
|
|
72
|
+
console.error(`[DEBUG] ❌ C019: Analysis failed: ${error.message}`);
|
|
73
|
+
}
|
|
74
|
+
throw new Error(`C019 analysis failed: ${error.message}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
deduplicateViolations(violations) {
|
|
79
|
+
// Remove duplicates based on file, line, and type
|
|
80
|
+
const seen = new Set();
|
|
81
|
+
return violations.filter(violation => {
|
|
82
|
+
const key = `${violation.filePath}:${violation.line}:${violation.type}`;
|
|
83
|
+
if (seen.has(key)) return false;
|
|
84
|
+
seen.add(key);
|
|
85
|
+
return true;
|
|
86
|
+
});
|
|
87
|
+
}
|
|
17
88
|
|
|
89
|
+
async analyzeFiles(files, options = {}) {
|
|
90
|
+
const allViolations = [];
|
|
91
|
+
for (const filePath of files) {
|
|
92
|
+
try {
|
|
93
|
+
const violations = await this.analyzeFileBasic(filePath, options);
|
|
94
|
+
allViolations.push(...violations);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.warn(`C019: Skipping ${filePath}: ${error.message}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return allViolations;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Legacy method for backward compatibility
|
|
103
|
+
async analyze(files, language, config = {}) {
|
|
18
104
|
// Initialize AI analyzer if enabled
|
|
19
105
|
if (config.ai && config.ai.enabled) {
|
|
20
106
|
this.aiAnalyzer = new AIAnalyzer(config.ai);
|
|
21
107
|
console.log('🤖 AI analysis enabled for C019');
|
|
22
108
|
}
|
|
23
109
|
|
|
110
|
+
const allViolations = [];
|
|
111
|
+
|
|
24
112
|
for (const filePath of files) {
|
|
25
113
|
try {
|
|
26
|
-
const fileContent = fs.readFileSync(filePath, 'utf8');
|
|
114
|
+
const fileContent = require('fs').readFileSync(filePath, 'utf8');
|
|
27
115
|
const fileViolations = await this.analyzeFile(filePath, fileContent, language, config);
|
|
28
|
-
|
|
116
|
+
allViolations.push(...fileViolations);
|
|
29
117
|
} catch (error) {
|
|
30
118
|
console.error(`Error analyzing file ${filePath}:`, error.message);
|
|
31
119
|
}
|
|
32
120
|
}
|
|
33
121
|
|
|
34
|
-
return
|
|
122
|
+
return allViolations;
|
|
35
123
|
}
|
|
36
124
|
|
|
37
125
|
async analyzeFile(filePath, content, language, config) {
|
|
@@ -40,7 +128,7 @@ class C019Analyzer {
|
|
|
40
128
|
// Try AI analysis first if enabled
|
|
41
129
|
if (this.aiAnalyzer) {
|
|
42
130
|
try {
|
|
43
|
-
console.log(`🤖 Running AI analysis on ${path.basename(filePath)}`);
|
|
131
|
+
console.log(`🤖 Running AI analysis on ${require('path').basename(filePath)}`);
|
|
44
132
|
const aiViolations = await this.aiAnalyzer.analyzeWithAI(filePath, content, {
|
|
45
133
|
name: this.ruleName,
|
|
46
134
|
description: this.description,
|
|
@@ -48,315 +136,20 @@ class C019Analyzer {
|
|
|
48
136
|
});
|
|
49
137
|
|
|
50
138
|
if (aiViolations && aiViolations.length > 0) {
|
|
51
|
-
|
|
52
|
-
return
|
|
139
|
+
violations.push(...aiViolations);
|
|
140
|
+
return violations;
|
|
53
141
|
}
|
|
54
142
|
} catch (error) {
|
|
55
|
-
console.warn(
|
|
143
|
+
console.warn(`AI analysis failed for ${filePath}, falling back to heuristic analysis`);
|
|
56
144
|
}
|
|
57
145
|
}
|
|
58
|
-
|
|
59
|
-
// Fallback to pattern-based analysis
|
|
60
|
-
if (config.verbose) {
|
|
61
|
-
console.log(`🔍 Running pattern analysis on ${path.basename(filePath)}`);
|
|
62
|
-
}
|
|
63
|
-
switch (language) {
|
|
64
|
-
case 'typescript':
|
|
65
|
-
case 'javascript':
|
|
66
|
-
return this.analyzeTypeScript(filePath, content, config);
|
|
67
|
-
case 'dart':
|
|
68
|
-
return this.analyzeDart(filePath, content, config);
|
|
69
|
-
case 'kotlin':
|
|
70
|
-
return this.analyzeKotlin(filePath, content, config);
|
|
71
|
-
default:
|
|
72
|
-
return [];
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
async analyzeTypeScript(filePath, content, config) {
|
|
77
|
-
const violations = [];
|
|
78
|
-
const lines = content.split('\n');
|
|
79
|
-
|
|
80
|
-
// Parse TypeScript/JavaScript code
|
|
81
|
-
const sourceFile = ts.createSourceFile(
|
|
82
|
-
filePath,
|
|
83
|
-
content,
|
|
84
|
-
ts.ScriptTarget.Latest,
|
|
85
|
-
true
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
const visit = (node) => {
|
|
89
|
-
// Look for method calls that might be logging
|
|
90
|
-
if (ts.isCallExpression(node)) {
|
|
91
|
-
const callText = node.getText(sourceFile);
|
|
92
|
-
|
|
93
|
-
// Check if this is an error-level log call
|
|
94
|
-
const isErrorLog = this.isErrorLogCall(callText);
|
|
95
|
-
|
|
96
|
-
if (isErrorLog) {
|
|
97
|
-
const line = sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1;
|
|
98
|
-
const column = sourceFile.getLineAndCharacterOfPosition(node.getStart()).character + 1;
|
|
99
|
-
const lineText = lines[line - 1]?.trim() || '';
|
|
100
|
-
|
|
101
|
-
// Analyze the log message for inappropriate error usage
|
|
102
|
-
const logMessage = this.extractLogMessage(node, sourceFile);
|
|
103
|
-
const analysis = this.analyzeLogMessage(logMessage, callText);
|
|
104
|
-
|
|
105
|
-
if (analysis.isViolation) {
|
|
106
|
-
violations.push({
|
|
107
|
-
ruleId: this.ruleId,
|
|
108
|
-
file: filePath,
|
|
109
|
-
line,
|
|
110
|
-
column,
|
|
111
|
-
message: analysis.reason,
|
|
112
|
-
severity: analysis.severity || 'warning',
|
|
113
|
-
code: lineText,
|
|
114
|
-
type: analysis.type,
|
|
115
|
-
confidence: analysis.confidence || 0.8,
|
|
116
|
-
suggestion: analysis.suggestion
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
ts.forEachChild(node, visit);
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
visit(sourceFile);
|
|
126
|
-
return violations;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
async analyzeDart(filePath, content, config) {
|
|
130
|
-
const violations = [];
|
|
131
|
-
const lines = content.split('\n');
|
|
132
|
-
|
|
133
|
-
// Pattern-based analysis for Dart
|
|
134
|
-
const errorLogPatterns = [
|
|
135
|
-
/log\.error\(/g,
|
|
136
|
-
/logger\.error\(/g,
|
|
137
|
-
/Logger\.error\(/g,
|
|
138
|
-
/print\(.*error.*\)/gi,
|
|
139
|
-
/_logger\.error\(/g
|
|
140
|
-
];
|
|
141
|
-
|
|
142
|
-
lines.forEach((line, index) => {
|
|
143
|
-
const lineNumber = index + 1;
|
|
144
|
-
const trimmedLine = line.trim();
|
|
145
|
-
|
|
146
|
-
errorLogPatterns.forEach(pattern => {
|
|
147
|
-
const matches = trimmedLine.match(pattern);
|
|
148
|
-
if (matches) {
|
|
149
|
-
const analysis = this.analyzeLogMessage(trimmedLine, trimmedLine);
|
|
150
|
-
|
|
151
|
-
if (analysis.isViolation) {
|
|
152
|
-
violations.push({
|
|
153
|
-
ruleId: this.ruleId,
|
|
154
|
-
file: filePath,
|
|
155
|
-
line: lineNumber,
|
|
156
|
-
column: line.indexOf(matches[0]) + 1,
|
|
157
|
-
message: analysis.reason,
|
|
158
|
-
severity: analysis.severity || 'warning',
|
|
159
|
-
code: trimmedLine,
|
|
160
|
-
type: analysis.type,
|
|
161
|
-
confidence: analysis.confidence || 0.7,
|
|
162
|
-
suggestion: analysis.suggestion
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
});
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
return violations;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
async analyzeKotlin(filePath, content, config) {
|
|
173
|
-
const violations = [];
|
|
174
|
-
const lines = content.split('\n');
|
|
175
|
-
|
|
176
|
-
// Pattern-based analysis for Kotlin
|
|
177
|
-
const errorLogPatterns = [
|
|
178
|
-
/Log\.e\(/g,
|
|
179
|
-
/logger\.error\(/g,
|
|
180
|
-
/log\.error\(/g,
|
|
181
|
-
/Timber\.e\(/g
|
|
182
|
-
];
|
|
183
|
-
|
|
184
|
-
lines.forEach((line, index) => {
|
|
185
|
-
const lineNumber = index + 1;
|
|
186
|
-
const trimmedLine = line.trim();
|
|
187
|
-
|
|
188
|
-
errorLogPatterns.forEach(pattern => {
|
|
189
|
-
const matches = trimmedLine.match(pattern);
|
|
190
|
-
if (matches) {
|
|
191
|
-
const analysis = this.analyzeLogMessage(trimmedLine, trimmedLine);
|
|
192
|
-
|
|
193
|
-
if (analysis.isViolation) {
|
|
194
|
-
violations.push({
|
|
195
|
-
ruleId: this.ruleId,
|
|
196
|
-
file: filePath,
|
|
197
|
-
line: lineNumber,
|
|
198
|
-
column: line.indexOf(matches[0]) + 1,
|
|
199
|
-
message: analysis.reason,
|
|
200
|
-
severity: analysis.severity || 'warning',
|
|
201
|
-
code: trimmedLine,
|
|
202
|
-
type: analysis.type,
|
|
203
|
-
confidence: analysis.confidence || 0.7,
|
|
204
|
-
suggestion: analysis.suggestion
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
});
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
return violations;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
isErrorLogCall(callText) {
|
|
215
|
-
const errorLogPatterns = [
|
|
216
|
-
'console.error(',
|
|
217
|
-
'logger.error(',
|
|
218
|
-
'log.error(',
|
|
219
|
-
'.error(',
|
|
220
|
-
'Logger.error(',
|
|
221
|
-
'winston.error(',
|
|
222
|
-
'bunyan.error('
|
|
223
|
-
];
|
|
224
|
-
|
|
225
|
-
return errorLogPatterns.some(pattern => callText.includes(pattern));
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
extractLogMessage(callNode, sourceFile) {
|
|
229
|
-
// Try to extract the log message from the call expression
|
|
230
|
-
if (callNode.arguments && callNode.arguments.length > 0) {
|
|
231
|
-
const firstArg = callNode.arguments[0];
|
|
232
|
-
if (ts.isStringLiteral(firstArg) || ts.isTemplateExpression(firstArg)) {
|
|
233
|
-
return firstArg.getText(sourceFile).replace(/['"`]/g, '');
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
return '';
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
analyzeLogMessage(message, fullCall) {
|
|
240
|
-
const config = {
|
|
241
|
-
errorKeywords: [
|
|
242
|
-
'not found', 'invalid', 'unauthorized', 'forbidden',
|
|
243
|
-
'validation failed', 'bad request', 'cache miss',
|
|
244
|
-
'retry', 'fallback', 'user error', 'input error',
|
|
245
|
-
'validation', 'invalid input', 'missing parameter'
|
|
246
|
-
],
|
|
247
|
-
legitimateErrorKeywords: [
|
|
248
|
-
'exception', 'crash', 'fatal', 'critical', 'emergency',
|
|
249
|
-
'database', 'connection', 'timeout', 'security breach',
|
|
250
|
-
'system error', 'memory', 'disk space', 'internal server error',
|
|
251
|
-
'unhandled exception', 'stack overflow'
|
|
252
|
-
]
|
|
253
|
-
};
|
|
254
|
-
|
|
255
|
-
const lowerMessage = message.toLowerCase();
|
|
256
|
-
const lowerCall = fullCall.toLowerCase();
|
|
257
|
-
|
|
258
|
-
// Skip if this is in a catch block (legitimate error logging)
|
|
259
|
-
const isCatchBlockContext = this.isCatchBlockContext(fullCall);
|
|
260
|
-
if (isCatchBlockContext) {
|
|
261
|
-
return { isViolation: false };
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// Check for legitimate error usage
|
|
265
|
-
const hasLegitimateError = config.legitimateErrorKeywords.some(keyword =>
|
|
266
|
-
lowerMessage.includes(keyword) || lowerCall.includes(keyword)
|
|
267
|
-
);
|
|
268
|
-
|
|
269
|
-
if (hasLegitimateError) {
|
|
270
|
-
return { isViolation: false };
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// Check for inappropriate error usage
|
|
274
|
-
const hasInappropriateError = config.errorKeywords.some(keyword =>
|
|
275
|
-
lowerMessage.includes(keyword)
|
|
276
|
-
);
|
|
277
|
-
|
|
278
|
-
if (hasInappropriateError) {
|
|
279
|
-
return {
|
|
280
|
-
isViolation: true,
|
|
281
|
-
reason: 'Error log level used for non-critical issue - should use warn/info level',
|
|
282
|
-
severity: 'warning',
|
|
283
|
-
type: 'inappropriate_error_level',
|
|
284
|
-
confidence: 0.9,
|
|
285
|
-
suggestion: 'Consider using logger.warn() or logger.info() instead'
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// Validation patterns
|
|
290
|
-
if (lowerMessage.includes('validation') || lowerMessage.includes('invalid')) {
|
|
291
|
-
return {
|
|
292
|
-
isViolation: true,
|
|
293
|
-
reason: 'Validation errors should typically use warn level',
|
|
294
|
-
severity: 'warning',
|
|
295
|
-
type: 'validation_error_level',
|
|
296
|
-
confidence: 0.85,
|
|
297
|
-
suggestion: 'Use logger.warn() for validation failures'
|
|
298
|
-
};
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// User input patterns
|
|
302
|
-
if (lowerMessage.includes('user') && (lowerMessage.includes('input') || lowerMessage.includes('request'))) {
|
|
303
|
-
return {
|
|
304
|
-
isViolation: true,
|
|
305
|
-
reason: 'User input errors should not use error level',
|
|
306
|
-
severity: 'warning',
|
|
307
|
-
type: 'user_input_error_level',
|
|
308
|
-
confidence: 0.8,
|
|
309
|
-
suggestion: 'Use logger.warn() or logger.info() for user input issues'
|
|
310
|
-
};
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
// Authorization patterns
|
|
314
|
-
if (lowerMessage.includes('unauthorized') || lowerMessage.includes('forbidden')) {
|
|
315
|
-
return {
|
|
316
|
-
isViolation: true,
|
|
317
|
-
reason: 'Authorization failures are typically business logic, not system errors',
|
|
318
|
-
severity: 'info',
|
|
319
|
-
type: 'auth_error_level',
|
|
320
|
-
confidence: 0.7,
|
|
321
|
-
suggestion: 'Consider logger.warn() for authorization failures'
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// Generic error call without clear context
|
|
326
|
-
if (message === '' || message.length < 10) {
|
|
327
|
-
// Don't flag if it's clearly a system error with error object concatenation
|
|
328
|
-
if (fullCall.includes('+ error') || fullCall.includes('${error}') ||
|
|
329
|
-
fullCall.includes('+ e') || fullCall.includes('${e}') ||
|
|
330
|
-
fullCall.includes('error:') || fullCall.includes('failed:')) {
|
|
331
|
-
return { isViolation: false };
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
return {
|
|
335
|
-
isViolation: true,
|
|
336
|
-
reason: 'Generic error log without specific message - might be inappropriate',
|
|
337
|
-
severity: 'info',
|
|
338
|
-
type: 'generic_error_level',
|
|
339
|
-
confidence: 0.6,
|
|
340
|
-
suggestion: 'Add specific error message and verify if error level is appropriate'
|
|
341
|
-
};
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
return { isViolation: false };
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
isCatchBlockContext(callText) {
|
|
348
|
-
// Simple heuristic: if the call contains error/e parameters common in catch blocks
|
|
349
|
-
const catchPatterns = [
|
|
350
|
-
'console.error(error)',
|
|
351
|
-
'console.error(e)',
|
|
352
|
-
'logger.error(error)',
|
|
353
|
-
'logger.error(e)',
|
|
354
|
-
'console.error("', // followed by message and error
|
|
355
|
-
'logger.error("'
|
|
356
|
-
];
|
|
357
146
|
|
|
358
|
-
|
|
147
|
+
// Use the new analyzer architecture
|
|
148
|
+
const heuristicViolations = await this.analyzeFileBasic(filePath, config);
|
|
149
|
+
violations.push(...heuristicViolations);
|
|
150
|
+
|
|
151
|
+
return violations;
|
|
359
152
|
}
|
|
360
153
|
}
|
|
361
154
|
|
|
362
|
-
module.exports =
|
|
155
|
+
module.exports = C019Analyzer;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// Pattern-based fallback analyzer for C019
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
|
|
4
|
+
class C019PatternAnalyzer {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.verbose = false;
|
|
7
|
+
|
|
8
|
+
// Configuration for pattern matching
|
|
9
|
+
this.config = {
|
|
10
|
+
// Patterns that suggest inappropriate ERROR usage
|
|
11
|
+
inappropriateErrorPatterns: [
|
|
12
|
+
// Business logic rejections
|
|
13
|
+
'validation.*(?:failed|error)', 'invalid.*(?:input|parameter|format)',
|
|
14
|
+
'user.*(?:not.*found|unauthorized|forbidden)', 'permission.*denied',
|
|
15
|
+
'authentication.*(?:failed|invalid)', 'authorization.*failed',
|
|
16
|
+
|
|
17
|
+
// Client-side errors
|
|
18
|
+
'bad.*request', 'missing.*(?:parameter|field)', 'invalid.*request',
|
|
19
|
+
'malformed.*(?:json|xml|payload)', 'unsupported.*(?:format|type)',
|
|
20
|
+
|
|
21
|
+
// Recoverable operations
|
|
22
|
+
'retry.*(?:attempt|failed)', 'fallback.*(?:triggered|used)',
|
|
23
|
+
'cache.*(?:miss|expired)', 'rate.*limit.*exceeded',
|
|
24
|
+
|
|
25
|
+
// Expected business flows
|
|
26
|
+
'user.*not.*found', 'resource.*not.*found', 'item.*not.*available',
|
|
27
|
+
'quota.*exceeded', 'limit.*reached', 'threshold.*exceeded'
|
|
28
|
+
]
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async initialize(options = {}) {
|
|
33
|
+
this.verbose = options.verbose || false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async analyzeFileBasic(filePath, options = {}) {
|
|
37
|
+
const violations = [];
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
41
|
+
const lines = content.split('\n');
|
|
42
|
+
|
|
43
|
+
if (this.verbose) {
|
|
44
|
+
console.log(`[DEBUG] 🔍 C019: Pattern-based analysis of ${filePath.split('/').pop()}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
for (let i = 0; i < lines.length; i++) {
|
|
48
|
+
const line = lines[i];
|
|
49
|
+
const lineNumber = i + 1;
|
|
50
|
+
|
|
51
|
+
// Simple pattern matching for error logs
|
|
52
|
+
const errorLogMatch = line.match(/(console|logger|log|winston|bunyan)\.error\(/i);
|
|
53
|
+
if (errorLogMatch) {
|
|
54
|
+
// Check for inappropriate patterns in the line
|
|
55
|
+
const hasInappropriatePattern = this.config.inappropriateErrorPatterns.some(pattern =>
|
|
56
|
+
new RegExp(pattern, 'i').test(line)
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
if (hasInappropriatePattern) {
|
|
60
|
+
violations.push({
|
|
61
|
+
ruleId: 'C019',
|
|
62
|
+
message: 'Log level "error" may be inappropriate for this context. Consider using "warn" or "info".',
|
|
63
|
+
filePath: filePath,
|
|
64
|
+
line: lineNumber,
|
|
65
|
+
column: errorLogMatch.index + 1,
|
|
66
|
+
severity: 'warning',
|
|
67
|
+
category: 'logging',
|
|
68
|
+
confidence: 0.5,
|
|
69
|
+
suggestion: 'Review the error context and use appropriate log level (warn/info for expected errors)'
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (this.verbose) {
|
|
76
|
+
console.log(`[DEBUG] 🔍 C019: Pattern analysis found ${violations.length} violations`);
|
|
77
|
+
}
|
|
78
|
+
} catch (error) {
|
|
79
|
+
if (this.verbose) {
|
|
80
|
+
console.error(`[DEBUG] ❌ C019: Pattern analysis error: ${error.message}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return violations;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
module.exports = C019PatternAnalyzer;
|