@sun-asterisk/sunlint 1.2.2 → 1.3.1
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 +107 -1
- package/CONTRIBUTING.md +1654 -66
- package/README.md +19 -6
- package/config/ci-cd.json +54 -0
- package/config/development.json +56 -0
- package/config/engines/engines-enhanced.json +86 -0
- package/config/engines/semantic-config.json +114 -0
- package/config/eslint-rule-mapping.json +50 -38
- package/config/large-project.json +143 -0
- package/config/presets/all.json +0 -1
- package/config/release.json +70 -0
- package/config/rule-analysis-strategies.js +23 -4
- package/config/rules/S027-categories.json +122 -0
- package/config/rules/enhanced-rules-registry.json +2564 -0
- package/config/rules/rules-registry-generated.json +785 -837
- package/config/rules/rules-registry.json +13 -1
- package/core/adapters/sunlint-rule-adapter.js +25 -30
- package/core/analysis-orchestrator.js +42 -2
- package/core/categories.js +52 -0
- package/core/category-constants.js +39 -0
- package/core/cli-action-handler.js +53 -32
- package/core/cli-program.js +11 -3
- package/core/config-manager.js +111 -0
- package/core/config-merger.js +88 -0
- package/core/constants/categories.js +168 -0
- package/core/constants/defaults.js +165 -0
- package/core/constants/engines.js +185 -0
- package/core/constants/index.js +30 -0
- package/core/constants/rules.js +215 -0
- package/core/enhanced-rules-registry.js +3 -3
- package/core/file-targeting-service.js +128 -7
- package/core/interfaces/rule-plugin.interface.js +207 -0
- package/core/plugin-manager.js +448 -0
- package/core/rule-selection-service.js +42 -15
- package/core/semantic-engine.js +658 -0
- package/core/semantic-rule-base.js +433 -0
- package/core/unified-rule-registry.js +484 -0
- package/docs/COMMAND-EXAMPLES.md +134 -0
- package/docs/CONSTANTS-ARCHITECTURE.md +288 -0
- package/docs/LARGE-PROJECT-GUIDE.md +324 -0
- package/engines/core/base-engine.js +249 -0
- package/engines/engine-factory.js +275 -0
- package/engines/eslint-engine.js +171 -19
- package/engines/heuristic-engine.js +569 -78
- package/integrations/eslint/plugin/index.js +26 -28
- package/origin-rules/common-en.md +8 -8
- package/package.json +10 -6
- package/rules/common/C003_no_vague_abbreviations/analyzer.js +1 -1
- package/rules/common/C017_constructor_logic/analyzer.js +254 -17
- package/rules/common/C017_constructor_logic/semantic-analyzer.js +340 -0
- package/rules/common/C029_catch_block_logging/analyzer.js +17 -5
- package/rules/common/C033_separate_service_repository/README.md +78 -0
- package/rules/common/C033_separate_service_repository/analyzer.js +160 -0
- package/rules/common/C033_separate_service_repository/config.json +50 -0
- package/rules/common/C033_separate_service_repository/regex-based-analyzer.js +585 -0
- package/rules/common/C033_separate_service_repository/symbol-based-analyzer.js +368 -0
- package/rules/common/C035_error_logging_context/STRATEGY.md +99 -0
- package/rules/common/C035_error_logging_context/analyzer.js +230 -0
- package/rules/common/C035_error_logging_context/config.json +54 -0
- package/rules/common/C035_error_logging_context/regex-based-analyzer.js +299 -0
- package/rules/common/C035_error_logging_context/symbol-based-analyzer.js +454 -0
- package/rules/common/C040_centralized_validation/analyzer.js +165 -0
- package/rules/common/C040_centralized_validation/config.json +46 -0
- package/rules/common/C040_centralized_validation/regex-based-analyzer.js +243 -0
- package/rules/common/C040_centralized_validation/symbol-based-analyzer.js +416 -0
- package/rules/common/C047_no_duplicate_retry_logic/c047-semantic-rule.js +278 -0
- package/rules/common/C047_no_duplicate_retry_logic/symbol-analyzer-enhanced.js +968 -0
- package/rules/common/C047_no_duplicate_retry_logic/symbol-config.json +71 -0
- package/rules/common/{C076_single_test_behavior → C072_single_test_behavior}/analyzer.js +6 -6
- package/rules/common/C076_explicit_function_types/README.md +30 -0
- package/rules/common/C076_explicit_function_types/analyzer.js +172 -0
- package/rules/common/C076_explicit_function_types/config.json +15 -0
- package/rules/common/C076_explicit_function_types/semantic-analyzer.js +341 -0
- package/rules/index.js +8 -0
- package/rules/parser/rule-parser.js +13 -2
- package/rules/security/S005_no_origin_auth/README.md +226 -0
- package/rules/security/S005_no_origin_auth/analyzer.js +184 -0
- package/rules/security/S005_no_origin_auth/ast-analyzer.js +406 -0
- package/rules/security/S005_no_origin_auth/config.json +85 -0
- package/rules/security/S006_no_plaintext_recovery_codes/README.md +139 -0
- package/rules/security/S006_no_plaintext_recovery_codes/analyzer.js +306 -0
- package/rules/security/S006_no_plaintext_recovery_codes/config.json +48 -0
- package/rules/security/S007_no_plaintext_otp/README.md +198 -0
- package/rules/security/S007_no_plaintext_otp/analyzer.js +406 -0
- package/rules/security/S007_no_plaintext_otp/config.json +79 -0
- package/rules/security/S007_no_plaintext_otp/semantic-analyzer.js +609 -0
- package/rules/security/S007_no_plaintext_otp/semantic-config.json +195 -0
- package/rules/security/S007_no_plaintext_otp/semantic-wrapper.js +280 -0
- package/rules/security/S027_no_hardcoded_secrets/analyzer.js +180 -366
- package/rules/security/S027_no_hardcoded_secrets/categories.json +153 -0
- package/rules/security/S027_no_hardcoded_secrets/categorized-analyzer.js +250 -0
- package/scripts/category-manager.js +150 -0
- package/scripts/generate-rules-registry.js +88 -0
- package/scripts/migrate-rule-registry.js +157 -0
- package/scripts/prepare-release.sh +1 -1
- package/scripts/validate-system.js +48 -0
- package/.sunlint.json +0 -35
- package/config/README.md +0 -88
- package/config/engines/eslint-rule-mapping.json +0 -74
- package/config/schemas/sunlint-schema.json +0 -0
- package/config/testing/test-s005-working.ts +0 -22
- package/core/multi-rule-runner.js +0 -0
- package/docs/ESLINT-INTEGRATION-STRATEGY.md +0 -392
- package/docs/FUTURE_PACKAGES.md +0 -83
- package/docs/HEURISTIC_VS_AI.md +0 -113
- package/docs/PRODUCTION_DEPLOYMENT_ANALYSIS.md +0 -112
- package/docs/PRODUCTION_SIZE_IMPACT.md +0 -183
- package/docs/RELEASE_GUIDE.md +0 -230
- package/docs/STANDARDIZED-CATEGORY-FILTERING.md +0 -156
- package/engines/tree-sitter-parser.js +0 -0
- package/engines/universal-ast-engine.js +0 -0
- package/integrations/eslint/plugin/rules/common/c076-single-behavior-per-test.js +0 -254
- package/rules/common/C029_catch_block_logging/analyzer-backup.js +0 -426
- package/rules/common/C029_catch_block_logging/analyzer-fixed.js +0 -130
- package/rules/common/C029_catch_block_logging/analyzer-multi-tech.js +0 -487
- package/rules/common/C029_catch_block_logging/analyzer-simple.js +0 -110
- package/rules/common/C029_catch_block_logging/ast-analyzer-backup.js +0 -441
- package/rules/common/C029_catch_block_logging/ast-analyzer-new.js +0 -127
- package/rules/common/C029_catch_block_logging/ast-analyzer.js +0 -133
- package/rules/common/C029_catch_block_logging/cfg-analyzer.js +0 -408
- package/rules/common/C029_catch_block_logging/dataflow-analyzer.js +0 -454
- package/rules/common/C029_catch_block_logging/multi-language-ast-engine.js +0 -700
- package/rules/common/C029_catch_block_logging/pattern-learning-analyzer.js +0 -568
- package/rules/common/C029_catch_block_logging/semantic-analyzer.js +0 -459
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AST-based C029 Analyzer - Smart Pipeline Integration
|
|
3
|
-
*
|
|
4
|
-
* This analyzer forwards to the Smart Pipeline for superior accuracy and performance
|
|
5
|
-
* Maintains compatibility with the heuristic engine's AST expectations
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
class C029ASTAnalyzer {
|
|
9
|
-
constructor() {
|
|
10
|
-
this.ruleId = 'C029';
|
|
11
|
-
this.ruleName = 'AST-Enhanced Catch Block Error Logging (Smart Pipeline)';
|
|
12
|
-
this.description = 'Catch blocks must log errors - using Smart Pipeline 3-stage analysis';
|
|
13
|
-
|
|
14
|
-
// Load Smart Pipeline
|
|
15
|
-
this.smartPipeline = null;
|
|
16
|
-
|
|
17
|
-
try {
|
|
18
|
-
this.smartPipeline = require('./analyzer-smart-pipeline.js');
|
|
19
|
-
// Debug message will be shown by Smart Pipeline itself when verbose
|
|
20
|
-
} catch (error) {
|
|
21
|
-
console.warn('⚠️ C029 AST: Smart Pipeline failed:', error.message);
|
|
22
|
-
this.smartPipeline = null;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
async analyze(files, language, options = {}) {
|
|
27
|
-
// Use Smart Pipeline if available
|
|
28
|
-
if (this.smartPipeline) {
|
|
29
|
-
if (options.verbose) {
|
|
30
|
-
console.log('🎯 C029 AST: Using Smart Pipeline (3-stage analysis)...');
|
|
31
|
-
}
|
|
32
|
-
return await this.smartPipeline.analyze(files, language, options);
|
|
33
|
-
} else {
|
|
34
|
-
if (options.verbose) {
|
|
35
|
-
console.log('🔍 C029 AST: Using fallback analysis...');
|
|
36
|
-
}
|
|
37
|
-
return await this.fallbackAnalysis(files, language, options);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async fallbackAnalysis(files, language, options = {}) {
|
|
42
|
-
const violations = [];
|
|
43
|
-
const fs = require('fs');
|
|
44
|
-
const path = require('path');
|
|
45
|
-
|
|
46
|
-
if (options.verbose) {
|
|
47
|
-
console.log(`🔍 C029 AST: Processing ${files.length} files with fallback analysis...`);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
for (const filePath of files) {
|
|
51
|
-
try {
|
|
52
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
53
|
-
const fileViolations = await this.analyzeFile(filePath, content, language);
|
|
54
|
-
violations.push(...fileViolations);
|
|
55
|
-
} catch (error) {
|
|
56
|
-
console.warn(`⚠️ C029 AST: Error processing ${filePath}:`, error.message);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return violations;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async analyzeFile(filePath, content, language) {
|
|
64
|
-
const violations = [];
|
|
65
|
-
const lines = content.split('\n');
|
|
66
|
-
|
|
67
|
-
for (let i = 0; i < lines.length; i++) {
|
|
68
|
-
const line = lines[i];
|
|
69
|
-
|
|
70
|
-
// Simple catch block detection for fallback
|
|
71
|
-
if (line.includes('catch') && line.includes('(')) {
|
|
72
|
-
const catchBlock = this.extractCatchBlock(lines, i);
|
|
73
|
-
|
|
74
|
-
if (this.isCatchBlockEmpty(catchBlock.content)) {
|
|
75
|
-
violations.push({
|
|
76
|
-
file: filePath,
|
|
77
|
-
line: i + 1,
|
|
78
|
-
column: line.indexOf('catch') + 1,
|
|
79
|
-
message: 'Empty catch block detected (fallback analysis)',
|
|
80
|
-
severity: 'error',
|
|
81
|
-
ruleId: this.ruleId,
|
|
82
|
-
type: 'empty_catch_fallback'
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return violations;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
extractCatchBlock(lines, startIndex) {
|
|
92
|
-
const content = [];
|
|
93
|
-
let braceCount = 0;
|
|
94
|
-
let inBlock = false;
|
|
95
|
-
|
|
96
|
-
for (let i = startIndex; i < lines.length; i++) {
|
|
97
|
-
const line = lines[i];
|
|
98
|
-
content.push(line);
|
|
99
|
-
|
|
100
|
-
for (const char of line) {
|
|
101
|
-
if (char === '{') {
|
|
102
|
-
braceCount++;
|
|
103
|
-
inBlock = true;
|
|
104
|
-
} else if (char === '}') {
|
|
105
|
-
braceCount--;
|
|
106
|
-
if (braceCount === 0 && inBlock) {
|
|
107
|
-
return { content, endIndex: i };
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return { content, endIndex: startIndex };
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
isCatchBlockEmpty(content) {
|
|
117
|
-
const blockContent = content.join('\n');
|
|
118
|
-
|
|
119
|
-
// Remove comments and whitespace
|
|
120
|
-
const cleanContent = blockContent
|
|
121
|
-
.replace(/\/\*[\s\S]*?\*\//g, '') // Remove multi-line comments
|
|
122
|
-
.replace(/\/\/.*$/gm, '') // Remove single-line comments
|
|
123
|
-
.replace(/\s+/g, ' ') // Normalize whitespace
|
|
124
|
-
.trim();
|
|
125
|
-
|
|
126
|
-
// Check if only contains catch declaration and braces
|
|
127
|
-
const hasOnlyStructure = /^catch\s*\([^)]*\)\s*\{\s*\}$/.test(cleanContent);
|
|
128
|
-
|
|
129
|
-
return hasOnlyStructure;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
module.exports = C029ASTAnalyzer;
|
|
@@ -1,408 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* C029 Control Flow Graph Analyzer
|
|
3
|
-
*
|
|
4
|
-
* Uses Control Flow Graph to understand execution paths and detect
|
|
5
|
-
* unreachable error handling or dead error variables
|
|
6
|
-
*
|
|
7
|
-
* Technology Showcase: Advanced static analysis beyond traditional linters
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
class C029ControlFlowAnalyzer {
|
|
11
|
-
constructor() {
|
|
12
|
-
this.ruleId = 'C029';
|
|
13
|
-
this.ruleName = 'Control Flow Graph Enhanced Catch Analysis';
|
|
14
|
-
this.description = 'Uses CFG to detect unreachable error handling and dead variables';
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
async analyze(files, language, options = {}) {
|
|
18
|
-
const violations = [];
|
|
19
|
-
console.log(`📊 C029 CFG: Analyzing ${files.length} files with Control Flow Graph...`);
|
|
20
|
-
|
|
21
|
-
for (const filePath of files) {
|
|
22
|
-
try {
|
|
23
|
-
const content = require('fs').readFileSync(filePath, 'utf8');
|
|
24
|
-
const ast = await this.parseAST(content, language, filePath);
|
|
25
|
-
|
|
26
|
-
if (ast) {
|
|
27
|
-
// Build Control Flow Graph
|
|
28
|
-
const cfg = this.buildControlFlowGraph(ast);
|
|
29
|
-
|
|
30
|
-
// Analyze catch blocks within CFG context
|
|
31
|
-
const cfgViolations = this.analyzeCatchBlocksInCFG(cfg, filePath, content);
|
|
32
|
-
violations.push(...cfgViolations);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
} catch (error) {
|
|
36
|
-
console.warn(`C029 CFG skipping ${filePath}: ${error.message}`);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return violations;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Build Control Flow Graph from AST
|
|
45
|
-
* Maps all possible execution paths through the code
|
|
46
|
-
*/
|
|
47
|
-
buildControlFlowGraph(ast) {
|
|
48
|
-
const cfg = {
|
|
49
|
-
nodes: new Map(), // CFG nodes (basic blocks)
|
|
50
|
-
edges: new Map(), // CFG edges (control flow)
|
|
51
|
-
entryNode: null, // Program entry point
|
|
52
|
-
exitNodes: new Set(), // Program exit points
|
|
53
|
-
catchBlocks: new Map() // Catch block metadata
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
let nodeId = 0;
|
|
57
|
-
|
|
58
|
-
// Traverse AST and build CFG
|
|
59
|
-
const buildCFGVisitor = {
|
|
60
|
-
// Handle function declarations/expressions
|
|
61
|
-
visitFunction: (node) => {
|
|
62
|
-
const functionCFG = this.buildFunctionCFG(node, nodeId);
|
|
63
|
-
this.mergeCFG(cfg, functionCFG);
|
|
64
|
-
nodeId += functionCFG.nodeCount;
|
|
65
|
-
return true;
|
|
66
|
-
},
|
|
67
|
-
|
|
68
|
-
// Handle try-catch statements
|
|
69
|
-
visitTryStatement: (node) => {
|
|
70
|
-
const tryCatchCFG = this.buildTryCatchCFG(node, nodeId);
|
|
71
|
-
this.mergeCFG(cfg, tryCatchCFG);
|
|
72
|
-
nodeId += tryCatchCFG.nodeCount;
|
|
73
|
-
return true;
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
this.traverseAST(ast, buildCFGVisitor);
|
|
78
|
-
return cfg;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Build CFG for try-catch-finally blocks
|
|
83
|
-
* Models exception flow and normal flow paths
|
|
84
|
-
*/
|
|
85
|
-
buildTryCatchCFG(tryNode, startNodeId) {
|
|
86
|
-
const cfg = {
|
|
87
|
-
nodes: new Map(),
|
|
88
|
-
edges: new Map(),
|
|
89
|
-
nodeCount: 0,
|
|
90
|
-
catchBlocks: new Map()
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
let currentNodeId = startNodeId;
|
|
94
|
-
|
|
95
|
-
// Try block entry
|
|
96
|
-
const tryEntry = this.createCFGNode(currentNodeId++, 'try_entry', tryNode.block);
|
|
97
|
-
cfg.nodes.set(tryEntry.id, tryEntry);
|
|
98
|
-
|
|
99
|
-
// Try block execution paths
|
|
100
|
-
const tryExitNormal = this.createCFGNode(currentNodeId++, 'try_exit_normal');
|
|
101
|
-
const tryExitException = this.createCFGNode(currentNodeId++, 'try_exit_exception');
|
|
102
|
-
|
|
103
|
-
// Catch block(s)
|
|
104
|
-
const catchNodes = [];
|
|
105
|
-
if (tryNode.handler) {
|
|
106
|
-
const catchEntry = this.createCFGNode(currentNodeId++, 'catch_entry', tryNode.handler);
|
|
107
|
-
const catchExit = this.createCFGNode(currentNodeId++, 'catch_exit');
|
|
108
|
-
|
|
109
|
-
catchNodes.push({ entry: catchEntry, exit: catchExit, handler: tryNode.handler });
|
|
110
|
-
cfg.nodes.set(catchEntry.id, catchEntry);
|
|
111
|
-
cfg.nodes.set(catchExit.id, catchExit);
|
|
112
|
-
|
|
113
|
-
// Store catch block metadata for analysis
|
|
114
|
-
cfg.catchBlocks.set(catchEntry.id, {
|
|
115
|
-
handler: tryNode.handler,
|
|
116
|
-
errorParam: tryNode.handler.param,
|
|
117
|
-
body: tryNode.handler.body,
|
|
118
|
-
reachableFromTry: true // Will be computed later
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Finally block (if exists)
|
|
123
|
-
let finallyEntry = null, finallyExit = null;
|
|
124
|
-
if (tryNode.finalizer) {
|
|
125
|
-
finallyEntry = this.createCFGNode(currentNodeId++, 'finally_entry', tryNode.finalizer);
|
|
126
|
-
finallyExit = this.createCFGNode(currentNodeId++, 'finally_exit');
|
|
127
|
-
cfg.nodes.set(finallyEntry.id, finallyEntry);
|
|
128
|
-
cfg.nodes.set(finallyExit.id, finallyExit);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Build control flow edges
|
|
132
|
-
this.addCFGEdge(cfg, tryEntry.id, tryExitNormal.id, 'normal_flow');
|
|
133
|
-
this.addCFGEdge(cfg, tryEntry.id, tryExitException.id, 'exception_flow');
|
|
134
|
-
|
|
135
|
-
// Exception flow to catch blocks
|
|
136
|
-
for (const catchNode of catchNodes) {
|
|
137
|
-
this.addCFGEdge(cfg, tryExitException.id, catchNode.entry.id, 'catch_flow');
|
|
138
|
-
this.addCFGEdge(cfg, catchNode.exit.id, finallyEntry?.id || 'exit', 'post_catch');
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Normal flow to finally or exit
|
|
142
|
-
this.addCFGEdge(cfg, tryExitNormal.id, finallyEntry?.id || 'exit', 'normal_completion');
|
|
143
|
-
|
|
144
|
-
cfg.nodeCount = currentNodeId - startNodeId;
|
|
145
|
-
return cfg;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Analyze catch blocks within CFG context
|
|
150
|
-
* Detects unreachable error handling and control flow issues
|
|
151
|
-
*/
|
|
152
|
-
analyzeCatchBlocksInCFG(cfg, filePath, content) {
|
|
153
|
-
const violations = [];
|
|
154
|
-
const isTestFile = this.isTestFile(filePath);
|
|
155
|
-
|
|
156
|
-
for (const [nodeId, catchInfo] of cfg.catchBlocks) {
|
|
157
|
-
const analysis = this.analyzeCatchBlockCFG(nodeId, catchInfo, cfg, isTestFile);
|
|
158
|
-
|
|
159
|
-
if (analysis.isViolation) {
|
|
160
|
-
violations.push({
|
|
161
|
-
ruleId: this.ruleId,
|
|
162
|
-
file: filePath,
|
|
163
|
-
line: catchInfo.handler.loc ? catchInfo.handler.loc.start.line : 1,
|
|
164
|
-
column: catchInfo.handler.loc ? catchInfo.handler.loc.start.column + 1 : 1,
|
|
165
|
-
message: analysis.message,
|
|
166
|
-
severity: analysis.severity,
|
|
167
|
-
code: this.getCodeFromNode(catchInfo.handler, content),
|
|
168
|
-
type: analysis.type,
|
|
169
|
-
confidence: analysis.confidence,
|
|
170
|
-
suggestion: analysis.suggestion,
|
|
171
|
-
cfgAnalysis: analysis.cfgData
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
return violations;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* CFG-based analysis of single catch block
|
|
181
|
-
*/
|
|
182
|
-
analyzeCatchBlockCFG(nodeId, catchInfo, cfg, isTestFile) {
|
|
183
|
-
const errorParam = catchInfo.errorParam;
|
|
184
|
-
const catchBody = catchInfo.body;
|
|
185
|
-
|
|
186
|
-
// 1. Check if catch block is reachable
|
|
187
|
-
const reachabilityAnalysis = this.analyzeCatchReachability(nodeId, cfg);
|
|
188
|
-
if (!reachabilityAnalysis.isReachable) {
|
|
189
|
-
return {
|
|
190
|
-
isViolation: true,
|
|
191
|
-
type: 'unreachable_catch',
|
|
192
|
-
message: 'Catch block is unreachable - dead code',
|
|
193
|
-
severity: 'warning',
|
|
194
|
-
confidence: 0.9,
|
|
195
|
-
suggestion: 'Remove unreachable catch block or fix control flow',
|
|
196
|
-
cfgData: reachabilityAnalysis
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// 2. Check for control flow that bypasses error handling
|
|
201
|
-
const flowAnalysis = this.analyzeErrorHandlingFlow(nodeId, catchInfo, cfg);
|
|
202
|
-
if (flowAnalysis.hasBypassFlow) {
|
|
203
|
-
return {
|
|
204
|
-
isViolation: true,
|
|
205
|
-
type: 'bypassed_error_handling',
|
|
206
|
-
message: 'Control flow may bypass error handling',
|
|
207
|
-
severity: 'warning',
|
|
208
|
-
confidence: 0.8,
|
|
209
|
-
suggestion: 'Ensure all exception paths are properly handled',
|
|
210
|
-
cfgData: flowAnalysis
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// 3. Check for error variable usage in control flow
|
|
215
|
-
if (errorParam) {
|
|
216
|
-
const variableFlowAnalysis = this.analyzeErrorVariableFlow(errorParam, catchInfo, cfg);
|
|
217
|
-
|
|
218
|
-
if (!variableFlowAnalysis.isUsed) {
|
|
219
|
-
return {
|
|
220
|
-
isViolation: true,
|
|
221
|
-
type: 'unused_error_variable_cfg',
|
|
222
|
-
message: `Error variable '${errorParam.name}' is unused in all control flow paths`,
|
|
223
|
-
severity: 'error',
|
|
224
|
-
confidence: 0.95,
|
|
225
|
-
suggestion: 'Use error variable or rename to indicate intentional ignore',
|
|
226
|
-
cfgData: variableFlowAnalysis
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
if (variableFlowAnalysis.hasDeadCode) {
|
|
231
|
-
return {
|
|
232
|
-
isViolation: true,
|
|
233
|
-
type: 'dead_error_code',
|
|
234
|
-
message: 'Error handling code contains unreachable statements',
|
|
235
|
-
severity: 'warning',
|
|
236
|
-
confidence: 0.85,
|
|
237
|
-
suggestion: 'Remove dead code or fix control flow logic',
|
|
238
|
-
cfgData: variableFlowAnalysis
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// 4. Apply context-specific rules
|
|
244
|
-
if (isTestFile && this.hasTestAssertions(catchBody)) {
|
|
245
|
-
return { isViolation: false, reason: 'test_assertions_cfg' };
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
return { isViolation: false, reason: 'properly_handled_cfg' };
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* Analyze if catch block is reachable through any execution path
|
|
253
|
-
*/
|
|
254
|
-
analyzeCatchReachability(catchNodeId, cfg) {
|
|
255
|
-
const reachable = new Set();
|
|
256
|
-
const visited = new Set();
|
|
257
|
-
|
|
258
|
-
// DFS from entry points to find reachable nodes
|
|
259
|
-
const dfs = (nodeId) => {
|
|
260
|
-
if (visited.has(nodeId)) return;
|
|
261
|
-
visited.add(nodeId);
|
|
262
|
-
reachable.add(nodeId);
|
|
263
|
-
|
|
264
|
-
const edges = cfg.edges.get(nodeId) || [];
|
|
265
|
-
for (const edge of edges) {
|
|
266
|
-
dfs(edge.to);
|
|
267
|
-
}
|
|
268
|
-
};
|
|
269
|
-
|
|
270
|
-
// Start from all entry points
|
|
271
|
-
if (cfg.entryNode) {
|
|
272
|
-
dfs(cfg.entryNode);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
return {
|
|
276
|
-
isReachable: reachable.has(catchNodeId),
|
|
277
|
-
reachablePaths: this.findPathsTo(catchNodeId, cfg),
|
|
278
|
-
totalReachableNodes: reachable.size
|
|
279
|
-
};
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* Analyze error variable usage across control flow paths
|
|
284
|
-
*/
|
|
285
|
-
analyzeErrorVariableFlow(errorParam, catchInfo, cfg) {
|
|
286
|
-
const variableName = errorParam.name;
|
|
287
|
-
const usageMap = new Map(); // nodeId -> usage info
|
|
288
|
-
|
|
289
|
-
// Find all references to error variable in catch block
|
|
290
|
-
const references = this.findVariableReferences(variableName, catchInfo.body);
|
|
291
|
-
|
|
292
|
-
// Analyze usage in context of control flow
|
|
293
|
-
let hasUsefulUsage = false;
|
|
294
|
-
let hasDeadCode = false;
|
|
295
|
-
|
|
296
|
-
for (const ref of references) {
|
|
297
|
-
const usageType = this.categorizeErrorUsage(ref);
|
|
298
|
-
if (['logging', 'rethrowing', 'error_handling'].includes(usageType)) {
|
|
299
|
-
hasUsefulUsage = true;
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
return {
|
|
304
|
-
isUsed: references.length > 0,
|
|
305
|
-
hasUsefulUsage,
|
|
306
|
-
hasDeadCode,
|
|
307
|
-
references: references.length,
|
|
308
|
-
usageDistribution: this.getUsageDistribution(references)
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// Utility methods (simplified versions)
|
|
313
|
-
createCFGNode(id, type, astNode = null) {
|
|
314
|
-
return {
|
|
315
|
-
id,
|
|
316
|
-
type,
|
|
317
|
-
astNode,
|
|
318
|
-
predecessors: new Set(),
|
|
319
|
-
successors: new Set()
|
|
320
|
-
};
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
addCFGEdge(cfg, fromId, toId, type) {
|
|
324
|
-
if (!cfg.edges.has(fromId)) {
|
|
325
|
-
cfg.edges.set(fromId, []);
|
|
326
|
-
}
|
|
327
|
-
cfg.edges.get(fromId).push({ to: toId, type });
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
mergeCFG(targetCFG, sourceCFG) {
|
|
331
|
-
// Merge nodes and edges from source to target
|
|
332
|
-
for (const [id, node] of sourceCFG.nodes) {
|
|
333
|
-
targetCFG.nodes.set(id, node);
|
|
334
|
-
}
|
|
335
|
-
for (const [id, edges] of sourceCFG.edges) {
|
|
336
|
-
targetCFG.edges.set(id, edges);
|
|
337
|
-
}
|
|
338
|
-
for (const [id, catchInfo] of sourceCFG.catchBlocks) {
|
|
339
|
-
targetCFG.catchBlocks.set(id, catchInfo);
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
findPathsTo(targetNodeId, cfg) {
|
|
344
|
-
// Simplified path finding implementation
|
|
345
|
-
return [];
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
findVariableReferences(variableName, astNode) {
|
|
349
|
-
// Reuse from previous implementation
|
|
350
|
-
return [];
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
categorizeErrorUsage(referenceNode) {
|
|
354
|
-
// Reuse from previous implementation
|
|
355
|
-
return 'unknown';
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
getUsageDistribution(references) {
|
|
359
|
-
return {
|
|
360
|
-
logging: 0,
|
|
361
|
-
rethrowing: 0,
|
|
362
|
-
trivial: 0,
|
|
363
|
-
other: references.length
|
|
364
|
-
};
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
hasTestAssertions(astNode) {
|
|
368
|
-
// Simplified test assertion check
|
|
369
|
-
return false;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
isTestFile(filePath) {
|
|
373
|
-
const testPatterns = ['__tests__', '.test.', '.spec.', '/test/', '/tests/'];
|
|
374
|
-
return testPatterns.some(pattern => filePath.includes(pattern));
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
getCodeFromNode(node, content) {
|
|
378
|
-
// Simplified code extraction
|
|
379
|
-
return node.toString ? node.toString() : 'catch block';
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
async parseAST(content, language, filePath) {
|
|
383
|
-
// Reuse existing AST parsing logic
|
|
384
|
-
return null;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
traverseAST(node, visitor) {
|
|
388
|
-
// Reuse existing AST traversal logic
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
analyzeErrorHandlingFlow(nodeId, catchInfo, cfg) {
|
|
392
|
-
return {
|
|
393
|
-
hasBypassFlow: false,
|
|
394
|
-
flowPaths: []
|
|
395
|
-
};
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
buildFunctionCFG(node, startNodeId) {
|
|
399
|
-
return {
|
|
400
|
-
nodes: new Map(),
|
|
401
|
-
edges: new Map(),
|
|
402
|
-
nodeCount: 1,
|
|
403
|
-
catchBlocks: new Map()
|
|
404
|
-
};
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
module.exports = new C029ControlFlowAnalyzer();
|