@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,700 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* C029 Multi-Language AST Engine
|
|
3
|
+
*
|
|
4
|
+
* Provides unified AST-based analysis across multiple programming languages
|
|
5
|
+
* with language-specific adaptations for error handling patterns
|
|
6
|
+
*
|
|
7
|
+
* Technology Showcase: Cross-language compatibility and unified analysis
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
class C029MultiLanguageASTEngine {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.ruleId = 'C029';
|
|
13
|
+
this.ruleName = 'Multi-Language Error Handling Analysis';
|
|
14
|
+
this.description = 'Unified AST-based error handling analysis across languages';
|
|
15
|
+
|
|
16
|
+
// Language-specific parsers and configurations
|
|
17
|
+
this.languageSupport = {
|
|
18
|
+
'javascript': {
|
|
19
|
+
parser: 'babel',
|
|
20
|
+
fileExtensions: ['.js', '.jsx', '.mjs'],
|
|
21
|
+
errorHandlingPatterns: ['try-catch', 'promise-catch', 'async-catch'],
|
|
22
|
+
keywords: { try: 'try', catch: 'catch', throw: 'throw', error: 'Error' }
|
|
23
|
+
},
|
|
24
|
+
'typescript': {
|
|
25
|
+
parser: 'typescript',
|
|
26
|
+
fileExtensions: ['.ts', '.tsx'],
|
|
27
|
+
errorHandlingPatterns: ['try-catch', 'promise-catch', 'async-catch', 'generic-error'],
|
|
28
|
+
keywords: { try: 'try', catch: 'catch', throw: 'throw', error: 'Error' }
|
|
29
|
+
},
|
|
30
|
+
'java': {
|
|
31
|
+
parser: 'tree-sitter-java',
|
|
32
|
+
fileExtensions: ['.java'],
|
|
33
|
+
errorHandlingPatterns: ['try-catch-finally', 'throws-declaration', 'checked-exceptions'],
|
|
34
|
+
keywords: { try: 'try', catch: 'catch', throw: 'throw', throws: 'throws', finally: 'finally' }
|
|
35
|
+
},
|
|
36
|
+
'kotlin': {
|
|
37
|
+
parser: 'tree-sitter-kotlin',
|
|
38
|
+
fileExtensions: ['.kt', '.kts'],
|
|
39
|
+
errorHandlingPatterns: ['try-catch', 'result-type', 'elvis-operator'],
|
|
40
|
+
keywords: { try: 'try', catch: 'catch', throw: 'throw', result: 'Result' }
|
|
41
|
+
},
|
|
42
|
+
'dart': {
|
|
43
|
+
parser: 'tree-sitter-dart',
|
|
44
|
+
fileExtensions: ['.dart'],
|
|
45
|
+
errorHandlingPatterns: ['try-catch-on', 'future-catchError', 'stream-error'],
|
|
46
|
+
keywords: { try: 'try', catch: 'catch', on: 'on', throw: 'throw', rethrow: 'rethrow' }
|
|
47
|
+
},
|
|
48
|
+
'python': {
|
|
49
|
+
parser: 'tree-sitter-python',
|
|
50
|
+
fileExtensions: ['.py', '.pyw'],
|
|
51
|
+
errorHandlingPatterns: ['try-except-finally', 'context-manager', 'exception-chaining'],
|
|
52
|
+
keywords: { try: 'try', except: 'except', raise: 'raise', finally: 'finally' }
|
|
53
|
+
},
|
|
54
|
+
'go': {
|
|
55
|
+
parser: 'tree-sitter-go',
|
|
56
|
+
fileExtensions: ['.go'],
|
|
57
|
+
errorHandlingPatterns: ['error-return', 'panic-recover', 'defer-recover'],
|
|
58
|
+
keywords: { if: 'if', err: 'err', panic: 'panic', recover: 'recover', defer: 'defer' }
|
|
59
|
+
},
|
|
60
|
+
'rust': {
|
|
61
|
+
parser: 'tree-sitter-rust',
|
|
62
|
+
fileExtensions: ['.rs'],
|
|
63
|
+
errorHandlingPatterns: ['result-match', 'option-match', 'panic-catch'],
|
|
64
|
+
keywords: { match: 'match', Result: 'Result', Option: 'Option', panic: 'panic!' }
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// Cross-language error handling concepts
|
|
69
|
+
this.universalConcepts = {
|
|
70
|
+
'error_propagation': ['throw', 'raise', 'panic', 'return_error'],
|
|
71
|
+
'error_handling': ['catch', 'except', 'match', 'if_err'],
|
|
72
|
+
'resource_cleanup': ['finally', 'defer', 'drop', 'using'],
|
|
73
|
+
'error_logging': ['log', 'print', 'console', 'debug', 'warn'],
|
|
74
|
+
'error_transformation': ['wrap', 'chain', 'convert', 'map_err']
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async analyze(files, language, options = {}) {
|
|
79
|
+
const violations = [];
|
|
80
|
+
console.log(`🌐 C029 Multi-Language AST: Analyzing ${files.length} ${language} files...`);
|
|
81
|
+
|
|
82
|
+
const langConfig = this.languageSupport[language.toLowerCase()];
|
|
83
|
+
if (!langConfig) {
|
|
84
|
+
console.warn(`⚠️ Language ${language} not supported by Multi-Language AST Engine`);
|
|
85
|
+
return violations;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
for (const filePath of files) {
|
|
89
|
+
try {
|
|
90
|
+
const content = require('fs').readFileSync(filePath, 'utf8');
|
|
91
|
+
const languageViolations = await this.analyzeWithLanguageSupport(
|
|
92
|
+
content, filePath, language, langConfig
|
|
93
|
+
);
|
|
94
|
+
violations.push(...languageViolations);
|
|
95
|
+
|
|
96
|
+
} catch (error) {
|
|
97
|
+
console.warn(`C029 Multi-Language AST skipping ${filePath}: ${error.message}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return violations;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Analyze file with language-specific support
|
|
106
|
+
*/
|
|
107
|
+
async analyzeWithLanguageSupport(content, filePath, language, langConfig) {
|
|
108
|
+
const violations = [];
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
// Parse with language-specific parser
|
|
112
|
+
const ast = await this.parseWithLanguageParser(content, language, langConfig);
|
|
113
|
+
|
|
114
|
+
// Extract error handling constructs
|
|
115
|
+
const errorHandlingNodes = this.extractErrorHandlingNodes(ast, langConfig);
|
|
116
|
+
|
|
117
|
+
// Analyze each error handling construct
|
|
118
|
+
for (const node of errorHandlingNodes) {
|
|
119
|
+
const analysis = this.analyzeErrorHandlingNode(node, content, langConfig, language);
|
|
120
|
+
|
|
121
|
+
if (analysis.isViolation) {
|
|
122
|
+
violations.push({
|
|
123
|
+
ruleId: this.ruleId,
|
|
124
|
+
file: filePath,
|
|
125
|
+
line: analysis.line,
|
|
126
|
+
column: analysis.column,
|
|
127
|
+
message: analysis.message,
|
|
128
|
+
severity: analysis.severity,
|
|
129
|
+
code: analysis.code,
|
|
130
|
+
type: analysis.type,
|
|
131
|
+
confidence: analysis.confidence,
|
|
132
|
+
suggestion: analysis.suggestion,
|
|
133
|
+
language: language,
|
|
134
|
+
errorHandlingPattern: analysis.pattern,
|
|
135
|
+
crossLanguageInsight: analysis.insight
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
} catch (parseError) {
|
|
141
|
+
console.warn(`AST parsing failed for ${filePath}: ${parseError.message}`);
|
|
142
|
+
// Fallback to regex-based analysis
|
|
143
|
+
return this.fallbackAnalysis(content, filePath, language, langConfig);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return violations;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Parse content with appropriate language parser
|
|
151
|
+
*/
|
|
152
|
+
async parseWithLanguageParser(content, language, langConfig) {
|
|
153
|
+
switch (language.toLowerCase()) {
|
|
154
|
+
case 'javascript':
|
|
155
|
+
case 'typescript':
|
|
156
|
+
return this.parseJavaScriptTypeScript(content, language);
|
|
157
|
+
|
|
158
|
+
case 'java':
|
|
159
|
+
return this.parseJava(content);
|
|
160
|
+
|
|
161
|
+
case 'kotlin':
|
|
162
|
+
return this.parseKotlin(content);
|
|
163
|
+
|
|
164
|
+
case 'dart':
|
|
165
|
+
return this.parseDart(content);
|
|
166
|
+
|
|
167
|
+
case 'python':
|
|
168
|
+
return this.parsePython(content);
|
|
169
|
+
|
|
170
|
+
case 'go':
|
|
171
|
+
return this.parseGo(content);
|
|
172
|
+
|
|
173
|
+
case 'rust':
|
|
174
|
+
return this.parseRust(content);
|
|
175
|
+
|
|
176
|
+
default:
|
|
177
|
+
throw new Error(`Parser not implemented for ${language}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Extract error handling nodes from AST
|
|
183
|
+
*/
|
|
184
|
+
extractErrorHandlingNodes(ast, langConfig) {
|
|
185
|
+
const errorNodes = [];
|
|
186
|
+
|
|
187
|
+
// Language-specific node extraction
|
|
188
|
+
for (const pattern of langConfig.errorHandlingPatterns) {
|
|
189
|
+
const nodes = this.extractNodesForPattern(ast, pattern, langConfig);
|
|
190
|
+
errorNodes.push(...nodes);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return errorNodes;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* JavaScript/TypeScript AST parsing
|
|
198
|
+
*/
|
|
199
|
+
parseJavaScriptTypeScript(content, language) {
|
|
200
|
+
// Simplified AST representation for JavaScript/TypeScript
|
|
201
|
+
const tryBlocks = [];
|
|
202
|
+
const lines = content.split('\n');
|
|
203
|
+
|
|
204
|
+
for (let i = 0; i < lines.length; i++) {
|
|
205
|
+
const line = lines[i];
|
|
206
|
+
|
|
207
|
+
// Find try blocks
|
|
208
|
+
if (/\btry\s*\{/.test(line)) {
|
|
209
|
+
const tryBlock = this.extractJSTryBlock(lines, i);
|
|
210
|
+
if (tryBlock) {
|
|
211
|
+
tryBlocks.push({
|
|
212
|
+
type: 'TryStatement',
|
|
213
|
+
startLine: i + 1,
|
|
214
|
+
endLine: tryBlock.endLine,
|
|
215
|
+
tryBlock: tryBlock.tryBody,
|
|
216
|
+
catchBlock: tryBlock.catchBody,
|
|
217
|
+
finallyBlock: tryBlock.finallyBody,
|
|
218
|
+
language: language
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return { errorHandlingNodes: tryBlocks };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Java AST parsing
|
|
229
|
+
*/
|
|
230
|
+
parseJava(content) {
|
|
231
|
+
const javaNodes = [];
|
|
232
|
+
const lines = content.split('\n');
|
|
233
|
+
|
|
234
|
+
for (let i = 0; i < lines.length; i++) {
|
|
235
|
+
const line = lines[i];
|
|
236
|
+
|
|
237
|
+
// Java try-catch-finally
|
|
238
|
+
if (/\btry\s*\{/.test(line)) {
|
|
239
|
+
const tryBlock = this.extractJavaTryBlock(lines, i);
|
|
240
|
+
if (tryBlock) {
|
|
241
|
+
javaNodes.push({
|
|
242
|
+
type: 'TryStatement',
|
|
243
|
+
startLine: i + 1,
|
|
244
|
+
language: 'java',
|
|
245
|
+
tryBlock: tryBlock.tryBody,
|
|
246
|
+
catchBlocks: tryBlock.catchBlocks, // Java can have multiple catch blocks
|
|
247
|
+
finallyBlock: tryBlock.finallyBody,
|
|
248
|
+
exceptionTypes: tryBlock.exceptionTypes
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Method throws declaration
|
|
254
|
+
if (/throws\s+\w+/.test(line)) {
|
|
255
|
+
javaNodes.push({
|
|
256
|
+
type: 'ThrowsDeclaration',
|
|
257
|
+
startLine: i + 1,
|
|
258
|
+
language: 'java',
|
|
259
|
+
exceptions: this.extractThrowsExceptions(line)
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return { errorHandlingNodes: javaNodes };
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Kotlin AST parsing
|
|
269
|
+
*/
|
|
270
|
+
parseKotlin(content) {
|
|
271
|
+
const kotlinNodes = [];
|
|
272
|
+
const lines = content.split('\n');
|
|
273
|
+
|
|
274
|
+
for (let i = 0; i < lines.length; i++) {
|
|
275
|
+
const line = lines[i];
|
|
276
|
+
|
|
277
|
+
// Kotlin try-catch
|
|
278
|
+
if (/\btry\s*\{/.test(line)) {
|
|
279
|
+
const tryBlock = this.extractKotlinTryBlock(lines, i);
|
|
280
|
+
if (tryBlock) {
|
|
281
|
+
kotlinNodes.push({
|
|
282
|
+
type: 'TryExpression',
|
|
283
|
+
startLine: i + 1,
|
|
284
|
+
language: 'kotlin',
|
|
285
|
+
tryBlock: tryBlock.tryBody,
|
|
286
|
+
catchBlock: tryBlock.catchBody,
|
|
287
|
+
finallyBlock: tryBlock.finallyBody,
|
|
288
|
+
isExpression: tryBlock.isExpression
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Kotlin Result type usage
|
|
294
|
+
if (/Result</.test(line)) {
|
|
295
|
+
kotlinNodes.push({
|
|
296
|
+
type: 'ResultUsage',
|
|
297
|
+
startLine: i + 1,
|
|
298
|
+
language: 'kotlin',
|
|
299
|
+
resultType: this.extractResultType(line)
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return { errorHandlingNodes: kotlinNodes };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Dart AST parsing
|
|
309
|
+
*/
|
|
310
|
+
parseDart(content) {
|
|
311
|
+
const dartNodes = [];
|
|
312
|
+
const lines = content.split('\n');
|
|
313
|
+
|
|
314
|
+
for (let i = 0; i < lines.length; i++) {
|
|
315
|
+
const line = lines[i];
|
|
316
|
+
|
|
317
|
+
// Dart try-catch-on
|
|
318
|
+
if (/\btry\s*\{/.test(line)) {
|
|
319
|
+
const tryBlock = this.extractDartTryBlock(lines, i);
|
|
320
|
+
if (tryBlock) {
|
|
321
|
+
dartNodes.push({
|
|
322
|
+
type: 'TryStatement',
|
|
323
|
+
startLine: i + 1,
|
|
324
|
+
language: 'dart',
|
|
325
|
+
tryBlock: tryBlock.tryBody,
|
|
326
|
+
catchBlocks: tryBlock.catchBlocks,
|
|
327
|
+
onBlocks: tryBlock.onBlocks,
|
|
328
|
+
finallyBlock: tryBlock.finallyBody
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Future.catchError
|
|
334
|
+
if (/\.catchError/.test(line)) {
|
|
335
|
+
dartNodes.push({
|
|
336
|
+
type: 'FutureCatchError',
|
|
337
|
+
startLine: i + 1,
|
|
338
|
+
language: 'dart',
|
|
339
|
+
handler: this.extractCatchErrorHandler(line)
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return { errorHandlingNodes: dartNodes };
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Python AST parsing
|
|
349
|
+
*/
|
|
350
|
+
parsePython(content) {
|
|
351
|
+
const pythonNodes = [];
|
|
352
|
+
const lines = content.split('\n');
|
|
353
|
+
|
|
354
|
+
for (let i = 0; i < lines.length; i++) {
|
|
355
|
+
const line = lines[i].trim();
|
|
356
|
+
|
|
357
|
+
// Python try-except-finally
|
|
358
|
+
if (/^try:/.test(line)) {
|
|
359
|
+
const tryBlock = this.extractPythonTryBlock(lines, i);
|
|
360
|
+
if (tryBlock) {
|
|
361
|
+
pythonNodes.push({
|
|
362
|
+
type: 'TryStatement',
|
|
363
|
+
startLine: i + 1,
|
|
364
|
+
language: 'python',
|
|
365
|
+
tryBlock: tryBlock.tryBody,
|
|
366
|
+
exceptBlocks: tryBlock.exceptBlocks,
|
|
367
|
+
elseBlock: tryBlock.elseBody,
|
|
368
|
+
finallyBlock: tryBlock.finallyBody
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Context manager (with statement)
|
|
374
|
+
if (/^with\s+/.test(line)) {
|
|
375
|
+
pythonNodes.push({
|
|
376
|
+
type: 'WithStatement',
|
|
377
|
+
startLine: i + 1,
|
|
378
|
+
language: 'python',
|
|
379
|
+
contextManager: this.extractContextManager(line)
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return { errorHandlingNodes: pythonNodes };
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Go AST parsing
|
|
389
|
+
*/
|
|
390
|
+
parseGo(content) {
|
|
391
|
+
const goNodes = [];
|
|
392
|
+
const lines = content.split('\n');
|
|
393
|
+
|
|
394
|
+
for (let i = 0; i < lines.length; i++) {
|
|
395
|
+
const line = lines[i];
|
|
396
|
+
|
|
397
|
+
// Go error checking pattern
|
|
398
|
+
if (/if\s+err\s*!=\s*nil/.test(line)) {
|
|
399
|
+
goNodes.push({
|
|
400
|
+
type: 'ErrorCheck',
|
|
401
|
+
startLine: i + 1,
|
|
402
|
+
language: 'go',
|
|
403
|
+
errorVariable: 'err',
|
|
404
|
+
checkPattern: 'nil_comparison'
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Panic-recover pattern
|
|
409
|
+
if (/\bpanic\s*\(/.test(line)) {
|
|
410
|
+
goNodes.push({
|
|
411
|
+
type: 'PanicStatement',
|
|
412
|
+
startLine: i + 1,
|
|
413
|
+
language: 'go',
|
|
414
|
+
panicValue: this.extractPanicValue(line)
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (/\brecover\s*\(\s*\)/.test(line)) {
|
|
419
|
+
goNodes.push({
|
|
420
|
+
type: 'RecoverStatement',
|
|
421
|
+
startLine: i + 1,
|
|
422
|
+
language: 'go'
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return { errorHandlingNodes: goNodes };
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Analyze specific error handling node
|
|
432
|
+
*/
|
|
433
|
+
analyzeErrorHandlingNode(node, content, langConfig, language) {
|
|
434
|
+
switch (node.type) {
|
|
435
|
+
case 'TryStatement':
|
|
436
|
+
case 'TryExpression':
|
|
437
|
+
return this.analyzeTryStatement(node, content, langConfig, language);
|
|
438
|
+
|
|
439
|
+
case 'ErrorCheck':
|
|
440
|
+
return this.analyzeErrorCheck(node, content, langConfig, language);
|
|
441
|
+
|
|
442
|
+
case 'ThrowsDeclaration':
|
|
443
|
+
return this.analyzeThrowsDeclaration(node, content, langConfig, language);
|
|
444
|
+
|
|
445
|
+
case 'ResultUsage':
|
|
446
|
+
return this.analyzeResultUsage(node, content, langConfig, language);
|
|
447
|
+
|
|
448
|
+
case 'FutureCatchError':
|
|
449
|
+
return this.analyzeFutureCatchError(node, content, langConfig, language);
|
|
450
|
+
|
|
451
|
+
case 'WithStatement':
|
|
452
|
+
return this.analyzeWithStatement(node, content, langConfig, language);
|
|
453
|
+
|
|
454
|
+
case 'PanicStatement':
|
|
455
|
+
return this.analyzePanicStatement(node, content, langConfig, language);
|
|
456
|
+
|
|
457
|
+
default:
|
|
458
|
+
return { isViolation: false, reason: 'unknown_node_type' };
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Analyze try statement (universal pattern)
|
|
464
|
+
*/
|
|
465
|
+
analyzeTryStatement(node, content, langConfig, language) {
|
|
466
|
+
const catchBlock = node.catchBlock || node.catchBlocks?.[0];
|
|
467
|
+
|
|
468
|
+
if (!catchBlock || this.isEmpty(catchBlock)) {
|
|
469
|
+
return {
|
|
470
|
+
isViolation: true,
|
|
471
|
+
type: 'empty_catch_block',
|
|
472
|
+
message: `Empty ${langConfig.keywords.catch || 'catch'} block in ${language}`,
|
|
473
|
+
severity: 'error',
|
|
474
|
+
line: node.startLine,
|
|
475
|
+
column: 1,
|
|
476
|
+
code: this.getNodeCode(node, content),
|
|
477
|
+
confidence: 0.9,
|
|
478
|
+
suggestion: this.generateLanguageSpecificSuggestion(language, 'empty_catch'),
|
|
479
|
+
pattern: 'try-catch',
|
|
480
|
+
insight: `Cross-language analysis: Empty error handling detected`
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Check for logging
|
|
485
|
+
const hasLogging = this.hasErrorLogging(catchBlock, langConfig, language);
|
|
486
|
+
if (!hasLogging) {
|
|
487
|
+
return {
|
|
488
|
+
isViolation: true,
|
|
489
|
+
type: 'missing_error_logging',
|
|
490
|
+
message: `Missing error logging in ${language} ${langConfig.keywords.catch || 'catch'} block`,
|
|
491
|
+
severity: 'warning',
|
|
492
|
+
line: node.startLine,
|
|
493
|
+
column: 1,
|
|
494
|
+
code: this.getNodeCode(node, content),
|
|
495
|
+
confidence: 0.7,
|
|
496
|
+
suggestion: this.generateLanguageSpecificSuggestion(language, 'add_logging'),
|
|
497
|
+
pattern: 'try-catch',
|
|
498
|
+
insight: `Cross-language best practice: Add error logging for debugging`
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
return { isViolation: false, reason: 'proper_error_handling' };
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Analyze Go error check pattern
|
|
507
|
+
*/
|
|
508
|
+
analyzeErrorCheck(node, content, langConfig, language) {
|
|
509
|
+
const lines = content.split('\n');
|
|
510
|
+
const errorHandlingLine = lines[node.startLine]; // Line after the error check
|
|
511
|
+
|
|
512
|
+
if (!errorHandlingLine || errorHandlingLine.trim() === '') {
|
|
513
|
+
return {
|
|
514
|
+
isViolation: true,
|
|
515
|
+
type: 'go_empty_error_handling',
|
|
516
|
+
message: 'Empty error handling after error check in Go',
|
|
517
|
+
severity: 'error',
|
|
518
|
+
line: node.startLine + 1,
|
|
519
|
+
column: 1,
|
|
520
|
+
code: lines.slice(node.startLine - 1, node.startLine + 2).join('\n'),
|
|
521
|
+
confidence: 0.9,
|
|
522
|
+
suggestion: 'Add error handling logic (logging, return, panic, etc.)',
|
|
523
|
+
pattern: 'error-return',
|
|
524
|
+
insight: 'Go idiom: Always handle checked errors explicitly'
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
return { isViolation: false, reason: 'go_error_handled' };
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Generate language-specific suggestions
|
|
533
|
+
*/
|
|
534
|
+
generateLanguageSpecificSuggestion(language, violationType) {
|
|
535
|
+
const suggestions = {
|
|
536
|
+
'javascript': {
|
|
537
|
+
'empty_catch': 'Add console.error(error) or appropriate error handling',
|
|
538
|
+
'add_logging': 'Consider adding console.error(error) or using a logging library'
|
|
539
|
+
},
|
|
540
|
+
'typescript': {
|
|
541
|
+
'empty_catch': 'Add console.error(error) with proper typing',
|
|
542
|
+
'add_logging': 'Consider using a typed logging interface'
|
|
543
|
+
},
|
|
544
|
+
'java': {
|
|
545
|
+
'empty_catch': 'Add logger.error() or throw new RuntimeException()',
|
|
546
|
+
'add_logging': 'Use logger.error() or System.err.println()'
|
|
547
|
+
},
|
|
548
|
+
'kotlin': {
|
|
549
|
+
'empty_catch': 'Add println(error) or use Result<T> for error handling',
|
|
550
|
+
'add_logging': 'Consider using logging framework or Result type'
|
|
551
|
+
},
|
|
552
|
+
'dart': {
|
|
553
|
+
'empty_catch': 'Add print(error) or rethrow for debugging',
|
|
554
|
+
'add_logging': 'Use print() for simple cases or logging package'
|
|
555
|
+
},
|
|
556
|
+
'python': {
|
|
557
|
+
'empty_catch': 'Add print(e) or logging.error(e)',
|
|
558
|
+
'add_logging': 'Use logging.exception() to include stack trace'
|
|
559
|
+
},
|
|
560
|
+
'go': {
|
|
561
|
+
'empty_catch': 'Add fmt.Printf("Error: %v", err) or return err',
|
|
562
|
+
'add_logging': 'Use log.Printf() or return the error'
|
|
563
|
+
},
|
|
564
|
+
'rust': {
|
|
565
|
+
'empty_catch': 'Use eprintln!() or return Err() for proper error handling',
|
|
566
|
+
'add_logging': 'Consider using log crate or eprintln! macro'
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
|
|
570
|
+
return suggestions[language]?.[violationType] || 'Add appropriate error handling for this language';
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// Helper methods for language-specific parsing
|
|
574
|
+
extractJSTryBlock(lines, startIndex) {
|
|
575
|
+
// Simplified extraction for JavaScript/TypeScript try blocks
|
|
576
|
+
let braceCount = 0;
|
|
577
|
+
let inTry = false;
|
|
578
|
+
let tryBody = '';
|
|
579
|
+
let catchBody = '';
|
|
580
|
+
let finallyBody = '';
|
|
581
|
+
let currentSection = 'try';
|
|
582
|
+
|
|
583
|
+
for (let i = startIndex; i < lines.length; i++) {
|
|
584
|
+
const line = lines[i];
|
|
585
|
+
|
|
586
|
+
// Count braces to find block boundaries
|
|
587
|
+
braceCount += (line.match(/\{/g) || []).length;
|
|
588
|
+
braceCount -= (line.match(/\}/g) || []).length;
|
|
589
|
+
|
|
590
|
+
if (line.includes('catch')) currentSection = 'catch';
|
|
591
|
+
if (line.includes('finally')) currentSection = 'finally';
|
|
592
|
+
|
|
593
|
+
if (currentSection === 'try') tryBody += line + '\n';
|
|
594
|
+
else if (currentSection === 'catch') catchBody += line + '\n';
|
|
595
|
+
else if (currentSection === 'finally') finallyBody += line + '\n';
|
|
596
|
+
|
|
597
|
+
if (braceCount === 0 && i > startIndex) {
|
|
598
|
+
return {
|
|
599
|
+
endLine: i + 1,
|
|
600
|
+
tryBody: tryBody.trim(),
|
|
601
|
+
catchBody: catchBody.trim(),
|
|
602
|
+
finallyBody: finallyBody.trim()
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
return null;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
isEmpty(blockContent) {
|
|
611
|
+
if (!blockContent) return true;
|
|
612
|
+
|
|
613
|
+
const cleaned = blockContent
|
|
614
|
+
.replace(/\/\*[\s\S]*?\*\//g, '') // Remove block comments
|
|
615
|
+
.replace(/\/\/.*$/gm, '') // Remove line comments
|
|
616
|
+
.replace(/\s+/g, '') // Remove whitespace
|
|
617
|
+
.replace(/[{}]/g, ''); // Remove braces
|
|
618
|
+
|
|
619
|
+
return cleaned.length === 0;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
hasErrorLogging(blockContent, langConfig, language) {
|
|
623
|
+
if (!blockContent) return false;
|
|
624
|
+
|
|
625
|
+
// Language-specific logging patterns
|
|
626
|
+
const loggingPatterns = {
|
|
627
|
+
'javascript': /console\.(log|error|warn|debug)/,
|
|
628
|
+
'typescript': /console\.(log|error|warn|debug)/,
|
|
629
|
+
'java': /(logger?\.(error|warn|info|debug)|System\.(err|out)\.print)/,
|
|
630
|
+
'kotlin': /(println|print|Log\.[dewi])/,
|
|
631
|
+
'dart': /(print|debugPrint|log\.)/,
|
|
632
|
+
'python': /(print|logging\.(error|warning|info|debug)|logger\.(error|warning))/,
|
|
633
|
+
'go': /(fmt\.(Print|Error)|log\.(Print|Error))/,
|
|
634
|
+
'rust': /(println!|eprintln!|log::|debug!|error!)/
|
|
635
|
+
};
|
|
636
|
+
|
|
637
|
+
const pattern = loggingPatterns[language.toLowerCase()];
|
|
638
|
+
return pattern ? pattern.test(blockContent) : false;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
getNodeCode(node, content) {
|
|
642
|
+
const lines = content.split('\n');
|
|
643
|
+
const startLine = Math.max(0, node.startLine - 1);
|
|
644
|
+
const endLine = Math.min(lines.length, node.endLine || node.startLine + 2);
|
|
645
|
+
|
|
646
|
+
return lines.slice(startLine, endLine).join('\n');
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
fallbackAnalysis(content, filePath, language, langConfig) {
|
|
650
|
+
// Fallback to simple regex-based analysis when AST parsing fails
|
|
651
|
+
console.log(`📄 Falling back to regex analysis for ${filePath}`);
|
|
652
|
+
|
|
653
|
+
const violations = [];
|
|
654
|
+
const lines = content.split('\n');
|
|
655
|
+
|
|
656
|
+
for (let i = 0; i < lines.length; i++) {
|
|
657
|
+
const line = lines[i];
|
|
658
|
+
|
|
659
|
+
// Universal empty catch pattern
|
|
660
|
+
if (/catch\s*\([^)]*\)\s*\{\s*\}/.test(line)) {
|
|
661
|
+
violations.push({
|
|
662
|
+
ruleId: this.ruleId,
|
|
663
|
+
file: filePath,
|
|
664
|
+
line: i + 1,
|
|
665
|
+
column: 1,
|
|
666
|
+
message: `Empty catch block detected (${language} - fallback analysis)`,
|
|
667
|
+
severity: 'error',
|
|
668
|
+
code: line.trim(),
|
|
669
|
+
type: 'empty_catch_fallback',
|
|
670
|
+
confidence: 0.6,
|
|
671
|
+
suggestion: this.generateLanguageSpecificSuggestion(language, 'empty_catch'),
|
|
672
|
+
language: language,
|
|
673
|
+
crossLanguageInsight: 'Fallback analysis - consider AST-based analysis for better accuracy'
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
return violations;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// Placeholder methods for additional language parsers (to be implemented)
|
|
682
|
+
parseRust(content) { return { errorHandlingNodes: [] }; }
|
|
683
|
+
extractJavaTryBlock(lines, startIndex) { return null; }
|
|
684
|
+
extractKotlinTryBlock(lines, startIndex) { return null; }
|
|
685
|
+
extractDartTryBlock(lines, startIndex) { return null; }
|
|
686
|
+
extractPythonTryBlock(lines, startIndex) { return null; }
|
|
687
|
+
extractThrowsExceptions(line) { return []; }
|
|
688
|
+
extractResultType(line) { return ''; }
|
|
689
|
+
extractCatchErrorHandler(line) { return ''; }
|
|
690
|
+
extractContextManager(line) { return ''; }
|
|
691
|
+
extractPanicValue(line) { return ''; }
|
|
692
|
+
extractNodesForPattern(ast, pattern, langConfig) { return []; }
|
|
693
|
+
analyzeThrowsDeclaration(node, content, langConfig, language) { return { isViolation: false }; }
|
|
694
|
+
analyzeResultUsage(node, content, langConfig, language) { return { isViolation: false }; }
|
|
695
|
+
analyzeFutureCatchError(node, content, langConfig, language) { return { isViolation: false }; }
|
|
696
|
+
analyzeWithStatement(node, content, langConfig, language) { return { isViolation: false }; }
|
|
697
|
+
analyzePanicStatement(node, content, langConfig, language) { return { isViolation: false }; }
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
module.exports = new C029MultiLanguageASTEngine();
|