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.
@@ -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()