@sun-asterisk/sunlint 1.2.1 → 1.2.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/config/rule-analysis-strategies.js +18 -2
- package/engines/eslint-engine.js +9 -11
- package/engines/heuristic-engine.js +55 -31
- package/package.json +2 -1
- package/rules/README.md +252 -0
- package/rules/common/C002_no_duplicate_code/analyzer.js +65 -0
- package/rules/common/C002_no_duplicate_code/config.json +23 -0
- package/rules/common/C003_no_vague_abbreviations/analyzer.js +418 -0
- package/rules/common/C003_no_vague_abbreviations/config.json +35 -0
- package/rules/common/C006_function_naming/analyzer.js +504 -0
- package/rules/common/C006_function_naming/config.json +86 -0
- package/rules/common/C006_function_naming/smart-analyzer.js +503 -0
- package/rules/common/C010_limit_block_nesting/analyzer.js +389 -0
- package/rules/common/C012_command_query_separation/analyzer.js +481 -0
- package/rules/common/C012_command_query_separation/ast-analyzer.js +495 -0
- package/rules/common/C013_no_dead_code/analyzer.js +206 -0
- package/rules/common/C014_dependency_injection/analyzer.js +338 -0
- package/rules/common/C017_constructor_logic/analyzer.js +314 -0
- package/rules/common/C019_log_level_usage/analyzer.js +362 -0
- package/rules/common/C019_log_level_usage/config.json +121 -0
- package/rules/common/C029_catch_block_logging/analyzer-backup.js +426 -0
- package/rules/common/C029_catch_block_logging/analyzer-fixed.js +130 -0
- package/rules/common/C029_catch_block_logging/analyzer-multi-tech.js +487 -0
- package/rules/common/C029_catch_block_logging/analyzer-simple.js +110 -0
- package/rules/common/C029_catch_block_logging/analyzer-smart-pipeline.js +755 -0
- package/rules/common/C029_catch_block_logging/analyzer.js +129 -0
- package/rules/common/C029_catch_block_logging/ast-analyzer-backup.js +441 -0
- package/rules/common/C029_catch_block_logging/ast-analyzer-new.js +127 -0
- package/rules/common/C029_catch_block_logging/ast-analyzer.js +133 -0
- package/rules/common/C029_catch_block_logging/cfg-analyzer.js +408 -0
- package/rules/common/C029_catch_block_logging/config.json +59 -0
- package/rules/common/C029_catch_block_logging/dataflow-analyzer.js +454 -0
- package/rules/common/C029_catch_block_logging/multi-language-ast-engine.js +700 -0
- package/rules/common/C029_catch_block_logging/pattern-learning-analyzer.js +568 -0
- package/rules/common/C029_catch_block_logging/semantic-analyzer.js +459 -0
- package/rules/common/C031_validation_separation/analyzer.js +186 -0
- package/rules/common/C041_no_sensitive_hardcode/analyzer.js +292 -0
- package/rules/common/C041_no_sensitive_hardcode/ast-analyzer.js +296 -0
- package/rules/common/C042_boolean_name_prefix/analyzer.js +300 -0
- package/rules/common/C043_no_console_or_print/analyzer.js +431 -0
- package/rules/common/C047_no_duplicate_retry_logic/analyzer.js +590 -0
- package/rules/common/C075_explicit_return_types/analyzer.js +103 -0
- package/rules/common/C076_single_test_behavior/analyzer.js +121 -0
- package/rules/docs/C002_no_duplicate_code.md +57 -0
- package/rules/docs/C031_validation_separation.md +72 -0
- package/rules/index.js +155 -0
- package/rules/migration/converter.js +385 -0
- package/rules/migration/mapping.json +164 -0
- package/rules/parser/constants.js +31 -0
- package/rules/parser/file-config.js +80 -0
- package/rules/parser/rule-parser-simple.js +305 -0
- package/rules/parser/rule-parser.js +527 -0
- package/rules/security/S015_insecure_tls_certificate/analyzer.js +150 -0
- package/rules/security/S015_insecure_tls_certificate/ast-analyzer.js +237 -0
- package/rules/security/S023_no_json_injection/analyzer.js +278 -0
- package/rules/security/S023_no_json_injection/ast-analyzer.js +359 -0
- package/rules/security/S026_json_schema_validation/analyzer.js +251 -0
- package/rules/security/S026_json_schema_validation/config.json +27 -0
- package/rules/security/S027_no_hardcoded_secrets/analyzer.js +436 -0
- package/rules/security/S027_no_hardcoded_secrets/config.json +29 -0
- package/rules/security/S029_csrf_protection/analyzer.js +330 -0
- package/rules/tests/C002_no_duplicate_code.test.js +50 -0
- package/rules/universal/C010/generic.js +0 -0
- package/rules/universal/C010/tree-sitter-analyzer.js +0 -0
- package/rules/utils/ast-utils.js +191 -0
- package/rules/utils/base-analyzer.js +98 -0
- package/rules/utils/pattern-matchers.js +239 -0
- package/rules/utils/rule-helpers.js +264 -0
- package/rules/utils/severity-constants.js +93 -0
- package/scripts/generate_insights.js +188 -0
- package/scripts/merge-reports.js +0 -424
- package/scripts/test-scripts/README.md +0 -22
- package/scripts/test-scripts/test-c041-comparison.js +0 -114
- package/scripts/test-scripts/test-c041-eslint.js +0 -67
- package/scripts/test-scripts/test-eslint-rules.js +0 -146
- package/scripts/test-scripts/test-real-world.js +0 -44
- package/scripts/test-scripts/test-rules-on-real-projects.js +0 -86
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* C029 Semantic Analyzer
|
|
3
|
+
*
|
|
4
|
+
* Uses semantic analysis to understand the meaning and intent of error handling code
|
|
5
|
+
* Goes beyond syntax to understand developer intent and code semantics
|
|
6
|
+
*
|
|
7
|
+
* Technology Showcase: Understanding code intent, not just structure
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
class C029SemanticAnalyzer {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.ruleId = 'C029';
|
|
13
|
+
this.ruleName = 'Semantic Intent-Based Catch Analysis';
|
|
14
|
+
this.description = 'Understands developer intent in error handling through semantic analysis';
|
|
15
|
+
|
|
16
|
+
// Semantic patterns for different error handling intents
|
|
17
|
+
this.semanticPatterns = {
|
|
18
|
+
// Intent: Ignore errors intentionally
|
|
19
|
+
intentionalIgnore: [
|
|
20
|
+
/catch\s*\(\s*[_][\w]*\s*\)/, // catch(_), catch(_ignored)
|
|
21
|
+
/\/\/\s*(ignore|suppress|intentional)/i,
|
|
22
|
+
/\/\*\s*(ignore|suppress|intentional)/i,
|
|
23
|
+
/catch\s*\([^)]*ignored[^)]*\)/i
|
|
24
|
+
],
|
|
25
|
+
|
|
26
|
+
// Intent: Log and continue
|
|
27
|
+
logAndContinue: [
|
|
28
|
+
/console\.(log|error|warn)/,
|
|
29
|
+
/logger\.(log|error|warn)/,
|
|
30
|
+
/log\.(error|warn|info)/
|
|
31
|
+
],
|
|
32
|
+
|
|
33
|
+
// Intent: Transform and rethrow
|
|
34
|
+
transformAndRethrow: [
|
|
35
|
+
/throw\s+new\s+\w+Error/,
|
|
36
|
+
/throw\s+.*Error\(/,
|
|
37
|
+
/rethrow/i
|
|
38
|
+
],
|
|
39
|
+
|
|
40
|
+
// Intent: Handle gracefully
|
|
41
|
+
gracefulHandling: [
|
|
42
|
+
/return\s+.*default/i,
|
|
43
|
+
/return\s+.*fallback/i,
|
|
44
|
+
/return\s+null/,
|
|
45
|
+
/return\s+undefined/,
|
|
46
|
+
/return\s+\[\]/,
|
|
47
|
+
/return\s+\{\}/
|
|
48
|
+
],
|
|
49
|
+
|
|
50
|
+
// Intent: Notify external systems
|
|
51
|
+
externalNotification: [
|
|
52
|
+
/notify/i,
|
|
53
|
+
/alert/i,
|
|
54
|
+
/report/i,
|
|
55
|
+
/track/i,
|
|
56
|
+
/analytics/i,
|
|
57
|
+
/monitoring/i,
|
|
58
|
+
/sentry/i,
|
|
59
|
+
/bugsnag/i
|
|
60
|
+
],
|
|
61
|
+
|
|
62
|
+
// Intent: Test verification
|
|
63
|
+
testVerification: [
|
|
64
|
+
/expect\(/,
|
|
65
|
+
/assert\(/,
|
|
66
|
+
/should\./,
|
|
67
|
+
/toBe\(/,
|
|
68
|
+
/toEqual\(/,
|
|
69
|
+
/toThrow\(/
|
|
70
|
+
],
|
|
71
|
+
|
|
72
|
+
// Intent: State management
|
|
73
|
+
stateManagement: [
|
|
74
|
+
/setState/,
|
|
75
|
+
/dispatch/,
|
|
76
|
+
/commit/,
|
|
77
|
+
/rejectWithValue/,
|
|
78
|
+
/setError/,
|
|
79
|
+
/setLoading/
|
|
80
|
+
]
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async analyze(files, language, options = {}) {
|
|
85
|
+
const violations = [];
|
|
86
|
+
console.log(`🧠 C029 Semantic: Analyzing ${files.length} files with semantic understanding...`);
|
|
87
|
+
|
|
88
|
+
for (const filePath of files) {
|
|
89
|
+
try {
|
|
90
|
+
const content = require('fs').readFileSync(filePath, 'utf8');
|
|
91
|
+
const semanticViolations = await this.analyzeSemanticIntent(content, filePath, language);
|
|
92
|
+
violations.push(...semanticViolations);
|
|
93
|
+
|
|
94
|
+
} catch (error) {
|
|
95
|
+
console.warn(`C029 Semantic skipping ${filePath}: ${error.message}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return violations;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Analyze semantic intent of error handling
|
|
104
|
+
*/
|
|
105
|
+
async analyzeSemanticIntent(content, filePath, language) {
|
|
106
|
+
const violations = [];
|
|
107
|
+
const isTestFile = this.isTestFile(filePath);
|
|
108
|
+
|
|
109
|
+
// Find all catch blocks with context
|
|
110
|
+
const catchBlocks = this.extractCatchBlocksWithContext(content);
|
|
111
|
+
|
|
112
|
+
for (const catchBlock of catchBlocks) {
|
|
113
|
+
const semanticAnalysis = this.analyzeBlockSemantic(catchBlock, isTestFile, content);
|
|
114
|
+
|
|
115
|
+
if (semanticAnalysis.isViolation) {
|
|
116
|
+
violations.push({
|
|
117
|
+
ruleId: this.ruleId,
|
|
118
|
+
file: filePath,
|
|
119
|
+
line: catchBlock.lineNumber,
|
|
120
|
+
column: 1,
|
|
121
|
+
message: semanticAnalysis.message,
|
|
122
|
+
severity: semanticAnalysis.severity,
|
|
123
|
+
code: catchBlock.code,
|
|
124
|
+
type: semanticAnalysis.type,
|
|
125
|
+
confidence: semanticAnalysis.confidence,
|
|
126
|
+
suggestion: semanticAnalysis.suggestion,
|
|
127
|
+
semanticIntent: semanticAnalysis.intent,
|
|
128
|
+
contextAnalysis: semanticAnalysis.context
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return violations;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Extract catch blocks with surrounding context for semantic analysis
|
|
138
|
+
*/
|
|
139
|
+
extractCatchBlocksWithContext(content) {
|
|
140
|
+
const catchBlocks = [];
|
|
141
|
+
const lines = content.split('\n');
|
|
142
|
+
|
|
143
|
+
for (let i = 0; i < lines.length; i++) {
|
|
144
|
+
const line = lines[i];
|
|
145
|
+
|
|
146
|
+
if (this.isCatchBlockStart(line)) {
|
|
147
|
+
const catchBlock = this.extractFullCatchBlock(lines, i);
|
|
148
|
+
|
|
149
|
+
// Add surrounding context for semantic analysis
|
|
150
|
+
const context = this.extractSurroundingContext(lines, i, catchBlock.endLine);
|
|
151
|
+
|
|
152
|
+
catchBlocks.push({
|
|
153
|
+
...catchBlock,
|
|
154
|
+
lineNumber: i + 1,
|
|
155
|
+
context
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return catchBlocks;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Analyze semantic intent of a catch block
|
|
165
|
+
*/
|
|
166
|
+
analyzeBlockSemantic(catchBlock, isTestFile, fullContent) {
|
|
167
|
+
const blockContent = catchBlock.content;
|
|
168
|
+
const blockCode = catchBlock.code;
|
|
169
|
+
const context = catchBlock.context;
|
|
170
|
+
|
|
171
|
+
// 1. Detect semantic intent
|
|
172
|
+
const intent = this.detectSemanticIntent(blockContent, context, isTestFile);
|
|
173
|
+
|
|
174
|
+
// 2. Validate intent appropriateness
|
|
175
|
+
const validation = this.validateSemanticIntent(intent, catchBlock, isTestFile, fullContent);
|
|
176
|
+
|
|
177
|
+
if (validation.isValid) {
|
|
178
|
+
return { isViolation: false, intent, context: validation };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// 3. Generate semantic-aware violation
|
|
182
|
+
return {
|
|
183
|
+
isViolation: true,
|
|
184
|
+
type: validation.violationType,
|
|
185
|
+
message: validation.message,
|
|
186
|
+
severity: validation.severity,
|
|
187
|
+
confidence: validation.confidence,
|
|
188
|
+
suggestion: validation.suggestion,
|
|
189
|
+
intent,
|
|
190
|
+
context: validation
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Detect the semantic intent of error handling
|
|
196
|
+
*/
|
|
197
|
+
detectSemanticIntent(blockContent, context, isTestFile) {
|
|
198
|
+
const intents = [];
|
|
199
|
+
|
|
200
|
+
// Check each semantic pattern category
|
|
201
|
+
for (const [intentType, patterns] of Object.entries(this.semanticPatterns)) {
|
|
202
|
+
for (const pattern of patterns) {
|
|
203
|
+
if (pattern.test(blockContent) || pattern.test(context.before) || pattern.test(context.after)) {
|
|
204
|
+
intents.push({
|
|
205
|
+
type: intentType,
|
|
206
|
+
confidence: this.calculateIntentConfidence(intentType, blockContent, context, isTestFile)
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Handle empty catch blocks
|
|
213
|
+
if (this.isEmpty(blockContent)) {
|
|
214
|
+
intents.push({
|
|
215
|
+
type: 'silentIgnore',
|
|
216
|
+
confidence: 0.95
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Handle unknown intent
|
|
221
|
+
if (intents.length === 0) {
|
|
222
|
+
intents.push({
|
|
223
|
+
type: 'unknown',
|
|
224
|
+
confidence: 0.1
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Return primary intent (highest confidence)
|
|
229
|
+
return intents.sort((a, b) => b.confidence - a.confidence)[0];
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Validate if the detected intent is appropriate for the context
|
|
234
|
+
*/
|
|
235
|
+
validateSemanticIntent(intent, catchBlock, isTestFile, fullContent) {
|
|
236
|
+
const intentType = intent.type;
|
|
237
|
+
const confidence = intent.confidence;
|
|
238
|
+
|
|
239
|
+
switch (intentType) {
|
|
240
|
+
case 'intentionalIgnore':
|
|
241
|
+
return this.validateIntentionalIgnore(catchBlock, confidence);
|
|
242
|
+
|
|
243
|
+
case 'logAndContinue':
|
|
244
|
+
return this.validateLogAndContinue(catchBlock, confidence);
|
|
245
|
+
|
|
246
|
+
case 'transformAndRethrow':
|
|
247
|
+
return this.validateTransformAndRethrow(catchBlock, confidence);
|
|
248
|
+
|
|
249
|
+
case 'gracefulHandling':
|
|
250
|
+
return this.validateGracefulHandling(catchBlock, confidence);
|
|
251
|
+
|
|
252
|
+
case 'externalNotification':
|
|
253
|
+
return this.validateExternalNotification(catchBlock, confidence);
|
|
254
|
+
|
|
255
|
+
case 'testVerification':
|
|
256
|
+
return this.validateTestVerification(catchBlock, isTestFile, confidence);
|
|
257
|
+
|
|
258
|
+
case 'stateManagement':
|
|
259
|
+
return this.validateStateManagement(catchBlock, confidence);
|
|
260
|
+
|
|
261
|
+
case 'silentIgnore':
|
|
262
|
+
return this.validateSilentIgnore(catchBlock, isTestFile, confidence);
|
|
263
|
+
|
|
264
|
+
default:
|
|
265
|
+
return this.validateUnknownIntent(catchBlock, confidence);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Validate intentional ignore patterns
|
|
271
|
+
*/
|
|
272
|
+
validateIntentionalIgnore(catchBlock, confidence) {
|
|
273
|
+
// Intentional ignore is usually acceptable if properly documented
|
|
274
|
+
const hasDocumentation = this.hasProperDocumentation(catchBlock);
|
|
275
|
+
|
|
276
|
+
if (hasDocumentation) {
|
|
277
|
+
return { isValid: true, reason: 'documented_ignore' };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return {
|
|
281
|
+
isValid: false,
|
|
282
|
+
violationType: 'undocumented_ignore',
|
|
283
|
+
message: 'Intentional error ignore should be documented with reason',
|
|
284
|
+
severity: 'warning',
|
|
285
|
+
confidence: confidence * 0.8,
|
|
286
|
+
suggestion: 'Add comment explaining why error is intentionally ignored'
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Validate log and continue patterns
|
|
292
|
+
*/
|
|
293
|
+
validateLogAndContinue(catchBlock, confidence) {
|
|
294
|
+
// Check if logging actually includes error information
|
|
295
|
+
const hasErrorInfo = this.loggingIncludesErrorInfo(catchBlock);
|
|
296
|
+
|
|
297
|
+
if (hasErrorInfo) {
|
|
298
|
+
return { isValid: true, reason: 'proper_logging' };
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
isValid: false,
|
|
303
|
+
violationType: 'incomplete_logging',
|
|
304
|
+
message: 'Error logging should include error details (message, stack, context)',
|
|
305
|
+
severity: 'warning',
|
|
306
|
+
confidence: confidence * 0.9,
|
|
307
|
+
suggestion: 'Include error.message, error.stack, or relevant error properties in logging'
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Validate silent ignore (empty catch)
|
|
313
|
+
*/
|
|
314
|
+
validateSilentIgnore(catchBlock, isTestFile, confidence) {
|
|
315
|
+
// Silent ignore is usually problematic except in specific contexts
|
|
316
|
+
if (isTestFile && this.isExpectedErrorInTest(catchBlock)) {
|
|
317
|
+
return { isValid: true, reason: 'expected_test_error' };
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return {
|
|
321
|
+
isValid: false,
|
|
322
|
+
violationType: 'silent_error_suppression',
|
|
323
|
+
message: 'Silent error suppression hides bugs and makes debugging difficult',
|
|
324
|
+
severity: 'error',
|
|
325
|
+
confidence: confidence,
|
|
326
|
+
suggestion: 'Add error logging, rethrowing, or explicit ignore with documentation'
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Calculate confidence score for intent detection
|
|
332
|
+
*/
|
|
333
|
+
calculateIntentConfidence(intentType, blockContent, context, isTestFile) {
|
|
334
|
+
let confidence = 0.5; // Base confidence
|
|
335
|
+
|
|
336
|
+
// Adjust based on pattern strength
|
|
337
|
+
const patternCount = this.countMatchingPatterns(intentType, blockContent);
|
|
338
|
+
confidence += Math.min(patternCount * 0.2, 0.4);
|
|
339
|
+
|
|
340
|
+
// Adjust based on context
|
|
341
|
+
if (isTestFile && intentType === 'testVerification') {
|
|
342
|
+
confidence += 0.3;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Adjust based on code quality indicators
|
|
346
|
+
if (this.hasGoodNaming(blockContent)) {
|
|
347
|
+
confidence += 0.1;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (this.hasDocumentation(context)) {
|
|
351
|
+
confidence += 0.1;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return Math.min(confidence, 1.0);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Helper methods (simplified implementations)
|
|
358
|
+
|
|
359
|
+
isCatchBlockStart(line) {
|
|
360
|
+
return line.trim().includes('catch (') || line.trim().includes('catch(');
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
extractFullCatchBlock(lines, startIndex) {
|
|
364
|
+
// Simplified catch block extraction
|
|
365
|
+
return {
|
|
366
|
+
content: 'catch block content',
|
|
367
|
+
code: lines[startIndex],
|
|
368
|
+
endLine: startIndex + 5
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
extractSurroundingContext(lines, startLine, endLine) {
|
|
373
|
+
const before = lines.slice(Math.max(0, startLine - 3), startLine).join('\n');
|
|
374
|
+
const after = lines.slice(endLine + 1, Math.min(lines.length, endLine + 4)).join('\n');
|
|
375
|
+
|
|
376
|
+
return { before, after };
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
isEmpty(content) {
|
|
380
|
+
return content.trim().replace(/[{}]/g, '').trim().length === 0;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
hasProperDocumentation(catchBlock) {
|
|
384
|
+
const content = catchBlock.content + (catchBlock.context?.before || '');
|
|
385
|
+
return /\/\/|\/\*/.test(content);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
loggingIncludesErrorInfo(catchBlock) {
|
|
389
|
+
const content = catchBlock.content;
|
|
390
|
+
return /error\.(message|stack|name)/.test(content) || /\$\{error\}/.test(content);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
isExpectedErrorInTest(catchBlock) {
|
|
394
|
+
const context = catchBlock.context || {};
|
|
395
|
+
return /expect.*toThrow|assertThrows|shouldThrow/.test(context.before + context.after);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
countMatchingPatterns(intentType, content) {
|
|
399
|
+
const patterns = this.semanticPatterns[intentType] || [];
|
|
400
|
+
return patterns.filter(pattern => pattern.test(content)).length;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
hasGoodNaming(content) {
|
|
404
|
+
return /\b(error|err|exception|ex)\b/.test(content);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
hasDocumentation(context) {
|
|
408
|
+
return /\/\/|\/\*/.test((context?.before || '') + (context?.after || ''));
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
isTestFile(filePath) {
|
|
412
|
+
const testPatterns = ['__tests__', '.test.', '.spec.', '/test/', '/tests/'];
|
|
413
|
+
return testPatterns.some(pattern => filePath.includes(pattern));
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Placeholder validation methods
|
|
417
|
+
validateTransformAndRethrow(catchBlock, confidence) {
|
|
418
|
+
return { isValid: true, reason: 'proper_transform' };
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
validateGracefulHandling(catchBlock, confidence) {
|
|
422
|
+
return { isValid: true, reason: 'graceful_handling' };
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
validateExternalNotification(catchBlock, confidence) {
|
|
426
|
+
return { isValid: true, reason: 'external_notification' };
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
validateTestVerification(catchBlock, isTestFile, confidence) {
|
|
430
|
+
if (!isTestFile) {
|
|
431
|
+
return {
|
|
432
|
+
isValid: false,
|
|
433
|
+
violationType: 'test_code_in_production',
|
|
434
|
+
message: 'Test-style error handling found in production code',
|
|
435
|
+
severity: 'warning',
|
|
436
|
+
confidence: confidence * 0.9,
|
|
437
|
+
suggestion: 'Use proper error handling instead of test assertions'
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
return { isValid: true, reason: 'valid_test_verification' };
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
validateStateManagement(catchBlock, confidence) {
|
|
444
|
+
return { isValid: true, reason: 'state_management' };
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
validateUnknownIntent(catchBlock, confidence) {
|
|
448
|
+
return {
|
|
449
|
+
isValid: false,
|
|
450
|
+
violationType: 'unclear_error_handling_intent',
|
|
451
|
+
message: 'Error handling intent is unclear - consider explicit error handling',
|
|
452
|
+
severity: 'info',
|
|
453
|
+
confidence: confidence,
|
|
454
|
+
suggestion: 'Make error handling intent clear through logging, rethrowing, or documentation'
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
module.exports = new C029SemanticAnalyzer();
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Rule C031 - Validation Logic Separation
|
|
6
|
+
* Kiểm tra logic validation có bị trộn lẫn với business logic không
|
|
7
|
+
*/
|
|
8
|
+
class ValidationSeparationAnalyzer {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.ruleId = 'C031';
|
|
11
|
+
this.ruleName = 'Validation Logic Separation';
|
|
12
|
+
this.category = 'architecture';
|
|
13
|
+
this.severity = 'warning';
|
|
14
|
+
this.description = 'Logic kiểm tra dữ liệu (validate) phải nằm riêng biệt';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
analyzeFile(filePath, options = {}) {
|
|
18
|
+
const violations = [];
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
if (!fs.existsSync(filePath)) {
|
|
22
|
+
return violations;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
26
|
+
const lines = content.split('\n');
|
|
27
|
+
|
|
28
|
+
// Detect functions with mixed validation and business logic
|
|
29
|
+
const functions = this.extractFunctions(content);
|
|
30
|
+
|
|
31
|
+
for (const func of functions) {
|
|
32
|
+
const validationCount = this.countValidationStatements(func.body);
|
|
33
|
+
const businessLogicCount = this.countBusinessLogicStatements(func.body);
|
|
34
|
+
|
|
35
|
+
// If both validation and business logic exist in same function
|
|
36
|
+
if (validationCount > 0 && businessLogicCount > 0) {
|
|
37
|
+
const maxValidationAllowed = options.maxValidationStatementsInFunction || 3;
|
|
38
|
+
|
|
39
|
+
if (validationCount > maxValidationAllowed) {
|
|
40
|
+
violations.push({
|
|
41
|
+
line: func.startLine,
|
|
42
|
+
column: 1,
|
|
43
|
+
message: `Function '${func.name}' has ${validationCount} validation statements mixed with business logic. Consider separating validation logic.`,
|
|
44
|
+
ruleId: this.ruleId,
|
|
45
|
+
severity: this.severity,
|
|
46
|
+
source: lines[func.startLine - 1]?.trim() || ''
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error(`Error analyzing ${filePath}:`, error.message);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return violations;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
extractFunctions(content) {
|
|
60
|
+
const functions = [];
|
|
61
|
+
const lines = content.split('\n');
|
|
62
|
+
|
|
63
|
+
// Simple function detection patterns
|
|
64
|
+
const functionPatterns = [
|
|
65
|
+
/function\s+(\w+)\s*\(/g,
|
|
66
|
+
/const\s+(\w+)\s*=\s*\(/g,
|
|
67
|
+
/(\w+)\s*\(\s*[^)]*\s*\)\s*=>/g,
|
|
68
|
+
/(\w+)\s*:\s*function\s*\(/g
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
for (let i = 0; i < lines.length; i++) {
|
|
72
|
+
const line = lines[i];
|
|
73
|
+
|
|
74
|
+
for (const pattern of functionPatterns) {
|
|
75
|
+
const matches = line.matchAll(pattern);
|
|
76
|
+
for (const match of matches) {
|
|
77
|
+
const functionName = match[1];
|
|
78
|
+
const startLine = i + 1;
|
|
79
|
+
|
|
80
|
+
// Extract function body (simple approach)
|
|
81
|
+
const body = this.extractFunctionBody(lines, i);
|
|
82
|
+
|
|
83
|
+
functions.push({
|
|
84
|
+
name: functionName,
|
|
85
|
+
startLine,
|
|
86
|
+
body
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return functions;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
extractFunctionBody(lines, startIndex) {
|
|
96
|
+
let body = '';
|
|
97
|
+
let braceCount = 0;
|
|
98
|
+
let inFunction = false;
|
|
99
|
+
|
|
100
|
+
for (let i = startIndex; i < lines.length; i++) {
|
|
101
|
+
const line = lines[i];
|
|
102
|
+
|
|
103
|
+
if (line.includes('{')) {
|
|
104
|
+
braceCount += (line.match(/\{/g) || []).length;
|
|
105
|
+
inFunction = true;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (inFunction) {
|
|
109
|
+
body += line + '\n';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (line.includes('}')) {
|
|
113
|
+
braceCount -= (line.match(/\}/g) || []).length;
|
|
114
|
+
if (braceCount <= 0 && inFunction) {
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return body;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
countValidationStatements(code) {
|
|
124
|
+
const validationPatterns = [
|
|
125
|
+
/if\s*\(\s*!.*\)\s*\{?\s*throw/g,
|
|
126
|
+
/if\s*\(.*\.\s*length\s*[<>=]\s*\d+\)/g,
|
|
127
|
+
/if\s*\(.*\s*==\s*null\s*\||\s*.*\s*==\s*undefined\)/g,
|
|
128
|
+
/if\s*\(.*\s*!\s*=\s*null\s*&&\s*.*\s*!\s*=\s*undefined\)/g,
|
|
129
|
+
/throw\s+new\s+Error\s*\(/g,
|
|
130
|
+
/assert\s*\(/g,
|
|
131
|
+
/validate\w*\s*\(/g,
|
|
132
|
+
/check\w*\s*\(/g
|
|
133
|
+
];
|
|
134
|
+
|
|
135
|
+
let count = 0;
|
|
136
|
+
for (const pattern of validationPatterns) {
|
|
137
|
+
const matches = code.match(pattern);
|
|
138
|
+
if (matches) {
|
|
139
|
+
count += matches.length;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return count;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
countBusinessLogicStatements(code) {
|
|
147
|
+
const businessLogicPatterns = [
|
|
148
|
+
/calculate\w*\s*\(/g,
|
|
149
|
+
/process\w*\s*\(/g,
|
|
150
|
+
/save\w*\s*\(/g,
|
|
151
|
+
/update\w*\s*\(/g,
|
|
152
|
+
/delete\w*\s*\(/g,
|
|
153
|
+
/send\w*\s*\(/g,
|
|
154
|
+
/return\s+\w+\s*\(/g,
|
|
155
|
+
/await\s+\w+\s*\(/g
|
|
156
|
+
];
|
|
157
|
+
|
|
158
|
+
let count = 0;
|
|
159
|
+
for (const pattern of businessLogicPatterns) {
|
|
160
|
+
const matches = code.match(pattern);
|
|
161
|
+
if (matches) {
|
|
162
|
+
count += matches.length;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return count;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Main analyze method expected by CLI
|
|
170
|
+
async analyze(files, language, config) {
|
|
171
|
+
const violations = [];
|
|
172
|
+
|
|
173
|
+
for (const filePath of files) {
|
|
174
|
+
try {
|
|
175
|
+
const fileViolations = this.analyzeFile(filePath, config);
|
|
176
|
+
violations.push(...fileViolations);
|
|
177
|
+
} catch (error) {
|
|
178
|
+
console.error(`Error analyzing file ${filePath}:`, error.message);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return violations;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
module.exports = new ValidationSeparationAnalyzer();
|