@sun-asterisk/sunlint 1.3.24 → 1.3.25
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/rules/enhanced-rules-registry.json +32 -0
- package/package.json +1 -1
- package/rules/common/C010_limit_block_nesting/symbol-based-analyzer.js +40 -11
- package/rules/common/C013_no_dead_code/symbol-based-analyzer.js +104 -28
- package/rules/common/C019_log_level_usage/analyzer.js +30 -27
- package/rules/common/C019_log_level_usage/config.json +4 -2
- package/rules/common/C019_log_level_usage/ts-morph-analyzer.js +274 -0
- package/rules/common/C020_unused_imports/analyzer.js +88 -0
- package/rules/common/C020_unused_imports/config.json +64 -0
- package/rules/common/C020_unused_imports/ts-morph-analyzer.js +358 -0
- package/rules/common/C021_import_organization/analyzer.js +88 -0
- package/rules/common/C021_import_organization/config.json +77 -0
- package/rules/common/C021_import_organization/ts-morph-analyzer.js +373 -0
- package/rules/common/C029_catch_block_logging/analyzer.js +106 -31
- package/rules/common/C033_separate_service_repository/symbol-based-analyzer.js +377 -87
|
@@ -31,6 +31,38 @@
|
|
|
31
31
|
"heuristic": ["rules/common/C019_log_level_usage/analyzer.js"]
|
|
32
32
|
}
|
|
33
33
|
},
|
|
34
|
+
"C020": {
|
|
35
|
+
"name": "Unused Imports",
|
|
36
|
+
"description": "Không import các module hoặc symbol không sử dụng",
|
|
37
|
+
"category": "code-quality",
|
|
38
|
+
"severity": "warning",
|
|
39
|
+
"languages": ["typescript", "javascript"],
|
|
40
|
+
"analyzer": "./rules/common/C020_unused_imports/analyzer.js",
|
|
41
|
+
"config": "./rules/common/C020_unused_imports/config.json",
|
|
42
|
+
"version": "1.0.0",
|
|
43
|
+
"status": "stable",
|
|
44
|
+
"tags": ["imports", "cleanup", "unused-code"],
|
|
45
|
+
"engineMappings": {
|
|
46
|
+
"eslint": ["no-unused-vars", "@typescript-eslint/no-unused-vars"],
|
|
47
|
+
"heuristic": ["rules/common/C020_unused_imports/analyzer.js"]
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"C021": {
|
|
51
|
+
"name": "Import Organization",
|
|
52
|
+
"description": "Tổ chức và sắp xếp imports theo nhóm và thứ tự alphabet",
|
|
53
|
+
"category": "code-quality",
|
|
54
|
+
"severity": "info",
|
|
55
|
+
"languages": ["typescript", "javascript"],
|
|
56
|
+
"analyzer": "./rules/common/C021_import_organization/analyzer.js",
|
|
57
|
+
"config": "./rules/common/C021_import_organization/config.json",
|
|
58
|
+
"version": "1.0.0",
|
|
59
|
+
"status": "stable",
|
|
60
|
+
"tags": ["imports", "organization", "readability"],
|
|
61
|
+
"engineMappings": {
|
|
62
|
+
"eslint": ["import/order", "sort-imports"],
|
|
63
|
+
"heuristic": ["rules/common/C021_import_organization/analyzer.js"]
|
|
64
|
+
}
|
|
65
|
+
},
|
|
34
66
|
"C006": {
|
|
35
67
|
"name": "Function Naming Convention",
|
|
36
68
|
"description": "Tên hàm phải là động từ/verb-noun pattern",
|
package/package.json
CHANGED
|
@@ -17,6 +17,8 @@ class C010SymbolBasedAnalyzer {
|
|
|
17
17
|
this.maxNestingLevel = 3;
|
|
18
18
|
|
|
19
19
|
// Block statement kinds that count toward nesting (use ts-morph SyntaxKind)
|
|
20
|
+
// IMPORTANT: Only control flow statements that represent LOGICAL complexity
|
|
21
|
+
// Try/catch are error handling wrappers and should NOT count toward nesting
|
|
20
22
|
this.blockStatementKinds = new Set([
|
|
21
23
|
SyntaxKind.IfStatement,
|
|
22
24
|
SyntaxKind.ForStatement,
|
|
@@ -24,10 +26,9 @@ class C010SymbolBasedAnalyzer {
|
|
|
24
26
|
SyntaxKind.ForOfStatement,
|
|
25
27
|
SyntaxKind.WhileStatement,
|
|
26
28
|
SyntaxKind.DoStatement,
|
|
27
|
-
SyntaxKind.SwitchStatement
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
SyntaxKind.Block
|
|
29
|
+
SyntaxKind.SwitchStatement
|
|
30
|
+
// Removed: TryStatement, CatchClause, Block
|
|
31
|
+
// Rationale: Error handling wrappers don't add logical complexity
|
|
31
32
|
]);
|
|
32
33
|
|
|
33
34
|
// Statements that DON'T count toward nesting
|
|
@@ -44,6 +45,13 @@ class C010SymbolBasedAnalyzer {
|
|
|
44
45
|
SyntaxKind.ObjectLiteralExpression,
|
|
45
46
|
SyntaxKind.ArrayLiteralExpression
|
|
46
47
|
]);
|
|
48
|
+
|
|
49
|
+
// Error handling constructs that should be transparent (don't add or reset nesting)
|
|
50
|
+
this.errorHandlingKinds = new Set([
|
|
51
|
+
SyntaxKind.TryStatement,
|
|
52
|
+
SyntaxKind.CatchClause,
|
|
53
|
+
SyntaxKind.FinallyKeyword
|
|
54
|
+
]);
|
|
47
55
|
}
|
|
48
56
|
|
|
49
57
|
async initialize(semanticEngine = null) {
|
|
@@ -137,22 +145,30 @@ class C010SymbolBasedAnalyzer {
|
|
|
137
145
|
violations.push({
|
|
138
146
|
ruleId: this.ruleId,
|
|
139
147
|
severity: 'warning',
|
|
140
|
-
message: `Block nesting
|
|
148
|
+
message: `Block nesting depth ${newDepth} exceeds maximum of ${this.maxNestingLevel}. Consider refactoring to reduce complexity.`,
|
|
141
149
|
filePath: filePath,
|
|
142
150
|
line: lineAndChar.line + 1,
|
|
143
151
|
column: lineAndChar.column + 1,
|
|
144
|
-
context: this.getNodeContext(node)
|
|
152
|
+
context: this.getNodeContext(node),
|
|
153
|
+
suggestion: this.getSuggestion(newDepth)
|
|
145
154
|
});
|
|
146
155
|
}
|
|
147
156
|
}
|
|
148
157
|
|
|
149
158
|
// Recursively analyze child nodes
|
|
150
159
|
node.forEachChild(child => {
|
|
151
|
-
|
|
160
|
+
const childKind = child.getKind();
|
|
161
|
+
|
|
162
|
+
// Reset depth for function boundaries
|
|
152
163
|
if (this.isFunctionBoundary(child)) {
|
|
153
|
-
// Reset depth for function/method/class boundaries
|
|
154
164
|
this.traverseNode(child, 0, violations, filePath);
|
|
155
|
-
}
|
|
165
|
+
}
|
|
166
|
+
// Keep same depth for error handling (transparent wrappers)
|
|
167
|
+
else if (this.errorHandlingKinds.has(childKind)) {
|
|
168
|
+
this.traverseNode(child, currentDepth, violations, filePath);
|
|
169
|
+
}
|
|
170
|
+
// Normal case: use new depth
|
|
171
|
+
else {
|
|
156
172
|
this.traverseNode(child, newDepth, violations, filePath);
|
|
157
173
|
}
|
|
158
174
|
});
|
|
@@ -195,17 +211,30 @@ class C010SymbolBasedAnalyzer {
|
|
|
195
211
|
const text = node.getText();
|
|
196
212
|
const lines = text.split('\n');
|
|
197
213
|
const firstLine = lines[0].trim();
|
|
198
|
-
|
|
214
|
+
|
|
199
215
|
// Return first line or statement type
|
|
200
216
|
if (firstLine.length > 0) {
|
|
201
217
|
return firstLine.length > 50 ? firstLine.substring(0, 47) + '...' : firstLine;
|
|
202
218
|
}
|
|
203
|
-
|
|
219
|
+
|
|
204
220
|
// Fallback to node kind
|
|
205
221
|
const kind = node.getKind();
|
|
206
222
|
return SyntaxKind[kind] || 'Unknown';
|
|
207
223
|
}
|
|
208
224
|
|
|
225
|
+
/**
|
|
226
|
+
* Get refactoring suggestion based on nesting depth
|
|
227
|
+
*/
|
|
228
|
+
getSuggestion(depth) {
|
|
229
|
+
if (depth >= 6) {
|
|
230
|
+
return 'Critical: Extract nested logic into separate functions';
|
|
231
|
+
} else if (depth >= 5) {
|
|
232
|
+
return 'Use early returns, guard clauses, or extract methods';
|
|
233
|
+
} else {
|
|
234
|
+
return 'Consider using early returns or extracting to helper functions';
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
209
238
|
/**
|
|
210
239
|
* Get detailed information about nesting violation
|
|
211
240
|
*/
|
|
@@ -396,23 +396,30 @@ class C013SymbolBasedAnalyzer {
|
|
|
396
396
|
|
|
397
397
|
detectUnusedVariables(sourceFile, filePath) {
|
|
398
398
|
const violations = [];
|
|
399
|
-
|
|
399
|
+
|
|
400
400
|
// Get all variable declarations
|
|
401
401
|
const variableDeclarations = sourceFile.getDescendantsOfKind(SyntaxKind.VariableDeclaration);
|
|
402
|
-
|
|
402
|
+
|
|
403
403
|
for (const declaration of variableDeclarations) {
|
|
404
404
|
const name = declaration.getName();
|
|
405
|
-
|
|
405
|
+
|
|
406
406
|
// Skip variables with underscore prefix (conventional ignore)
|
|
407
407
|
if (name.startsWith('_') || name.startsWith('$')) {
|
|
408
408
|
continue;
|
|
409
409
|
}
|
|
410
|
-
|
|
410
|
+
|
|
411
411
|
// Skip destructured variables for now (complex analysis)
|
|
412
412
|
if (declaration.getNameNode().getKind() !== SyntaxKind.Identifier) {
|
|
413
413
|
continue;
|
|
414
414
|
}
|
|
415
|
-
|
|
415
|
+
|
|
416
|
+
// Skip catch clause error variables (defensive programming pattern)
|
|
417
|
+
// Example: catch (error) { return false; } - error doesn't need to be used
|
|
418
|
+
// This aligns with C029 rule improvements
|
|
419
|
+
if (this.isInCatchClause(declaration)) {
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
|
|
416
423
|
// Skip exported variables (they might be used externally)
|
|
417
424
|
const variableStatement = declaration.getParent()?.getParent();
|
|
418
425
|
if (variableStatement && variableStatement.getKind() === SyntaxKind.VariableStatement) {
|
|
@@ -420,17 +427,17 @@ class C013SymbolBasedAnalyzer {
|
|
|
420
427
|
continue;
|
|
421
428
|
}
|
|
422
429
|
}
|
|
423
|
-
|
|
430
|
+
|
|
424
431
|
// Check if variable is used
|
|
425
432
|
const usages = declaration.getNameNode().findReferences();
|
|
426
|
-
const isUsed = usages.some(ref =>
|
|
433
|
+
const isUsed = usages.some(ref =>
|
|
427
434
|
ref.getReferences().length > 1 // More than just the declaration itself
|
|
428
435
|
);
|
|
429
|
-
|
|
436
|
+
|
|
430
437
|
if (!isUsed) {
|
|
431
438
|
const line = sourceFile.getLineAndColumnAtPos(declaration.getStart()).line;
|
|
432
439
|
const column = sourceFile.getLineAndColumnAtPos(declaration.getStart()).column;
|
|
433
|
-
|
|
440
|
+
|
|
434
441
|
violations.push(this.createViolation(
|
|
435
442
|
filePath,
|
|
436
443
|
line,
|
|
@@ -440,10 +447,32 @@ class C013SymbolBasedAnalyzer {
|
|
|
440
447
|
));
|
|
441
448
|
}
|
|
442
449
|
}
|
|
443
|
-
|
|
450
|
+
|
|
444
451
|
return violations;
|
|
445
452
|
}
|
|
446
453
|
|
|
454
|
+
/**
|
|
455
|
+
* Check if a node is within a catch clause
|
|
456
|
+
* Used to skip catch clause error variables from unused variable detection
|
|
457
|
+
*/
|
|
458
|
+
isInCatchClause(node) {
|
|
459
|
+
let current = node.getParent();
|
|
460
|
+
while (current) {
|
|
461
|
+
if (current.getKind() === SyntaxKind.CatchClause) {
|
|
462
|
+
return true;
|
|
463
|
+
}
|
|
464
|
+
// Stop at function boundaries
|
|
465
|
+
if (current.getKind() === SyntaxKind.FunctionDeclaration ||
|
|
466
|
+
current.getKind() === SyntaxKind.FunctionExpression ||
|
|
467
|
+
current.getKind() === SyntaxKind.ArrowFunction ||
|
|
468
|
+
current.getKind() === SyntaxKind.MethodDeclaration) {
|
|
469
|
+
return false;
|
|
470
|
+
}
|
|
471
|
+
current = current.getParent();
|
|
472
|
+
}
|
|
473
|
+
return false;
|
|
474
|
+
}
|
|
475
|
+
|
|
447
476
|
detectUnusedFunctions(sourceFile, filePath) {
|
|
448
477
|
const violations = [];
|
|
449
478
|
|
|
@@ -493,58 +522,66 @@ class C013SymbolBasedAnalyzer {
|
|
|
493
522
|
|
|
494
523
|
detectUnreachableCode(sourceFile, filePath) {
|
|
495
524
|
const violations = [];
|
|
496
|
-
|
|
497
|
-
// Find all return
|
|
525
|
+
|
|
526
|
+
// Find all return and throw statements (truly terminating)
|
|
527
|
+
// Note: Break and continue are NOT included - they only exit loops/switches,
|
|
528
|
+
// not the containing function block
|
|
498
529
|
const terminatingStatements = [
|
|
499
530
|
...sourceFile.getDescendantsOfKind(SyntaxKind.ReturnStatement),
|
|
500
|
-
...sourceFile.getDescendantsOfKind(SyntaxKind.ThrowStatement)
|
|
501
|
-
|
|
502
|
-
|
|
531
|
+
...sourceFile.getDescendantsOfKind(SyntaxKind.ThrowStatement)
|
|
532
|
+
// Removed: BreakStatement, ContinueStatement - these don't make code unreachable
|
|
533
|
+
// Example: switch(x) { case A: break; } const y = 1; // ← y is reachable!
|
|
503
534
|
];
|
|
504
|
-
|
|
535
|
+
|
|
505
536
|
for (const statement of terminatingStatements) {
|
|
506
537
|
// Find the statement that contains this terminating statement
|
|
507
538
|
let containingStatement = statement;
|
|
508
539
|
let parent = statement.getParent();
|
|
509
|
-
|
|
540
|
+
|
|
510
541
|
// Walk up to find the statement that's directly in a block
|
|
511
542
|
while (parent && parent.getKind() !== SyntaxKind.Block && parent.getKind() !== SyntaxKind.SourceFile) {
|
|
512
543
|
containingStatement = parent;
|
|
513
544
|
parent = parent.getParent();
|
|
514
545
|
}
|
|
515
|
-
|
|
546
|
+
|
|
516
547
|
// Get the parent block
|
|
517
548
|
const parentBlock = parent;
|
|
518
|
-
|
|
549
|
+
|
|
519
550
|
if (!parentBlock || parentBlock.getKind() === SyntaxKind.SourceFile) continue;
|
|
520
|
-
|
|
551
|
+
|
|
552
|
+
// Check if this is in a conditional block (if/else/switch/loop)
|
|
553
|
+
// If yes, code after the conditional is still reachable
|
|
554
|
+
if (this.isInConditionalBlock(containingStatement)) {
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
557
|
+
|
|
521
558
|
// Find all statements in the same block after this terminating statement
|
|
522
559
|
const allStatements = parentBlock.getStatements();
|
|
523
560
|
const currentIndex = allStatements.indexOf(containingStatement);
|
|
524
|
-
|
|
561
|
+
|
|
525
562
|
if (currentIndex >= 0 && currentIndex < allStatements.length - 1) {
|
|
526
563
|
// Check statements after the terminating statement
|
|
527
564
|
for (let i = currentIndex + 1; i < allStatements.length; i++) {
|
|
528
565
|
const nextStatement = allStatements[i];
|
|
529
|
-
|
|
566
|
+
|
|
530
567
|
// Skip comments and empty statements
|
|
531
568
|
if (nextStatement.getKind() === SyntaxKind.EmptyStatement) {
|
|
532
569
|
continue;
|
|
533
570
|
}
|
|
534
|
-
|
|
571
|
+
|
|
535
572
|
// Don't flag catch/finally blocks as unreachable
|
|
536
573
|
if (this.isInTryCatchFinally(nextStatement)) {
|
|
537
574
|
continue;
|
|
538
575
|
}
|
|
539
|
-
|
|
576
|
+
|
|
540
577
|
// Skip if this is within a conditional (if/else) or loop that might not execute
|
|
541
578
|
if (this.isConditionallyReachable(containingStatement, nextStatement)) {
|
|
542
579
|
continue;
|
|
543
580
|
}
|
|
544
|
-
|
|
581
|
+
|
|
545
582
|
const line = sourceFile.getLineAndColumnAtPos(nextStatement.getStart()).line;
|
|
546
583
|
const column = sourceFile.getLineAndColumnAtPos(nextStatement.getStart()).column;
|
|
547
|
-
|
|
584
|
+
|
|
548
585
|
violations.push(this.createViolation(
|
|
549
586
|
filePath,
|
|
550
587
|
line,
|
|
@@ -552,15 +589,54 @@ class C013SymbolBasedAnalyzer {
|
|
|
552
589
|
`Unreachable code detected after ${statement.getKindName().toLowerCase()}. Remove dead code.`,
|
|
553
590
|
'unreachable-code'
|
|
554
591
|
));
|
|
555
|
-
|
|
592
|
+
|
|
556
593
|
break; // Only flag the first unreachable statement to avoid spam
|
|
557
594
|
}
|
|
558
595
|
}
|
|
559
596
|
}
|
|
560
|
-
|
|
597
|
+
|
|
561
598
|
return violations;
|
|
562
599
|
}
|
|
563
600
|
|
|
601
|
+
/**
|
|
602
|
+
* Check if a statement is within a conditional block (if/else/switch/loop)
|
|
603
|
+
* Code after such blocks is still reachable even if the block has return/throw
|
|
604
|
+
*/
|
|
605
|
+
isInConditionalBlock(statement) {
|
|
606
|
+
let current = statement;
|
|
607
|
+
|
|
608
|
+
// Walk up the tree to check if we're inside conditional constructs
|
|
609
|
+
while (current) {
|
|
610
|
+
const kind = current.getKind();
|
|
611
|
+
|
|
612
|
+
// If we hit these, the code after the containing block is reachable
|
|
613
|
+
if (kind === SyntaxKind.IfStatement ||
|
|
614
|
+
kind === SyntaxKind.ElseKeyword ||
|
|
615
|
+
kind === SyntaxKind.SwitchStatement ||
|
|
616
|
+
kind === SyntaxKind.CaseClause ||
|
|
617
|
+
kind === SyntaxKind.DefaultClause ||
|
|
618
|
+
kind === SyntaxKind.ForStatement ||
|
|
619
|
+
kind === SyntaxKind.ForInStatement ||
|
|
620
|
+
kind === SyntaxKind.ForOfStatement ||
|
|
621
|
+
kind === SyntaxKind.WhileStatement ||
|
|
622
|
+
kind === SyntaxKind.DoStatement) {
|
|
623
|
+
return true;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// Stop at function boundaries
|
|
627
|
+
if (kind === SyntaxKind.FunctionDeclaration ||
|
|
628
|
+
kind === SyntaxKind.FunctionExpression ||
|
|
629
|
+
kind === SyntaxKind.ArrowFunction ||
|
|
630
|
+
kind === SyntaxKind.MethodDeclaration) {
|
|
631
|
+
return false;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
current = current.getParent();
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
return false;
|
|
638
|
+
}
|
|
639
|
+
|
|
564
640
|
isInTryCatchFinally(node) {
|
|
565
641
|
// Check if the node is inside a try-catch-finally block
|
|
566
642
|
let parent = node.getParent();
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
const
|
|
1
|
+
const C019TsMorphAnalyzer = require('./ts-morph-analyzer.js');
|
|
2
2
|
const C019PatternAnalyzer = require('./pattern-analyzer.js');
|
|
3
3
|
|
|
4
4
|
class C019Analyzer {
|
|
5
5
|
constructor(semanticEngine = null) {
|
|
6
6
|
this.ruleId = 'C019';
|
|
7
7
|
this.ruleName = 'Log Level Usage';
|
|
8
|
-
this.description = '
|
|
8
|
+
this.description = 'Detect inappropriate ERROR log level for business logic errors';
|
|
9
9
|
this.semanticEngine = semanticEngine;
|
|
10
10
|
this.verbose = false;
|
|
11
|
-
|
|
12
|
-
// Initialize analyzers -
|
|
13
|
-
this.
|
|
11
|
+
|
|
12
|
+
// Initialize analyzers - ts-morph primary, pattern fallback
|
|
13
|
+
this.tsMorphAnalyzer = new C019TsMorphAnalyzer(semanticEngine);
|
|
14
14
|
this.patternAnalyzer = new C019PatternAnalyzer();
|
|
15
15
|
this.aiAnalyzer = null;
|
|
16
16
|
}
|
|
@@ -20,53 +20,56 @@ class C019Analyzer {
|
|
|
20
20
|
this.semanticEngine = semanticEngine;
|
|
21
21
|
}
|
|
22
22
|
this.verbose = semanticEngine?.verbose || false;
|
|
23
|
-
|
|
24
|
-
await this.
|
|
23
|
+
|
|
24
|
+
await this.tsMorphAnalyzer.initialize(semanticEngine);
|
|
25
25
|
await this.patternAnalyzer.initialize({ verbose: this.verbose });
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
async analyzeFileBasic(filePath, options = {}) {
|
|
29
29
|
const allViolations = [];
|
|
30
|
-
|
|
30
|
+
|
|
31
31
|
try {
|
|
32
|
-
//
|
|
32
|
+
// Use ts-morph analysis (Primary - AST) if semantic engine is available
|
|
33
33
|
if (this.semanticEngine?.isSymbolEngineReady?.() && this.semanticEngine.project) {
|
|
34
34
|
if (this.verbose) {
|
|
35
|
-
console.log(`[DEBUG] 🎯 C019: Using
|
|
35
|
+
console.log(`[DEBUG] 🎯 C019: Using ts-morph analysis for ${filePath.split('/').pop()}`);
|
|
36
36
|
}
|
|
37
|
-
|
|
37
|
+
|
|
38
38
|
try {
|
|
39
|
-
const
|
|
40
|
-
allViolations.push(...
|
|
41
|
-
|
|
39
|
+
const tsMorphViolations = await this.tsMorphAnalyzer.analyzeFile(filePath, options);
|
|
40
|
+
allViolations.push(...tsMorphViolations);
|
|
41
|
+
|
|
42
42
|
if (this.verbose) {
|
|
43
|
-
console.log(`[DEBUG] 🎯 C019:
|
|
43
|
+
console.log(`[DEBUG] 🎯 C019: ts-morph analysis found ${tsMorphViolations.length} violations`);
|
|
44
44
|
}
|
|
45
|
-
|
|
45
|
+
|
|
46
|
+
// Return ts-morph results (even if 0) - do NOT fall back to pattern analyzer
|
|
47
|
+
// Pattern analyzer has no catch block detection and will produce false positives
|
|
48
|
+
return this.deduplicateViolations(allViolations);
|
|
49
|
+
|
|
50
|
+
} catch (tsMorphError) {
|
|
46
51
|
if (this.verbose) {
|
|
47
|
-
console.warn(`[DEBUG] ⚠️ C019:
|
|
52
|
+
console.warn(`[DEBUG] ⚠️ C019: ts-morph analysis failed: ${tsMorphError.message}`);
|
|
48
53
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (allViolations.length > 0) {
|
|
52
|
-
return this.deduplicateViolations(allViolations);
|
|
54
|
+
// Only fall through to pattern analyzer if ts-morph failed
|
|
53
55
|
}
|
|
54
56
|
}
|
|
55
|
-
|
|
56
|
-
// Fall back to pattern-based analysis (Secondary - Regex)
|
|
57
|
+
|
|
58
|
+
// Fall back to pattern-based analysis (Secondary - Regex)
|
|
59
|
+
// ONLY if semantic engine is not available or ts-morph failed
|
|
57
60
|
if (this.verbose) {
|
|
58
61
|
console.log(`[DEBUG] 🔄 C019: Running pattern-based analysis for ${filePath.split('/').pop()}`);
|
|
59
62
|
}
|
|
60
|
-
|
|
63
|
+
|
|
61
64
|
const patternViolations = await this.patternAnalyzer.analyzeFileBasic(filePath, options);
|
|
62
65
|
allViolations.push(...patternViolations);
|
|
63
|
-
|
|
66
|
+
|
|
64
67
|
if (this.verbose) {
|
|
65
68
|
console.log(`[DEBUG] 🔄 C019: Pattern analysis found ${patternViolations.length} violations`);
|
|
66
69
|
}
|
|
67
|
-
|
|
70
|
+
|
|
68
71
|
return this.deduplicateViolations(allViolations);
|
|
69
|
-
|
|
72
|
+
|
|
70
73
|
} catch (error) {
|
|
71
74
|
if (this.verbose) {
|
|
72
75
|
console.error(`[DEBUG] ❌ C019: Analysis failed: ${error.message}`);
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"errorKeywords": [
|
|
13
13
|
"not found",
|
|
14
14
|
"invalid",
|
|
15
|
-
"unauthorized",
|
|
15
|
+
"unauthorized",
|
|
16
16
|
"forbidden",
|
|
17
17
|
"validation failed",
|
|
18
18
|
"bad request",
|
|
@@ -23,7 +23,9 @@
|
|
|
23
23
|
"input error",
|
|
24
24
|
"validation",
|
|
25
25
|
"invalid input",
|
|
26
|
-
"missing parameter"
|
|
26
|
+
"missing parameter",
|
|
27
|
+
"exceed",
|
|
28
|
+
"limit"
|
|
27
29
|
],
|
|
28
30
|
"legitimateErrorKeywords": [
|
|
29
31
|
"exception",
|