code-simplifier 1.0.0 → 1.2.0

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.
@@ -0,0 +1,1095 @@
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
+ const functionRegex = this.getFunctionRegex(language)
507
+ const functionRegexNoGlobal = new RegExp(functionRegex.source, functionRegex.flags.replace('g', ''))
508
+
509
+ let inFunction = false
510
+ let functionStartLine = 0
511
+ let braceDepth = 0
512
+ let decisionPoints = 0
513
+
514
+ lines.forEach((line, index) => {
515
+ const trimmed = line.trim()
516
+
517
+ // Check for function start
518
+ if (functionRegexNoGlobal.test(line)) {
519
+ if (inFunction && decisionPoints > 10) {
520
+ issues.push({
521
+ type: 'highComplexity',
522
+ name: CODE_SMELLS.highComplexity.name,
523
+ severity: CODE_SMELLS.highComplexity.severity,
524
+ category: CODE_SMELLS.highComplexity.category,
525
+ line: functionStartLine,
526
+ message: `Cyclomatic complexity: ${decisionPoints} (recommended: < 10)`,
527
+ fix: 'Simplify logic by extracting methods or using early returns'
528
+ })
529
+ }
530
+
531
+ inFunction = true
532
+ functionStartLine = index + 1
533
+ braceDepth = 0
534
+ decisionPoints = 1 // Start with 1 for the function itself
535
+ }
536
+
537
+ if (inFunction) {
538
+ // Count braces to track function scope
539
+ for (const char of line) {
540
+ if (char === '{') braceDepth++
541
+ else if (char === '}') braceDepth--
542
+ }
543
+
544
+ // Count decision points
545
+ if (trimmed.includes('if ') || trimmed.includes('else if')) {
546
+ decisionPoints++
547
+ } else if (trimmed.includes('for ') || trimmed.includes('while ')) {
548
+ decisionPoints++
549
+ } else if (trimmed.includes('case ') || trimmed.includes('default:')) {
550
+ decisionPoints++
551
+ } else if (trimmed.includes('catch ')) {
552
+ decisionPoints++
553
+ } else if (trimmed.includes('&&')) {
554
+ decisionPoints++
555
+ } else if (trimmed.includes('||')) {
556
+ decisionPoints++
557
+ }
558
+
559
+ // Check for function end (when we exit the function's brace scope)
560
+ if (trimmed === '}' && braceDepth === 0) {
561
+ inFunction = false
562
+ if (decisionPoints > 10) {
563
+ issues.push({
564
+ type: 'highComplexity',
565
+ name: CODE_SMELLS.highComplexity.name,
566
+ severity: CODE_SMELLS.highComplexity.severity,
567
+ category: CODE_SMELLS.highComplexity.category,
568
+ line: functionStartLine,
569
+ message: `Cyclomatic complexity: ${decisionPoints} (recommended: < 10)`,
570
+ fix: 'Simplify logic by extracting methods or using early returns'
571
+ })
572
+ }
573
+ }
574
+ }
575
+ })
576
+
577
+ return issues
578
+ }
579
+
580
+ /**
581
+ * Check for deeply nested conditionals
582
+ */
583
+ checkNestedConditionals(code, language, filePath) {
584
+ const issues = []
585
+ const lines = code.split('\n')
586
+
587
+ lines.forEach((line, index) => {
588
+ const trimmed = line.trim()
589
+ const indentation = line.match(/^(\s*)/)[1].length
590
+
591
+ if ((trimmed.includes('if ') || trimmed.includes('for ') || trimmed.includes('while ')) &&
592
+ indentation > 16) { // More than 4 levels of nesting
593
+ issues.push({
594
+ type: 'nestedConditionals',
595
+ name: CODE_SMELLS.nestedConditionals.name,
596
+ severity: CODE_SMELLS.nestedConditionals.severity,
597
+ category: CODE_SMELLS.nestedConditionals.category,
598
+ line: index + 1,
599
+ message: `Deeply nested conditional (indentation: ${indentation} spaces)`,
600
+ fix: 'Use early returns or extract nested logic to separate functions'
601
+ })
602
+ }
603
+ })
604
+
605
+ return issues
606
+ }
607
+
608
+ /**
609
+ * Check for dead code
610
+ */
611
+ checkDeadCode(code, language, filePath) {
612
+ const issues = []
613
+ const lines = code.split('\n')
614
+
615
+ // Check for unreachable code after return/throw
616
+ let foundReturn = false
617
+ let braceDepth = 0
618
+ let inBlock = false
619
+
620
+ lines.forEach((line, index) => {
621
+ const trimmed = line.trim()
622
+
623
+ // Track brace depth
624
+ for (const char of line) {
625
+ if (char === '{') {
626
+ braceDepth++
627
+ inBlock = true
628
+ } else if (char === '}') {
629
+ braceDepth--
630
+ if (braceDepth === 0) {
631
+ inBlock = false
632
+ foundReturn = false
633
+ }
634
+ }
635
+ }
636
+
637
+ if (foundReturn && trimmed && !trimmed.startsWith('//') && !trimmed.startsWith('*') && !trimmed.match(/^[{}]$/)) {
638
+ issues.push({
639
+ type: 'deadCode',
640
+ name: CODE_SMELLS.deadCode.name,
641
+ severity: CODE_SMELLS.deadCode.severity,
642
+ category: CODE_SMELLS.deadCode.category,
643
+ line: index + 1,
644
+ message: 'Code appears to be unreachable',
645
+ fix: 'Remove unreachable code'
646
+ })
647
+ }
648
+
649
+ if (!inBlock && (trimmed.startsWith('return') || trimmed.startsWith('throw') ||
650
+ trimmed.startsWith('exit') || trimmed.startsWith('sys.exit'))) {
651
+ foundReturn = true
652
+ }
653
+ })
654
+
655
+ return issues
656
+ }
657
+
658
+ /**
659
+ * Check for commented code
660
+ */
661
+ checkCommentedCode(code, language, filePath) {
662
+ const issues = []
663
+ const lines = code.split('\n')
664
+
665
+ lines.forEach((line, index) => {
666
+ const trimmed = line.trim()
667
+
668
+ // Check for commented code blocks
669
+ if ((trimmed.startsWith('//') || trimmed.startsWith('#')) &&
670
+ (trimmed.includes('function') || trimmed.includes('class') ||
671
+ trimmed.includes('const ') || trimmed.includes('let ') ||
672
+ trimmed.includes('var '))) {
673
+ issues.push({
674
+ type: 'commentedCode',
675
+ name: CODE_SMELLS.commentedCode.name,
676
+ severity: CODE_SMELLS.commentedCode.severity,
677
+ category: CODE_SMELLS.commentedCode.category,
678
+ line: index + 1,
679
+ message: 'Commented-out code found',
680
+ fix: 'Remove commented code or use version control to track changes'
681
+ })
682
+ }
683
+ })
684
+
685
+ return issues
686
+ }
687
+
688
+ /**
689
+ * Check for security issues
690
+ */
691
+ checkSecurityIssues(code, language, filePath) {
692
+ const issues = []
693
+ const lines = code.split('\n')
694
+
695
+ lines.forEach((line, index) => {
696
+ const trimmed = line.toLowerCase()
697
+
698
+ // SQL Injection
699
+ if ((trimmed.includes('query') || trimmed.includes('execute')) &&
700
+ trimmed.includes('+') && (trimmed.includes('select') || trimmed.includes('from'))) {
701
+ issues.push({
702
+ type: 'sqlInjection',
703
+ name: CODE_SMELLS.sqlInjection.name,
704
+ severity: CODE_SMELLS.sqlInjection.severity,
705
+ category: CODE_SMELLS.sqlInjection.category,
706
+ line: index + 1,
707
+ message: 'Potential SQL injection vulnerability',
708
+ fix: 'Use parameterized queries or prepared statements'
709
+ })
710
+ }
711
+
712
+ // XSS
713
+ if (trimmed.includes('innerhtml') || trimmed.includes('document.write')) {
714
+ issues.push({
715
+ type: 'xssRisk',
716
+ name: CODE_SMELLS.xssRisk.name,
717
+ severity: CODE_SMELLS.xssRisk.severity,
718
+ category: CODE_SMELLS.xssRisk.category,
719
+ line: index + 1,
720
+ message: 'Potential XSS vulnerability',
721
+ fix: 'Sanitize user input or use safe DOM APIs'
722
+ })
723
+ }
724
+
725
+ // Hardcoded secrets
726
+ if (trimmed.includes('api_key') || trimmed.includes('password') ||
727
+ trimmed.includes('secret') || trimmed.includes('token')) {
728
+ // Check for assignment without process.env
729
+ if (trimmed.includes('=') && !trimmed.includes('process.env') &&
730
+ !trimmed.includes('env.') && !trimmed.includes('env[')) {
731
+ issues.push({
732
+ type: 'hardcodedSecrets',
733
+ name: CODE_SMELLS.hardcodedSecrets.name,
734
+ severity: CODE_SMELLS.hardcodedSecrets.severity,
735
+ category: CODE_SMELLS.hardcodedSecrets.category,
736
+ line: index + 1,
737
+ message: 'Potential hardcoded secret',
738
+ fix: 'Use environment variables or secure configuration'
739
+ })
740
+ }
741
+ }
742
+ })
743
+
744
+ return issues
745
+ }
746
+
747
+ /**
748
+ * Check for performance issues
749
+ */
750
+ checkPerformanceIssues(code, language, filePath) {
751
+ const issues = []
752
+ const lines = code.split('\n')
753
+
754
+ lines.forEach((line, index) => {
755
+ const trimmed = line.trim()
756
+
757
+ // String concatenation in loop
758
+ if (line.includes('for ') || line.includes('while ')) {
759
+ const nextLine = lines[index + 1] || ''
760
+ if (nextLine.includes('+=') && (nextLine.includes('str') || nextLine.includes('string'))) {
761
+ issues.push({
762
+ type: 'stringConcatenation',
763
+ name: CODE_SMELLS.stringConcatenation.name,
764
+ severity: CODE_SMELLS.stringConcatenation.severity,
765
+ category: CODE_SMELLS.stringConcatenation.category,
766
+ line: index + 1,
767
+ message: 'String concatenation in loop',
768
+ fix: 'Use StringBuilder or array join for better performance'
769
+ })
770
+ }
771
+ }
772
+
773
+ // Inefficient loop
774
+ if (trimmed.includes('for (') && trimmed.includes('length')) {
775
+ issues.push({
776
+ type: 'inefficientLoop',
777
+ name: CODE_SMELLS.inefficientLoop.name,
778
+ severity: CODE_SMELLS.inefficientLoop.severity,
779
+ category: CODE_SMELLS.inefficientLoop.category,
780
+ line: index + 1,
781
+ message: 'Potentially inefficient loop (length accessed each iteration)',
782
+ fix: 'Cache length in a variable before loop'
783
+ })
784
+ }
785
+ })
786
+
787
+ return issues
788
+ }
789
+
790
+ /**
791
+ * Check for naming issues
792
+ */
793
+ checkNamingIssues(code, language, filePath) {
794
+ const issues = []
795
+ const lines = code.split('\n')
796
+
797
+ lines.forEach((line, index) => {
798
+ // Single letter variables (except loop counters)
799
+ const singleLetterVar = line.match(/\b([a-z])\b(?!\s*[=+\-*/]|\s*[;,\)\]])/g)
800
+ if (singleLetterVar && singleLetterVar.length > 0) {
801
+ // Check if it's not a common loop counter
802
+ const hasLoopCounter = /\bfor\s*\(\s*[a-z]\s+/.test(line)
803
+ if (!hasLoopCounter) {
804
+ issues.push({
805
+ type: 'badVariableNames',
806
+ name: CODE_SMELLS.badVariableNames.name,
807
+ severity: CODE_SMELLS.badVariableNames.severity,
808
+ category: CODE_SMELLS.badVariableNames.category,
809
+ line: index + 1,
810
+ message: 'Non-descriptive variable name',
811
+ fix: 'Use more descriptive variable names'
812
+ })
813
+ }
814
+ }
815
+
816
+ // Bad function names (too short or unclear)
817
+ const functionRegex = this.getFunctionRegex(language)
818
+ // Create a non-global version for matching
819
+ const functionRegexNoGlobal = new RegExp(functionRegex.source, functionRegex.flags.replace('g', ''))
820
+ const functionMatch = line.match(functionRegexNoGlobal)
821
+ if (functionMatch) {
822
+ // Extract function name from match groups
823
+ let funcName = null
824
+ for (let i = 1; i < functionMatch.length; i++) {
825
+ if (functionMatch[i]) {
826
+ funcName = functionMatch[i]
827
+ break
828
+ }
829
+ }
830
+
831
+ if (funcName && funcName.length < 3 && !['main', 'init', 'run'].includes(funcName)) {
832
+ issues.push({
833
+ type: 'badFunctionNames',
834
+ name: CODE_SMELLS.badFunctionNames.name,
835
+ severity: CODE_SMELLS.badFunctionNames.severity,
836
+ category: CODE_SMELLS.badFunctionNames.category,
837
+ line: index + 1,
838
+ message: 'Function name is too short or unclear',
839
+ fix: 'Use descriptive function names that indicate purpose'
840
+ })
841
+ }
842
+ }
843
+ })
844
+
845
+ return issues
846
+ }
847
+
848
+ /**
849
+ * Check for best practice violations
850
+ */
851
+ checkBestPractices(code, language, filePath) {
852
+ const issues = []
853
+ const lines = code.split('\n')
854
+
855
+ lines.forEach((line, index) => {
856
+ const trimmed = line.trim()
857
+
858
+ // Missing error handling
859
+ if ((trimmed.includes('fetch') || trimmed.includes('axios') ||
860
+ trimmed.includes('http') || trimmed.includes('request')) &&
861
+ !lines.slice(Math.max(0, index - 5), index).some(l => l.includes('try') || l.includes('catch'))) {
862
+ issues.push({
863
+ type: 'missingErrorHandling',
864
+ name: CODE_SMELLS.missingErrorHandling.name,
865
+ severity: CODE_SMELLS.missingErrorHandling.severity,
866
+ category: CODE_SMELLS.missingErrorHandling.category,
867
+ line: index + 1,
868
+ message: 'Network operation without error handling',
869
+ fix: 'Wrap in try-catch or use .catch() for promise-based operations'
870
+ })
871
+ }
872
+
873
+ // Missing documentation
874
+ const functionRegex = this.getFunctionRegex(language)
875
+ const functionRegexNoGlobal = new RegExp(functionRegex.source, functionRegex.flags.replace('g', ''))
876
+ const functionMatch = line.match(functionRegexNoGlobal)
877
+ if (functionMatch && index > 0) {
878
+ const prevLine = lines[index - 1].trim()
879
+ // Check if previous line is a documentation comment
880
+ const isDocumented = prevLine.startsWith('/**') ||
881
+ prevLine.startsWith('///') ||
882
+ prevLine.startsWith('#') ||
883
+ prevLine.startsWith('"""') ||
884
+ prevLine.startsWith('/*')
885
+
886
+ if (!isDocumented) {
887
+ issues.push({
888
+ type: 'missingDocumentation',
889
+ name: CODE_SMELLS.missingDocumentation.name,
890
+ severity: CODE_SMELLS.missingDocumentation.severity,
891
+ category: CODE_SMELLS.missingDocumentation.category,
892
+ line: index + 1,
893
+ message: 'Function lacks documentation',
894
+ fix: 'Add JSDoc comments or documentation comments'
895
+ })
896
+ }
897
+ }
898
+ })
899
+
900
+ return issues
901
+ }
902
+
903
+ /**
904
+ * Get function regex based on language
905
+ */
906
+ getFunctionRegex(language) {
907
+ const regexMap = {
908
+ javascript: /(?:function\s+(\w+)|(?:const|let|var)\s+(\w+)\s*=\s*(?:function|\([^)]*\)\s*=>))/g,
909
+ typescript: /(?:function\s+(\w+)|(\w+)\s*:\s*(?:.*?)=>\s*|(\w+)\s*\(.*?\)\s*{)/g,
910
+ python: /def\s+(\w+)\s*\(/g,
911
+ java: /(?:public|private|protected)?\s*(?:static)?\s*\w+\s+(\w+)\s*\(/g,
912
+ csharp: /(?:public|private|protected)?\s*(?:static)?\s*\w+\s+(\w+)\s*\(/g,
913
+ cpp: /\w+\s+(\w+)\s*\([^)]*\)\s*{/g,
914
+ php: /function\s+(\w+)\s*\(/g,
915
+ ruby: /def\s+(\w+)/g,
916
+ go: /func\s+(?:\([^)]+\)\s+)?(\w+)\s*\(/g,
917
+ rust: /fn\s+(\w+)\s*\(/g,
918
+ swift: /func\s+(\w+)\s*\(/g,
919
+ kotlin: /(?:fun|private fun|public fun)\s+(\w+)\s*\(/g
920
+ }
921
+
922
+ return regexMap[language] || regexMap.javascript
923
+ }
924
+
925
+ /**
926
+ * Extract function body from code
927
+ */
928
+ extractFunctionBody(code, functionIndex, language) {
929
+ const codeFromFunction = code.substring(functionIndex)
930
+ const lines = codeFromFunction.split('\n')
931
+
932
+ if (language === 'python') {
933
+ // Python: find next line at same indentation
934
+ const functionLine = lines[0]
935
+ const indentMatch = functionLine.match(/^(\s*)/)
936
+ const baseIndent = indentMatch ? indentMatch[1].length : 0
937
+ let bodyLines = []
938
+
939
+ for (let i = 1; i < lines.length; i++) {
940
+ const line = lines[i]
941
+ const indent = line.match(/^(\s*)/)?.[1]?.length || 0
942
+
943
+ if (indent > baseIndent || line.trim() === '') {
944
+ bodyLines.push(line)
945
+ } else {
946
+ break
947
+ }
948
+ }
949
+ return bodyLines.join('\n')
950
+ } else {
951
+ // JavaScript-like languages
952
+ let braceCount = 0
953
+ let bodyLines = []
954
+ let foundOpenBrace = false
955
+
956
+ for (let i = 0; i < lines.length; i++) {
957
+ const line = lines[i]
958
+
959
+ for (const char of line) {
960
+ if (char === '{') {
961
+ braceCount++
962
+ foundOpenBrace = true
963
+ } else if (char === '}') {
964
+ braceCount--
965
+ if (foundOpenBrace && braceCount === 0) {
966
+ return bodyLines.join('\n')
967
+ }
968
+ }
969
+ }
970
+
971
+ if (foundOpenBrace) {
972
+ bodyLines.push(line)
973
+ }
974
+ }
975
+ return lines.slice(1).join('\n')
976
+ }
977
+ }
978
+
979
+ /**
980
+ * Generate summary statistics
981
+ */
982
+ generateSummary(results) {
983
+ const summary = {
984
+ totalFiles: results.length,
985
+ totalIssues: 0,
986
+ errors: 0,
987
+ warnings: 0,
988
+ info: 0,
989
+ byCategory: {},
990
+ byLanguage: {}
991
+ }
992
+
993
+ results.forEach(result => {
994
+ result.issues.forEach(issue => {
995
+ summary.totalIssues++
996
+
997
+ if (issue.severity === 'error') summary.errors++
998
+ else if (issue.severity === 'warning') summary.warnings++
999
+ else summary.info++
1000
+
1001
+ // By category
1002
+ if (!summary.byCategory[issue.category]) {
1003
+ summary.byCategory[issue.category] = 0
1004
+ }
1005
+ summary.byCategory[issue.category]++
1006
+
1007
+ // By language
1008
+ if (!summary.byLanguage[result.language]) {
1009
+ summary.byLanguage[result.language] = 0
1010
+ }
1011
+ summary.byLanguage[result.language]++
1012
+ })
1013
+ })
1014
+
1015
+ return summary
1016
+ }
1017
+
1018
+ /**
1019
+ * Export results to JSON
1020
+ */
1021
+ async exportToJSON(results, outputPath) {
1022
+ const jsonContent = JSON.stringify(results, null, 2)
1023
+ await fs.writeFile(outputPath, jsonContent)
1024
+ return outputPath
1025
+ }
1026
+
1027
+ /**
1028
+ * Export results to Markdown
1029
+ */
1030
+ async exportToMarkdown(results, outputPath) {
1031
+ const markdown = this.generateMarkdownReport(results)
1032
+ await fs.writeFile(outputPath, markdown)
1033
+ return outputPath
1034
+ }
1035
+
1036
+ /**
1037
+ * Generate Markdown report
1038
+ */
1039
+ generateMarkdownReport(results) {
1040
+ const timestamp = new Date().toLocaleString()
1041
+ const summary = this.generateSummary(results.results || [results])
1042
+
1043
+ let report = `# AI Code Quality Analysis Report\n\n`
1044
+ report += `**Generated**: ${timestamp}\n`
1045
+ report += `**Provider**: ${this.currentProvider || 'Mock'}\n`
1046
+ report += `**Files Analyzed**: ${summary.totalFiles}\n\n`
1047
+
1048
+ report += `## Summary\n\n`
1049
+ report += `| Metric | Count |\n`
1050
+ report += `|--------|-------|\n`
1051
+ report += `| Total Issues | ${summary.totalIssues} |\n`
1052
+ report += `| Errors | ${summary.errors} |\n`
1053
+ report += `| Warnings | ${summary.warnings} |\n`
1054
+ report += `| Info | ${summary.info} |\n\n`
1055
+
1056
+ report += `### Issues by Category\n\n`
1057
+ Object.entries(summary.byCategory).forEach(([category, count]) => {
1058
+ report += `- **${category}**: ${count}\n`
1059
+ })
1060
+ report += `\n`
1061
+
1062
+ report += `### Issues by Language\n\n`
1063
+ Object.entries(summary.byLanguage).forEach(([language, count]) => {
1064
+ report += `- **${language}**: ${count}\n`
1065
+ })
1066
+ report += `\n`
1067
+
1068
+ report += `## Detailed Issues\n\n`
1069
+
1070
+ const analysisResults = results.results || [results]
1071
+ analysisResults.forEach(result => {
1072
+ if (result.issues && result.issues.length > 0) {
1073
+ report += `### ${result.file}\n\n`
1074
+ report += `**Language**: ${result.language}\n`
1075
+ report += `**Total Issues**: ${result.issues.length}\n\n`
1076
+
1077
+ result.issues.forEach(issue => {
1078
+ const severityIcon = issue.severity === 'error' ? '❌' :
1079
+ issue.severity === 'warning' ? '⚠️' : 'ℹ️'
1080
+ report += `${severityIcon} **${issue.name}** (Line ${issue.line})\n`
1081
+ report += `- Severity: ${issue.severity}\n`
1082
+ report += `- Category: ${issue.category}\n`
1083
+ report += `- Message: ${issue.message}\n`
1084
+ report += `- Fix: ${issue.fix}\n\n`
1085
+ })
1086
+ }
1087
+ })
1088
+
1089
+ report += `---\n*Generated by Code-Simplifier AI Quality Analyzer*\n`
1090
+
1091
+ return report
1092
+ }
1093
+ }
1094
+
1095
+ module.exports = new AIQualityAnalyzer()