code-simplifier 1.1.0 → 1.2.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/lib/ai-auto-fix.js +829 -0
- package/lib/ai-code-review.js +705 -0
- package/lib/ai-knowledge-base.js +760 -0
- package/lib/ai-quality-analyzer.js +1080 -0
- package/lib/ai-refactor-advisor.js +853 -0
- package/lib/ai-trend-analyzer.js +682 -0
- package/lib/ralph-integration.js +550 -0
- package/package.json +87 -77
|
@@ -0,0 +1,1080 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AI-Powered Code Quality Analyzer
|
|
5
|
+
* Supports analyzing code quality across 11+ languages using AI
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs-extra')
|
|
9
|
+
const path = require('path')
|
|
10
|
+
const chalk = require('chalk')
|
|
11
|
+
const ora = require('ora')
|
|
12
|
+
const { OpenAI } = require('openai')
|
|
13
|
+
const Anthropic = require('@anthropic-ai/sdk')
|
|
14
|
+
|
|
15
|
+
// Code smell patterns for different languages
|
|
16
|
+
const CODE_SMELLS = {
|
|
17
|
+
// Code Structure Issues
|
|
18
|
+
longFunction: {
|
|
19
|
+
name: 'Long Function',
|
|
20
|
+
description: 'Function exceeds recommended length',
|
|
21
|
+
severity: 'warning',
|
|
22
|
+
category: 'structure'
|
|
23
|
+
},
|
|
24
|
+
largeClass: {
|
|
25
|
+
name: 'Large Class',
|
|
26
|
+
description: 'Class has too many responsibilities',
|
|
27
|
+
severity: 'warning',
|
|
28
|
+
category: 'structure'
|
|
29
|
+
},
|
|
30
|
+
tooManyParameters: {
|
|
31
|
+
name: 'Too Many Parameters',
|
|
32
|
+
description: 'Function has excessive parameters',
|
|
33
|
+
severity: 'warning',
|
|
34
|
+
category: 'structure'
|
|
35
|
+
},
|
|
36
|
+
duplicateCode: {
|
|
37
|
+
name: 'Duplicate Code',
|
|
38
|
+
description: 'Code is duplicated across multiple locations',
|
|
39
|
+
severity: 'error',
|
|
40
|
+
category: 'maintainability'
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
// Naming Issues
|
|
44
|
+
badVariableNames: {
|
|
45
|
+
name: 'Poor Variable Names',
|
|
46
|
+
description: 'Variable names are not descriptive',
|
|
47
|
+
severity: 'warning',
|
|
48
|
+
category: 'naming'
|
|
49
|
+
},
|
|
50
|
+
badFunctionNames: {
|
|
51
|
+
name: 'Poor Function Names',
|
|
52
|
+
description: 'Function names are not descriptive',
|
|
53
|
+
severity: 'warning',
|
|
54
|
+
category: 'naming'
|
|
55
|
+
},
|
|
56
|
+
magicNumbers: {
|
|
57
|
+
name: 'Magic Numbers',
|
|
58
|
+
description: 'Unnamed numerical constants in code',
|
|
59
|
+
severity: 'warning',
|
|
60
|
+
category: 'maintainability'
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
// Complexity Issues
|
|
64
|
+
highComplexity: {
|
|
65
|
+
name: 'High Cyclomatic Complexity',
|
|
66
|
+
description: 'Function has too many decision points',
|
|
67
|
+
severity: 'error',
|
|
68
|
+
category: 'complexity'
|
|
69
|
+
},
|
|
70
|
+
nestedConditionals: {
|
|
71
|
+
name: 'Deeply Nested Conditionals',
|
|
72
|
+
description: 'Excessive nesting reduces readability',
|
|
73
|
+
severity: 'warning',
|
|
74
|
+
category: 'complexity'
|
|
75
|
+
},
|
|
76
|
+
arrowAntiPattern: {
|
|
77
|
+
name: 'Arrow Anti-Pattern',
|
|
78
|
+
description: 'Excessive callback nesting',
|
|
79
|
+
severity: 'warning',
|
|
80
|
+
category: 'complexity'
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
// Code Smells
|
|
84
|
+
godObject: {
|
|
85
|
+
name: 'God Object',
|
|
86
|
+
description: 'Class knows too much or does too much',
|
|
87
|
+
severity: 'error',
|
|
88
|
+
category: 'design'
|
|
89
|
+
},
|
|
90
|
+
featureEnvy: {
|
|
91
|
+
name: 'Feature Envy',
|
|
92
|
+
description: 'Class uses methods of another class excessively',
|
|
93
|
+
severity: 'warning',
|
|
94
|
+
category: 'design'
|
|
95
|
+
},
|
|
96
|
+
dataClass: {
|
|
97
|
+
name: 'Data Class',
|
|
98
|
+
description: 'Class with only data and no behavior',
|
|
99
|
+
severity: 'warning',
|
|
100
|
+
category: 'design'
|
|
101
|
+
},
|
|
102
|
+
deadCode: {
|
|
103
|
+
name: 'Dead Code',
|
|
104
|
+
description: 'Code that is never executed',
|
|
105
|
+
severity: 'warning',
|
|
106
|
+
category: 'maintainability'
|
|
107
|
+
},
|
|
108
|
+
commentedCode: {
|
|
109
|
+
name: 'Commented Code',
|
|
110
|
+
description: 'Old commented-out code',
|
|
111
|
+
severity: 'info',
|
|
112
|
+
category: 'maintainability'
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
// Performance Issues
|
|
116
|
+
inefficientLoop: {
|
|
117
|
+
name: 'Inefficient Loop',
|
|
118
|
+
description: 'Suboptimal loop implementation',
|
|
119
|
+
severity: 'warning',
|
|
120
|
+
category: 'performance'
|
|
121
|
+
},
|
|
122
|
+
stringConcatenation: {
|
|
123
|
+
name: 'String Concatenation in Loop',
|
|
124
|
+
description: 'Inefficient string concatenation',
|
|
125
|
+
severity: 'warning',
|
|
126
|
+
category: 'performance'
|
|
127
|
+
},
|
|
128
|
+
memoryLeak: {
|
|
129
|
+
name: 'Potential Memory Leak',
|
|
130
|
+
description: 'May cause memory leaks',
|
|
131
|
+
severity: 'error',
|
|
132
|
+
category: 'performance'
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
// Security Issues
|
|
136
|
+
sqlInjection: {
|
|
137
|
+
name: 'SQL Injection Risk',
|
|
138
|
+
description: 'Potential SQL injection vulnerability',
|
|
139
|
+
severity: 'error',
|
|
140
|
+
category: 'security'
|
|
141
|
+
},
|
|
142
|
+
xssRisk: {
|
|
143
|
+
name: 'XSS Risk',
|
|
144
|
+
description: 'Potential cross-site scripting vulnerability',
|
|
145
|
+
severity: 'error',
|
|
146
|
+
category: 'security'
|
|
147
|
+
},
|
|
148
|
+
hardcodedSecrets: {
|
|
149
|
+
name: 'Hardcoded Secrets',
|
|
150
|
+
description: 'Credentials or keys in source code',
|
|
151
|
+
severity: 'error',
|
|
152
|
+
category: 'security'
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
// Best Practices
|
|
156
|
+
missingErrorHandling: {
|
|
157
|
+
name: 'Missing Error Handling',
|
|
158
|
+
description: 'No error handling for risky operations',
|
|
159
|
+
severity: 'warning',
|
|
160
|
+
category: 'best-practice'
|
|
161
|
+
},
|
|
162
|
+
missingValidation: {
|
|
163
|
+
name: 'Missing Input Validation',
|
|
164
|
+
description: 'User input not validated',
|
|
165
|
+
severity: 'warning',
|
|
166
|
+
category: 'best-practice'
|
|
167
|
+
},
|
|
168
|
+
missingDocumentation: {
|
|
169
|
+
name: 'Missing Documentation',
|
|
170
|
+
description: 'Function/class lacks documentation',
|
|
171
|
+
severity: 'info',
|
|
172
|
+
category: 'documentation'
|
|
173
|
+
},
|
|
174
|
+
inconsistentStyle: {
|
|
175
|
+
name: 'Inconsistent Code Style',
|
|
176
|
+
description: 'Code style inconsistent with project',
|
|
177
|
+
severity: 'info',
|
|
178
|
+
category: 'style'
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const SUPPORTED_LANGUAGES = [
|
|
183
|
+
'javascript',
|
|
184
|
+
'typescript',
|
|
185
|
+
'python',
|
|
186
|
+
'java',
|
|
187
|
+
'csharp',
|
|
188
|
+
'cpp',
|
|
189
|
+
'php',
|
|
190
|
+
'ruby',
|
|
191
|
+
'go',
|
|
192
|
+
'rust',
|
|
193
|
+
'swift',
|
|
194
|
+
'kotlin'
|
|
195
|
+
]
|
|
196
|
+
|
|
197
|
+
class AIQualityAnalyzer {
|
|
198
|
+
constructor() {
|
|
199
|
+
this.openai = null
|
|
200
|
+
this.anthropic = null
|
|
201
|
+
this.apiProvider = process.env.AI_PROVIDER || 'openai'
|
|
202
|
+
this.apiKey = null
|
|
203
|
+
|
|
204
|
+
// Initialize API clients
|
|
205
|
+
this.initializeAPIs()
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Initialize API clients
|
|
210
|
+
*/
|
|
211
|
+
initializeAPIs() {
|
|
212
|
+
try {
|
|
213
|
+
const openaiKey = process.env.OPENAI_API_KEY
|
|
214
|
+
const anthropicKey = process.env.ANTHROPIC_API_KEY
|
|
215
|
+
|
|
216
|
+
if (openaiKey && (this.apiProvider === 'openai' || !anthropicKey)) {
|
|
217
|
+
this.openai = new OpenAI({ apiKey: openaiKey })
|
|
218
|
+
this.apiKey = openaiKey
|
|
219
|
+
this.currentProvider = 'openai'
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (anthropicKey && (this.apiProvider === 'anthropic' || !openaiKey)) {
|
|
223
|
+
this.anthropic = new Anthropic({ apiKey: anthropicKey })
|
|
224
|
+
this.apiKey = anthropicKey
|
|
225
|
+
this.currentProvider = 'anthropic'
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (!this.openai && !this.anthropic) {
|
|
229
|
+
console.warn(chalk.yellow('⚠️ No AI API keys found. Using mock mode for testing.'))
|
|
230
|
+
this.mockMode = true
|
|
231
|
+
}
|
|
232
|
+
} catch (error) {
|
|
233
|
+
console.warn(chalk.yellow('⚠️ Failed to initialize AI APIs. Using mock mode.'))
|
|
234
|
+
this.mockMode = true
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Analyze a single file
|
|
240
|
+
*/
|
|
241
|
+
async analyzeFile(filePath, options = {}) {
|
|
242
|
+
try {
|
|
243
|
+
const content = await fs.readFile(filePath, 'utf8')
|
|
244
|
+
const language = this.detectLanguage(filePath)
|
|
245
|
+
|
|
246
|
+
if (!language || !SUPPORTED_LANGUAGES.includes(language)) {
|
|
247
|
+
throw new Error(`Unsupported or unknown language for file: ${filePath}`)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const issues = this.detectCodeSmells(content, language, filePath)
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
file: filePath,
|
|
254
|
+
language,
|
|
255
|
+
issues,
|
|
256
|
+
summary: {
|
|
257
|
+
total: issues.length,
|
|
258
|
+
errors: issues.filter(i => i.severity === 'error').length,
|
|
259
|
+
warnings: issues.filter(i => i.severity === 'warning').length,
|
|
260
|
+
info: issues.filter(i => i.severity === 'info').length
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
} catch (error) {
|
|
264
|
+
throw new Error(`Failed to analyze file ${filePath}: ${error.message}`)
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Analyze multiple files
|
|
270
|
+
*/
|
|
271
|
+
async analyzeFiles(filePaths, options = {}) {
|
|
272
|
+
const spinner = ora(chalk.blue('Analyzing files with AI...')).start()
|
|
273
|
+
const results = []
|
|
274
|
+
const errors = []
|
|
275
|
+
|
|
276
|
+
try {
|
|
277
|
+
for (const filePath of filePaths) {
|
|
278
|
+
try {
|
|
279
|
+
const result = await this.analyzeFile(filePath, options)
|
|
280
|
+
results.push(result)
|
|
281
|
+
spinner.text = `Analyzed ${results.length}/${filePaths.length} files`
|
|
282
|
+
} catch (error) {
|
|
283
|
+
errors.push({ file: filePath, error: error.message })
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
spinner.succeed(chalk.green(`Analysis complete: ${results.length} files analyzed`))
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
results,
|
|
291
|
+
errors,
|
|
292
|
+
summary: this.generateSummary(results)
|
|
293
|
+
}
|
|
294
|
+
} catch (error) {
|
|
295
|
+
spinner.fail(chalk.red('Analysis failed'))
|
|
296
|
+
throw error
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Detect programming language from file extension
|
|
302
|
+
*/
|
|
303
|
+
detectLanguage(filePath) {
|
|
304
|
+
const ext = path.extname(filePath).toLowerCase()
|
|
305
|
+
const languageMap = {
|
|
306
|
+
'.js': 'javascript',
|
|
307
|
+
'.jsx': 'javascript',
|
|
308
|
+
'.ts': 'typescript',
|
|
309
|
+
'.tsx': 'typescript',
|
|
310
|
+
'.py': 'python',
|
|
311
|
+
'.java': 'java',
|
|
312
|
+
'.cs': 'csharp',
|
|
313
|
+
'.cpp': 'cpp',
|
|
314
|
+
'.cc': 'cpp',
|
|
315
|
+
'.cxx': 'cpp',
|
|
316
|
+
'.h': 'cpp',
|
|
317
|
+
'.hpp': 'cpp',
|
|
318
|
+
'.php': 'php',
|
|
319
|
+
'.rb': 'ruby',
|
|
320
|
+
'.go': 'go',
|
|
321
|
+
'.rs': 'rust',
|
|
322
|
+
'.swift': 'swift',
|
|
323
|
+
'.kt': 'kotlin',
|
|
324
|
+
'.kts': 'kotlin'
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return languageMap[ext]
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Detect code smells in code
|
|
332
|
+
*/
|
|
333
|
+
detectCodeSmells(code, language, filePath) {
|
|
334
|
+
const issues = []
|
|
335
|
+
const lines = code.split('\n')
|
|
336
|
+
|
|
337
|
+
// Check for various code smells
|
|
338
|
+
issues.push(...this.checkLongFunctions(code, language, filePath))
|
|
339
|
+
issues.push(...this.checkLargeClasses(code, language, filePath))
|
|
340
|
+
issues.push(...this.checkTooManyParameters(code, language, filePath))
|
|
341
|
+
issues.push(...this.checkMagicNumbers(code, language, filePath))
|
|
342
|
+
issues.push(...this.checkHighComplexity(code, language, filePath))
|
|
343
|
+
issues.push(...this.checkNestedConditionals(code, language, filePath))
|
|
344
|
+
issues.push(...this.checkDeadCode(code, language, filePath))
|
|
345
|
+
issues.push(...this.checkCommentedCode(code, language, filePath))
|
|
346
|
+
issues.push(...this.checkSecurityIssues(code, language, filePath))
|
|
347
|
+
issues.push(...this.checkPerformanceIssues(code, language, filePath))
|
|
348
|
+
issues.push(...this.checkNamingIssues(code, language, filePath))
|
|
349
|
+
issues.push(...this.checkBestPractices(code, language, filePath))
|
|
350
|
+
|
|
351
|
+
return issues
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Check for long functions
|
|
356
|
+
*/
|
|
357
|
+
checkLongFunctions(code, language, filePath) {
|
|
358
|
+
const issues = []
|
|
359
|
+
const lines = code.split('\n')
|
|
360
|
+
|
|
361
|
+
// Detect function definitions and their lengths
|
|
362
|
+
const functionRegex = this.getFunctionRegex(language)
|
|
363
|
+
const functionRegexNoGlobal = new RegExp(functionRegex.source, functionRegex.flags.replace('g', ''))
|
|
364
|
+
let match
|
|
365
|
+
|
|
366
|
+
while ((match = functionRegex.exec(code)) !== null) {
|
|
367
|
+
const functionStart = code.substring(0, match.index).split('\n').length
|
|
368
|
+
const functionBody = this.extractFunctionBody(code, match.index, language)
|
|
369
|
+
const functionLines = functionBody.split('\n').length
|
|
370
|
+
|
|
371
|
+
if (functionLines > 50) {
|
|
372
|
+
issues.push({
|
|
373
|
+
type: 'longFunction',
|
|
374
|
+
name: CODE_SMELLS.longFunction.name,
|
|
375
|
+
severity: CODE_SMELLS.longFunction.severity,
|
|
376
|
+
category: CODE_SMELLS.longFunction.category,
|
|
377
|
+
line: functionStart,
|
|
378
|
+
message: `Function is ${functionLines} lines (recommended: < 50)`,
|
|
379
|
+
fix: 'Break down into smaller, more focused functions'
|
|
380
|
+
})
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return issues
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Check for large classes
|
|
389
|
+
*/
|
|
390
|
+
checkLargeClasses(code, language, filePath) {
|
|
391
|
+
const issues = []
|
|
392
|
+
const lines = code.split('\n')
|
|
393
|
+
|
|
394
|
+
if (language === 'java' || language === 'csharp') {
|
|
395
|
+
const classRegex = /(?:class|interface)\s+(\w+)/g
|
|
396
|
+
let match
|
|
397
|
+
|
|
398
|
+
while ((match = classRegex.exec(code)) !== null) {
|
|
399
|
+
const classStart = code.substring(0, match.index).split('\n').length
|
|
400
|
+
// Count lines until next class or end of file
|
|
401
|
+
const afterClass = code.substring(match.index)
|
|
402
|
+
const classLines = afterClass.split('\n').findIndex((line, idx) =>
|
|
403
|
+
idx > 0 && (line.match(/^\s*(class|interface)\s+\w+/) || line.match(/^\s*}/))
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
if (classLines > 200) {
|
|
407
|
+
issues.push({
|
|
408
|
+
type: 'largeClass',
|
|
409
|
+
name: CODE_SMELLS.largeClass.name,
|
|
410
|
+
severity: CODE_SMELLS.largeClass.severity,
|
|
411
|
+
category: CODE_SMELLS.largeClass.category,
|
|
412
|
+
line: classStart,
|
|
413
|
+
message: `Class has ~${classLines} lines (recommended: < 200)`,
|
|
414
|
+
fix: 'Split into smaller, cohesive classes'
|
|
415
|
+
})
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return issues
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Check for too many parameters
|
|
425
|
+
*/
|
|
426
|
+
checkTooManyParameters(code, language, filePath) {
|
|
427
|
+
const issues = []
|
|
428
|
+
const functionRegex = this.getFunctionRegex(language)
|
|
429
|
+
const functionRegexNoGlobal = new RegExp(functionRegex.source, functionRegex.flags.replace('g', ''))
|
|
430
|
+
let match
|
|
431
|
+
|
|
432
|
+
while ((match = functionRegex.exec(code)) !== null) {
|
|
433
|
+
const functionStartLine = code.substring(0, match.index).split('\n').length
|
|
434
|
+
const codeFromFunction = code.substring(match.index)
|
|
435
|
+
const firstLine = codeFromFunction.split('\n')[0]
|
|
436
|
+
|
|
437
|
+
// Extract parameter list from the first line
|
|
438
|
+
const paramMatch = firstLine.match(/\(([^)]*)\)/)
|
|
439
|
+
|
|
440
|
+
if (paramMatch) {
|
|
441
|
+
const params = paramMatch[1].trim()
|
|
442
|
+
// Handle destructuring parameters and default values
|
|
443
|
+
const paramList = params.split(',')
|
|
444
|
+
.map(p => p.trim())
|
|
445
|
+
.filter(p => p.length > 0)
|
|
446
|
+
|
|
447
|
+
if (paramList.length > 5) {
|
|
448
|
+
issues.push({
|
|
449
|
+
type: 'tooManyParameters',
|
|
450
|
+
name: CODE_SMELLS.tooManyParameters.name,
|
|
451
|
+
severity: CODE_SMELLS.tooManyParameters.severity,
|
|
452
|
+
category: CODE_SMELLS.tooManyParameters.category,
|
|
453
|
+
line: functionStartLine,
|
|
454
|
+
message: `Function has ${paramList.length} parameters (recommended: < 5)`,
|
|
455
|
+
fix: 'Use parameter object or break down function'
|
|
456
|
+
})
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return issues
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Check for magic numbers
|
|
466
|
+
*/
|
|
467
|
+
checkMagicNumbers(code, language, filePath) {
|
|
468
|
+
const issues = []
|
|
469
|
+
const lines = code.split('\n')
|
|
470
|
+
|
|
471
|
+
lines.forEach((line, index) => {
|
|
472
|
+
// Skip if line already has issue
|
|
473
|
+
if (line.includes('//') || line.includes('#')) return
|
|
474
|
+
|
|
475
|
+
// Find standalone numbers (not in strings or comments)
|
|
476
|
+
const numberRegex = /\b(\d{4,})\b/g
|
|
477
|
+
let match
|
|
478
|
+
|
|
479
|
+
while ((match = numberRegex.exec(line)) !== null) {
|
|
480
|
+
const number = match[1]
|
|
481
|
+
|
|
482
|
+
// Skip version numbers, dates, etc.
|
|
483
|
+
if (!['2020', '2021', '2022', '2023', '2024'].includes(number)) {
|
|
484
|
+
issues.push({
|
|
485
|
+
type: 'magicNumbers',
|
|
486
|
+
name: CODE_SMELLS.magicNumbers.name,
|
|
487
|
+
severity: CODE_SMELLS.magicNumbers.severity,
|
|
488
|
+
category: CODE_SMELLS.magicNumbers.category,
|
|
489
|
+
line: index + 1,
|
|
490
|
+
message: `Magic number: ${number}`,
|
|
491
|
+
fix: 'Extract to a named constant'
|
|
492
|
+
})
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
})
|
|
496
|
+
|
|
497
|
+
return issues
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Check for high cyclomatic complexity
|
|
502
|
+
*/
|
|
503
|
+
checkHighComplexity(code, language, filePath) {
|
|
504
|
+
const issues = []
|
|
505
|
+
const lines = code.split('\n')
|
|
506
|
+
|
|
507
|
+
// Track nested structures by counting braces and if statements
|
|
508
|
+
let braceDepth = 0
|
|
509
|
+
let ifDepth = 0
|
|
510
|
+
let functionStartLine = 0
|
|
511
|
+
let inFunction = false
|
|
512
|
+
let maxNestingDepth = 0
|
|
513
|
+
|
|
514
|
+
lines.forEach((line, index) => {
|
|
515
|
+
const trimmed = line.trim()
|
|
516
|
+
|
|
517
|
+
// Check for function definition
|
|
518
|
+
const functionRegex = this.getFunctionRegex(language)
|
|
519
|
+
const functionRegexNoGlobal = new RegExp(functionRegex.source, functionRegex.flags.replace('g', ''))
|
|
520
|
+
if (functionRegexNoGlobal.test(line)) {
|
|
521
|
+
inFunction = true
|
|
522
|
+
functionStartLine = index + 1
|
|
523
|
+
braceDepth = 0
|
|
524
|
+
ifDepth = 0
|
|
525
|
+
maxNestingDepth = 0
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
if (inFunction) {
|
|
529
|
+
// Count opening braces
|
|
530
|
+
const openBraces = (line.match(/{/g) || []).length
|
|
531
|
+
const closeBraces = (line.match(/}/g) || []).length
|
|
532
|
+
braceDepth += openBraces - closeBraces
|
|
533
|
+
|
|
534
|
+
// Track if statement nesting
|
|
535
|
+
if (trimmed.match(/if\s*\(/)) {
|
|
536
|
+
ifDepth++
|
|
537
|
+
if (ifDepth > maxNestingDepth) {
|
|
538
|
+
maxNestingDepth = ifDepth
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// Check for function end (braceDepth returns to 0 or less)
|
|
543
|
+
if (braceDepth <= 0 && inFunction && index > functionStartLine) {
|
|
544
|
+
// Calculate complexity based on nesting depth
|
|
545
|
+
const complexity = ifDepth + 1 // Base complexity + nesting
|
|
546
|
+
|
|
547
|
+
if (complexity > 10 || maxNestingDepth > 5) {
|
|
548
|
+
issues.push({
|
|
549
|
+
type: 'highComplexity',
|
|
550
|
+
name: CODE_SMELLS.highComplexity.name,
|
|
551
|
+
severity: CODE_SMELLS.highComplexity.severity,
|
|
552
|
+
category: CODE_SMELLS.highComplexity.category,
|
|
553
|
+
line: functionStartLine,
|
|
554
|
+
message: `High cyclomatic complexity or deep nesting (max depth: ${maxNestingDepth})`,
|
|
555
|
+
fix: 'Simplify logic by extracting methods or using early returns'
|
|
556
|
+
})
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
inFunction = false
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
})
|
|
563
|
+
|
|
564
|
+
return issues
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Check for deeply nested conditionals
|
|
569
|
+
*/
|
|
570
|
+
checkNestedConditionals(code, language, filePath) {
|
|
571
|
+
const issues = []
|
|
572
|
+
const lines = code.split('\n')
|
|
573
|
+
|
|
574
|
+
lines.forEach((line, index) => {
|
|
575
|
+
const trimmed = line.trim()
|
|
576
|
+
const indentation = line.match(/^(\s*)/)[1].length
|
|
577
|
+
|
|
578
|
+
if ((trimmed.includes('if ') || trimmed.includes('for ') || trimmed.includes('while ')) &&
|
|
579
|
+
indentation > 16) { // More than 4 levels of nesting
|
|
580
|
+
issues.push({
|
|
581
|
+
type: 'nestedConditionals',
|
|
582
|
+
name: CODE_SMELLS.nestedConditionals.name,
|
|
583
|
+
severity: CODE_SMELLS.nestedConditionals.severity,
|
|
584
|
+
category: CODE_SMELLS.nestedConditionals.category,
|
|
585
|
+
line: index + 1,
|
|
586
|
+
message: `Deeply nested conditional (indentation: ${indentation} spaces)`,
|
|
587
|
+
fix: 'Use early returns or extract nested logic to separate functions'
|
|
588
|
+
})
|
|
589
|
+
}
|
|
590
|
+
})
|
|
591
|
+
|
|
592
|
+
return issues
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Check for dead code
|
|
597
|
+
*/
|
|
598
|
+
checkDeadCode(code, language, filePath) {
|
|
599
|
+
const issues = []
|
|
600
|
+
const lines = code.split('\n')
|
|
601
|
+
|
|
602
|
+
// Check for unreachable code after return/throw
|
|
603
|
+
let foundReturn = false
|
|
604
|
+
|
|
605
|
+
lines.forEach((line, index) => {
|
|
606
|
+
const trimmed = line.trim()
|
|
607
|
+
|
|
608
|
+
// Check for return/throw/break statements
|
|
609
|
+
if (trimmed.startsWith('return') || trimmed.startsWith('throw') ||
|
|
610
|
+
trimmed.startsWith('break') || trimmed.startsWith('exit') ||
|
|
611
|
+
trimmed.startsWith('sys.exit')) {
|
|
612
|
+
foundReturn = true
|
|
613
|
+
} else if (foundReturn && trimmed && !trimmed.startsWith('//') &&
|
|
614
|
+
!trimmed.startsWith('*') && !trimmed.startsWith('*') &&
|
|
615
|
+
!trimmed.match(/^[{}\]\);,]$/)) {
|
|
616
|
+
// Found code after return statement
|
|
617
|
+
issues.push({
|
|
618
|
+
type: 'deadCode',
|
|
619
|
+
name: CODE_SMELLS.deadCode.name,
|
|
620
|
+
severity: CODE_SMELLS.deadCode.severity,
|
|
621
|
+
category: CODE_SMELLS.deadCode.category,
|
|
622
|
+
line: index + 1,
|
|
623
|
+
message: 'Code appears to be unreachable',
|
|
624
|
+
fix: 'Remove unreachable code'
|
|
625
|
+
})
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// Reset flag when we hit a closing brace at the same level
|
|
629
|
+
if (trimmed === '}' && foundReturn) {
|
|
630
|
+
foundReturn = false
|
|
631
|
+
}
|
|
632
|
+
})
|
|
633
|
+
|
|
634
|
+
return issues
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Check for commented code
|
|
639
|
+
*/
|
|
640
|
+
checkCommentedCode(code, language, filePath) {
|
|
641
|
+
const issues = []
|
|
642
|
+
const lines = code.split('\n')
|
|
643
|
+
|
|
644
|
+
lines.forEach((line, index) => {
|
|
645
|
+
const trimmed = line.trim()
|
|
646
|
+
|
|
647
|
+
// Check for commented code blocks
|
|
648
|
+
if ((trimmed.startsWith('//') || trimmed.startsWith('#')) &&
|
|
649
|
+
(trimmed.includes('function') || trimmed.includes('class') ||
|
|
650
|
+
trimmed.includes('const ') || trimmed.includes('let ') ||
|
|
651
|
+
trimmed.includes('var '))) {
|
|
652
|
+
issues.push({
|
|
653
|
+
type: 'commentedCode',
|
|
654
|
+
name: CODE_SMELLS.commentedCode.name,
|
|
655
|
+
severity: CODE_SMELLS.commentedCode.severity,
|
|
656
|
+
category: CODE_SMELLS.commentedCode.category,
|
|
657
|
+
line: index + 1,
|
|
658
|
+
message: 'Commented-out code found',
|
|
659
|
+
fix: 'Remove commented code or use version control to track changes'
|
|
660
|
+
})
|
|
661
|
+
}
|
|
662
|
+
})
|
|
663
|
+
|
|
664
|
+
return issues
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
/**
|
|
668
|
+
* Check for security issues
|
|
669
|
+
*/
|
|
670
|
+
checkSecurityIssues(code, language, filePath) {
|
|
671
|
+
const issues = []
|
|
672
|
+
const lines = code.split('\n')
|
|
673
|
+
|
|
674
|
+
lines.forEach((line, index) => {
|
|
675
|
+
const trimmed = line.toLowerCase()
|
|
676
|
+
|
|
677
|
+
// SQL Injection
|
|
678
|
+
if ((trimmed.includes('query') || trimmed.includes('execute')) &&
|
|
679
|
+
trimmed.includes('+') && (trimmed.includes('select') || trimmed.includes('from'))) {
|
|
680
|
+
issues.push({
|
|
681
|
+
type: 'sqlInjection',
|
|
682
|
+
name: CODE_SMELLS.sqlInjection.name,
|
|
683
|
+
severity: CODE_SMELLS.sqlInjection.severity,
|
|
684
|
+
category: CODE_SMELLS.sqlInjection.category,
|
|
685
|
+
line: index + 1,
|
|
686
|
+
message: 'Potential SQL injection vulnerability',
|
|
687
|
+
fix: 'Use parameterized queries or prepared statements'
|
|
688
|
+
})
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// XSS
|
|
692
|
+
if (trimmed.includes('innerhtml') || trimmed.includes('document.write')) {
|
|
693
|
+
issues.push({
|
|
694
|
+
type: 'xssRisk',
|
|
695
|
+
name: CODE_SMELLS.xssRisk.name,
|
|
696
|
+
severity: CODE_SMELLS.xssRisk.severity,
|
|
697
|
+
category: CODE_SMELLS.xssRisk.category,
|
|
698
|
+
line: index + 1,
|
|
699
|
+
message: 'Potential XSS vulnerability',
|
|
700
|
+
fix: 'Sanitize user input or use safe DOM APIs'
|
|
701
|
+
})
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// Hardcoded secrets - check both snake_case and camelCase
|
|
705
|
+
const secretKeywords = ['api_key', 'apikey', 'password', 'secret', 'token', 'private_key', 'privatekey']
|
|
706
|
+
const hasSecretKeyword = secretKeywords.some(keyword => {
|
|
707
|
+
// Check both snake_case and camelCase versions
|
|
708
|
+
return trimmed.includes(keyword) || trimmed.includes(keyword.replace('_', ''))
|
|
709
|
+
})
|
|
710
|
+
|
|
711
|
+
if (hasSecretKeyword) {
|
|
712
|
+
// Check for assignment without process.env
|
|
713
|
+
if (trimmed.includes('=') && !trimmed.includes('process.env') &&
|
|
714
|
+
!trimmed.includes('env.') && !trimmed.includes('env[')) {
|
|
715
|
+
issues.push({
|
|
716
|
+
type: 'hardcodedSecrets',
|
|
717
|
+
name: CODE_SMELLS.hardcodedSecrets.name,
|
|
718
|
+
severity: CODE_SMELLS.hardcodedSecrets.severity,
|
|
719
|
+
category: CODE_SMELLS.hardcodedSecrets.category,
|
|
720
|
+
line: index + 1,
|
|
721
|
+
message: 'Potential hardcoded secret',
|
|
722
|
+
fix: 'Use environment variables or secure configuration'
|
|
723
|
+
})
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
})
|
|
727
|
+
|
|
728
|
+
return issues
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* Check for performance issues
|
|
733
|
+
*/
|
|
734
|
+
checkPerformanceIssues(code, language, filePath) {
|
|
735
|
+
const issues = []
|
|
736
|
+
const lines = code.split('\n')
|
|
737
|
+
|
|
738
|
+
lines.forEach((line, index) => {
|
|
739
|
+
const trimmed = line.trim()
|
|
740
|
+
|
|
741
|
+
// String concatenation in loop
|
|
742
|
+
if (line.includes('for ') || line.includes('while ')) {
|
|
743
|
+
const nextLine = lines[index + 1] || ''
|
|
744
|
+
if (nextLine.includes('+=') && (nextLine.includes('str') || nextLine.includes('string'))) {
|
|
745
|
+
issues.push({
|
|
746
|
+
type: 'stringConcatenation',
|
|
747
|
+
name: CODE_SMELLS.stringConcatenation.name,
|
|
748
|
+
severity: CODE_SMELLS.stringConcatenation.severity,
|
|
749
|
+
category: CODE_SMELLS.stringConcatenation.category,
|
|
750
|
+
line: index + 1,
|
|
751
|
+
message: 'String concatenation in loop',
|
|
752
|
+
fix: 'Use StringBuilder or array join for better performance'
|
|
753
|
+
})
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// Inefficient loop
|
|
758
|
+
if (trimmed.includes('for (') && trimmed.includes('length')) {
|
|
759
|
+
issues.push({
|
|
760
|
+
type: 'inefficientLoop',
|
|
761
|
+
name: CODE_SMELLS.inefficientLoop.name,
|
|
762
|
+
severity: CODE_SMELLS.inefficientLoop.severity,
|
|
763
|
+
category: CODE_SMELLS.inefficientLoop.category,
|
|
764
|
+
line: index + 1,
|
|
765
|
+
message: 'Potentially inefficient loop (length accessed each iteration)',
|
|
766
|
+
fix: 'Cache length in a variable before loop'
|
|
767
|
+
})
|
|
768
|
+
}
|
|
769
|
+
})
|
|
770
|
+
|
|
771
|
+
return issues
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
/**
|
|
775
|
+
* Check for naming issues
|
|
776
|
+
*/
|
|
777
|
+
checkNamingIssues(code, language, filePath) {
|
|
778
|
+
const issues = []
|
|
779
|
+
const lines = code.split('\n')
|
|
780
|
+
|
|
781
|
+
lines.forEach((line, index) => {
|
|
782
|
+
// Check for bad variable names (const/let/var with single letter or unclear names)
|
|
783
|
+
const varDeclaration = line.match(/(?:const|let|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=/)
|
|
784
|
+
if (varDeclaration) {
|
|
785
|
+
const varName = varDeclaration[1]
|
|
786
|
+
// Check if it's a single letter variable (except common exceptions)
|
|
787
|
+
const singleLetterExceptions = ['i', 'j', 'k', 'x', 'y', 'z', '_', '$']
|
|
788
|
+
if (varName.length === 1 && !singleLetterExceptions.includes(varName)) {
|
|
789
|
+
issues.push({
|
|
790
|
+
type: 'badVariableNames',
|
|
791
|
+
name: CODE_SMELLS.badVariableNames.name,
|
|
792
|
+
severity: CODE_SMELLS.badVariableNames.severity,
|
|
793
|
+
category: CODE_SMELLS.badVariableNames.category,
|
|
794
|
+
line: index + 1,
|
|
795
|
+
message: 'Non-descriptive variable name',
|
|
796
|
+
fix: 'Use more descriptive variable names'
|
|
797
|
+
})
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// Bad function names (too short or unclear)
|
|
802
|
+
const functionRegex = this.getFunctionRegex(language)
|
|
803
|
+
// Create a non-global version for matching
|
|
804
|
+
const functionRegexNoGlobal = new RegExp(functionRegex.source, functionRegex.flags.replace('g', ''))
|
|
805
|
+
const functionMatch = line.match(functionRegexNoGlobal)
|
|
806
|
+
if (functionMatch) {
|
|
807
|
+
// Extract function name from match groups
|
|
808
|
+
let funcName = null
|
|
809
|
+
for (let i = 1; i < functionMatch.length; i++) {
|
|
810
|
+
if (functionMatch[i]) {
|
|
811
|
+
funcName = functionMatch[i]
|
|
812
|
+
break
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
if (funcName && funcName.length < 3 && !['main', 'init', 'run'].includes(funcName)) {
|
|
817
|
+
issues.push({
|
|
818
|
+
type: 'badFunctionNames',
|
|
819
|
+
name: CODE_SMELLS.badFunctionNames.name,
|
|
820
|
+
severity: CODE_SMELLS.badFunctionNames.severity,
|
|
821
|
+
category: CODE_SMELLS.badFunctionNames.category,
|
|
822
|
+
line: index + 1,
|
|
823
|
+
message: 'Function name is too short or unclear',
|
|
824
|
+
fix: 'Use descriptive function names that indicate purpose'
|
|
825
|
+
})
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
})
|
|
829
|
+
|
|
830
|
+
return issues
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
/**
|
|
834
|
+
* Check for best practice violations
|
|
835
|
+
*/
|
|
836
|
+
checkBestPractices(code, language, filePath) {
|
|
837
|
+
const issues = []
|
|
838
|
+
const lines = code.split('\n')
|
|
839
|
+
|
|
840
|
+
lines.forEach((line, index) => {
|
|
841
|
+
const trimmed = line.trim()
|
|
842
|
+
|
|
843
|
+
// Missing error handling
|
|
844
|
+
if ((trimmed.includes('fetch') || trimmed.includes('axios') ||
|
|
845
|
+
trimmed.includes('http') || trimmed.includes('request')) &&
|
|
846
|
+
!lines.slice(Math.max(0, index - 5), index).some(l => l.includes('try') || l.includes('catch'))) {
|
|
847
|
+
issues.push({
|
|
848
|
+
type: 'missingErrorHandling',
|
|
849
|
+
name: CODE_SMELLS.missingErrorHandling.name,
|
|
850
|
+
severity: CODE_SMELLS.missingErrorHandling.severity,
|
|
851
|
+
category: CODE_SMELLS.missingErrorHandling.category,
|
|
852
|
+
line: index + 1,
|
|
853
|
+
message: 'Network operation without error handling',
|
|
854
|
+
fix: 'Wrap in try-catch or use .catch() for promise-based operations'
|
|
855
|
+
})
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// Missing documentation
|
|
859
|
+
const functionRegex = this.getFunctionRegex(language)
|
|
860
|
+
const functionRegexNoGlobal = new RegExp(functionRegex.source, functionRegex.flags.replace('g', ''))
|
|
861
|
+
const functionMatch = line.match(functionRegexNoGlobal)
|
|
862
|
+
if (functionMatch && index > 0) {
|
|
863
|
+
const prevLine = lines[index - 1].trim()
|
|
864
|
+
// Check if previous line is a documentation comment
|
|
865
|
+
const isDocumented = prevLine.startsWith('/**') ||
|
|
866
|
+
prevLine.startsWith('///') ||
|
|
867
|
+
prevLine.startsWith('#') ||
|
|
868
|
+
prevLine.startsWith('"""') ||
|
|
869
|
+
prevLine.startsWith('/*')
|
|
870
|
+
|
|
871
|
+
if (!isDocumented) {
|
|
872
|
+
issues.push({
|
|
873
|
+
type: 'missingDocumentation',
|
|
874
|
+
name: CODE_SMELLS.missingDocumentation.name,
|
|
875
|
+
severity: CODE_SMELLS.missingDocumentation.severity,
|
|
876
|
+
category: CODE_SMELLS.missingDocumentation.category,
|
|
877
|
+
line: index + 1,
|
|
878
|
+
message: 'Function lacks documentation',
|
|
879
|
+
fix: 'Add JSDoc comments or documentation comments'
|
|
880
|
+
})
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
})
|
|
884
|
+
|
|
885
|
+
return issues
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
/**
|
|
889
|
+
* Get function regex based on language
|
|
890
|
+
*/
|
|
891
|
+
getFunctionRegex(language) {
|
|
892
|
+
const regexMap = {
|
|
893
|
+
javascript: /(?:function\s+(\w+)|(?:const|let|var)\s+(\w+)\s*=\s*(?:function|\([^)]*\)\s*=>))/g,
|
|
894
|
+
typescript: /(?:function\s+(\w+)|(\w+)\s*:\s*(?:.*?)=>\s*|(\w+)\s*\(.*?\)\s*{)/g,
|
|
895
|
+
python: /def\s+(\w+)\s*\(/g,
|
|
896
|
+
java: /(?:public|private|protected)?\s*(?:static)?\s*\w+\s+(\w+)\s*\(/g,
|
|
897
|
+
csharp: /(?:public|private|protected)?\s*(?:static)?\s*\w+\s+(\w+)\s*\(/g,
|
|
898
|
+
cpp: /\w+\s+(\w+)\s*\([^)]*\)\s*{/g,
|
|
899
|
+
php: /function\s+(\w+)\s*\(/g,
|
|
900
|
+
ruby: /def\s+(\w+)/g,
|
|
901
|
+
go: /func\s+(?:\([^)]+\)\s+)?(\w+)\s*\(/g,
|
|
902
|
+
rust: /fn\s+(\w+)\s*\(/g,
|
|
903
|
+
swift: /func\s+(\w+)\s*\(/g,
|
|
904
|
+
kotlin: /(?:fun|private fun|public fun)\s+(\w+)\s*\(/g
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
return regexMap[language] || regexMap.javascript
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
/**
|
|
911
|
+
* Extract function body from code
|
|
912
|
+
*/
|
|
913
|
+
extractFunctionBody(code, functionIndex, language) {
|
|
914
|
+
const codeFromFunction = code.substring(functionIndex)
|
|
915
|
+
const lines = codeFromFunction.split('\n')
|
|
916
|
+
|
|
917
|
+
if (language === 'python') {
|
|
918
|
+
// Python: find next line at same indentation
|
|
919
|
+
const functionLine = lines[0]
|
|
920
|
+
const indentMatch = functionLine.match(/^(\s*)/)
|
|
921
|
+
const baseIndent = indentMatch ? indentMatch[1].length : 0
|
|
922
|
+
let bodyLines = []
|
|
923
|
+
|
|
924
|
+
for (let i = 1; i < lines.length; i++) {
|
|
925
|
+
const line = lines[i]
|
|
926
|
+
const indent = line.match(/^(\s*)/)?.[1]?.length || 0
|
|
927
|
+
|
|
928
|
+
if (indent > baseIndent || line.trim() === '') {
|
|
929
|
+
bodyLines.push(line)
|
|
930
|
+
} else {
|
|
931
|
+
break
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
return bodyLines.join('\n')
|
|
935
|
+
} else {
|
|
936
|
+
// JavaScript-like languages
|
|
937
|
+
let braceCount = 0
|
|
938
|
+
let bodyLines = []
|
|
939
|
+
let foundOpenBrace = false
|
|
940
|
+
|
|
941
|
+
for (let i = 0; i < lines.length; i++) {
|
|
942
|
+
const line = lines[i]
|
|
943
|
+
|
|
944
|
+
for (const char of line) {
|
|
945
|
+
if (char === '{') {
|
|
946
|
+
braceCount++
|
|
947
|
+
foundOpenBrace = true
|
|
948
|
+
} else if (char === '}') {
|
|
949
|
+
braceCount--
|
|
950
|
+
if (foundOpenBrace && braceCount === 0) {
|
|
951
|
+
return bodyLines.join('\n')
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
if (foundOpenBrace) {
|
|
957
|
+
bodyLines.push(line)
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
return lines.slice(1).join('\n')
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
/**
|
|
965
|
+
* Generate summary statistics
|
|
966
|
+
*/
|
|
967
|
+
generateSummary(results) {
|
|
968
|
+
const summary = {
|
|
969
|
+
totalFiles: results.length,
|
|
970
|
+
totalIssues: 0,
|
|
971
|
+
errors: 0,
|
|
972
|
+
warnings: 0,
|
|
973
|
+
info: 0,
|
|
974
|
+
byCategory: {},
|
|
975
|
+
byLanguage: {}
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
results.forEach(result => {
|
|
979
|
+
result.issues.forEach(issue => {
|
|
980
|
+
summary.totalIssues++
|
|
981
|
+
|
|
982
|
+
if (issue.severity === 'error') summary.errors++
|
|
983
|
+
else if (issue.severity === 'warning') summary.warnings++
|
|
984
|
+
else summary.info++
|
|
985
|
+
|
|
986
|
+
// By category
|
|
987
|
+
if (!summary.byCategory[issue.category]) {
|
|
988
|
+
summary.byCategory[issue.category] = 0
|
|
989
|
+
}
|
|
990
|
+
summary.byCategory[issue.category]++
|
|
991
|
+
|
|
992
|
+
// By language
|
|
993
|
+
if (!summary.byLanguage[result.language]) {
|
|
994
|
+
summary.byLanguage[result.language] = 0
|
|
995
|
+
}
|
|
996
|
+
summary.byLanguage[result.language]++
|
|
997
|
+
})
|
|
998
|
+
})
|
|
999
|
+
|
|
1000
|
+
return summary
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
/**
|
|
1004
|
+
* Export results to JSON
|
|
1005
|
+
*/
|
|
1006
|
+
async exportToJSON(results, outputPath) {
|
|
1007
|
+
const jsonContent = JSON.stringify(results, null, 2)
|
|
1008
|
+
await fs.writeFile(outputPath, jsonContent)
|
|
1009
|
+
return outputPath
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
/**
|
|
1013
|
+
* Export results to Markdown
|
|
1014
|
+
*/
|
|
1015
|
+
async exportToMarkdown(results, outputPath) {
|
|
1016
|
+
const markdown = this.generateMarkdownReport(results)
|
|
1017
|
+
await fs.writeFile(outputPath, markdown)
|
|
1018
|
+
return outputPath
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
/**
|
|
1022
|
+
* Generate Markdown report
|
|
1023
|
+
*/
|
|
1024
|
+
generateMarkdownReport(results) {
|
|
1025
|
+
const timestamp = new Date().toLocaleString()
|
|
1026
|
+
const summary = this.generateSummary(results.results || [results])
|
|
1027
|
+
|
|
1028
|
+
let report = `# AI Code Quality Analysis Report\n\n`
|
|
1029
|
+
report += `**Generated**: ${timestamp}\n`
|
|
1030
|
+
report += `**Provider**: ${this.currentProvider || 'Mock'}\n`
|
|
1031
|
+
report += `**Files Analyzed**: ${summary.totalFiles}\n\n`
|
|
1032
|
+
|
|
1033
|
+
report += `## Summary\n\n`
|
|
1034
|
+
report += `| Metric | Count |\n`
|
|
1035
|
+
report += `|--------|-------|\n`
|
|
1036
|
+
report += `| Total Issues | ${summary.totalIssues} |\n`
|
|
1037
|
+
report += `| Errors | ${summary.errors} |\n`
|
|
1038
|
+
report += `| Warnings | ${summary.warnings} |\n`
|
|
1039
|
+
report += `| Info | ${summary.info} |\n\n`
|
|
1040
|
+
|
|
1041
|
+
report += `### Issues by Category\n\n`
|
|
1042
|
+
Object.entries(summary.byCategory).forEach(([category, count]) => {
|
|
1043
|
+
report += `- **${category}**: ${count}\n`
|
|
1044
|
+
})
|
|
1045
|
+
report += `\n`
|
|
1046
|
+
|
|
1047
|
+
report += `### Issues by Language\n\n`
|
|
1048
|
+
Object.entries(summary.byLanguage).forEach(([language, count]) => {
|
|
1049
|
+
report += `- **${language}**: ${count}\n`
|
|
1050
|
+
})
|
|
1051
|
+
report += `\n`
|
|
1052
|
+
|
|
1053
|
+
report += `## Detailed Issues\n\n`
|
|
1054
|
+
|
|
1055
|
+
const analysisResults = results.results || [results]
|
|
1056
|
+
analysisResults.forEach(result => {
|
|
1057
|
+
if (result.issues && result.issues.length > 0) {
|
|
1058
|
+
report += `### ${result.file}\n\n`
|
|
1059
|
+
report += `**Language**: ${result.language}\n`
|
|
1060
|
+
report += `**Total Issues**: ${result.issues.length}\n\n`
|
|
1061
|
+
|
|
1062
|
+
result.issues.forEach(issue => {
|
|
1063
|
+
const severityIcon = issue.severity === 'error' ? '❌' :
|
|
1064
|
+
issue.severity === 'warning' ? '⚠️' : 'ℹ️'
|
|
1065
|
+
report += `${severityIcon} **${issue.name}** (Line ${issue.line})\n`
|
|
1066
|
+
report += `- Severity: ${issue.severity}\n`
|
|
1067
|
+
report += `- Category: ${issue.category}\n`
|
|
1068
|
+
report += `- Message: ${issue.message}\n`
|
|
1069
|
+
report += `- Fix: ${issue.fix}\n\n`
|
|
1070
|
+
})
|
|
1071
|
+
}
|
|
1072
|
+
})
|
|
1073
|
+
|
|
1074
|
+
report += `---\n*Generated by Code-Simplifier AI Quality Analyzer*\n`
|
|
1075
|
+
|
|
1076
|
+
return report
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
module.exports = new AIQualityAnalyzer()
|