react-code-smell-detector 1.4.2 → 1.5.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/README.md +227 -22
- package/dist/__tests__/aiRefactoring.test.d.ts +2 -0
- package/dist/__tests__/aiRefactoring.test.d.ts.map +1 -0
- package/dist/__tests__/aiRefactoring.test.js +86 -0
- package/dist/__tests__/analyzer-real.test.d.ts +2 -0
- package/dist/__tests__/analyzer-real.test.d.ts.map +1 -0
- package/dist/__tests__/analyzer-real.test.js +149 -0
- package/dist/__tests__/analyzer.test.d.ts +2 -0
- package/dist/__tests__/analyzer.test.d.ts.map +1 -0
- package/dist/__tests__/analyzer.test.js +173 -0
- package/dist/__tests__/baseline.test.d.ts +2 -0
- package/dist/__tests__/baseline.test.d.ts.map +1 -0
- package/dist/__tests__/baseline.test.js +136 -0
- package/dist/__tests__/bundleAnalyzer.test.d.ts +2 -0
- package/dist/__tests__/bundleAnalyzer.test.d.ts.map +1 -0
- package/dist/__tests__/bundleAnalyzer.test.js +182 -0
- package/dist/__tests__/customRules.test.d.ts +2 -0
- package/dist/__tests__/customRules.test.d.ts.map +1 -0
- package/dist/__tests__/customRules.test.js +283 -0
- package/dist/__tests__/detectors/index.test.d.ts +2 -0
- package/dist/__tests__/detectors/index.test.d.ts.map +1 -0
- package/dist/__tests__/detectors/index.test.js +1012 -0
- package/dist/__tests__/detectors/newDetectors.test.d.ts +2 -0
- package/dist/__tests__/detectors/newDetectors.test.d.ts.map +1 -0
- package/dist/__tests__/detectors/newDetectors.test.js +333 -0
- package/dist/__tests__/docGenerator.test.d.ts +2 -0
- package/dist/__tests__/docGenerator.test.d.ts.map +1 -0
- package/dist/__tests__/docGenerator.test.js +157 -0
- package/dist/__tests__/fixer.test.d.ts +2 -0
- package/dist/__tests__/fixer.test.d.ts.map +1 -0
- package/dist/__tests__/fixer.test.js +193 -0
- package/dist/__tests__/git.test.d.ts +2 -0
- package/dist/__tests__/git.test.d.ts.map +1 -0
- package/dist/__tests__/git.test.js +38 -0
- package/dist/__tests__/graphGenerator.test.d.ts +2 -0
- package/dist/__tests__/graphGenerator.test.d.ts.map +1 -0
- package/dist/__tests__/graphGenerator.test.js +190 -0
- package/dist/__tests__/htmlReporter.test.d.ts +2 -0
- package/dist/__tests__/htmlReporter.test.d.ts.map +1 -0
- package/dist/__tests__/htmlReporter.test.js +258 -0
- package/dist/__tests__/interactiveFixer.test.d.ts +2 -0
- package/dist/__tests__/interactiveFixer.test.d.ts.map +1 -0
- package/dist/__tests__/interactiveFixer.test.js +231 -0
- package/dist/__tests__/parser.test.d.ts +2 -0
- package/dist/__tests__/parser.test.d.ts.map +1 -0
- package/dist/__tests__/parser.test.js +56 -0
- package/dist/__tests__/performanceBudget.test.d.ts +2 -0
- package/dist/__tests__/performanceBudget.test.d.ts.map +1 -0
- package/dist/__tests__/performanceBudget.test.js +242 -0
- package/dist/__tests__/prComments.test.d.ts +2 -0
- package/dist/__tests__/prComments.test.d.ts.map +1 -0
- package/dist/__tests__/prComments.test.js +118 -0
- package/dist/__tests__/reporter.test.d.ts +2 -0
- package/dist/__tests__/reporter.test.d.ts.map +1 -0
- package/dist/__tests__/reporter.test.js +136 -0
- package/dist/__tests__/watcher.test.d.ts +2 -0
- package/dist/__tests__/watcher.test.d.ts.map +1 -0
- package/dist/__tests__/watcher.test.js +161 -0
- package/dist/__tests__/webhooks.test.d.ts +2 -0
- package/dist/__tests__/webhooks.test.d.ts.map +1 -0
- package/dist/__tests__/webhooks.test.js +209 -0
- package/dist/aiRefactoring.d.ts +29 -0
- package/dist/aiRefactoring.d.ts.map +1 -0
- package/dist/aiRefactoring.js +290 -0
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +33 -1
- package/dist/cli.js +123 -1
- package/dist/detectors/contextApi.d.ts +11 -0
- package/dist/detectors/contextApi.d.ts.map +1 -0
- package/dist/detectors/contextApi.js +151 -0
- package/dist/detectors/errorBoundary.d.ts +11 -0
- package/dist/detectors/errorBoundary.d.ts.map +1 -0
- package/dist/detectors/errorBoundary.js +167 -0
- package/dist/detectors/formValidation.d.ts +11 -0
- package/dist/detectors/formValidation.d.ts.map +1 -0
- package/dist/detectors/formValidation.js +193 -0
- package/dist/detectors/index.d.ts +6 -0
- package/dist/detectors/index.d.ts.map +1 -1
- package/dist/detectors/index.js +12 -0
- package/dist/detectors/serverComponents.d.ts +11 -0
- package/dist/detectors/serverComponents.d.ts.map +1 -0
- package/dist/detectors/serverComponents.js +222 -0
- package/dist/detectors/stateManagement.d.ts +11 -0
- package/dist/detectors/stateManagement.d.ts.map +1 -0
- package/dist/detectors/stateManagement.js +193 -0
- package/dist/detectors/testingGaps.d.ts +15 -0
- package/dist/detectors/testingGaps.d.ts.map +1 -0
- package/dist/detectors/testingGaps.js +182 -0
- package/dist/docGenerator.d.ts +37 -0
- package/dist/docGenerator.d.ts.map +1 -0
- package/dist/docGenerator.js +306 -0
- package/dist/guide.d.ts +9 -0
- package/dist/guide.d.ts.map +1 -0
- package/dist/guide.js +922 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/interactiveFixer.d.ts +20 -0
- package/dist/interactiveFixer.d.ts.map +1 -0
- package/dist/interactiveFixer.js +178 -0
- package/dist/performanceBudget.d.ts +54 -0
- package/dist/performanceBudget.d.ts.map +1 -0
- package/dist/performanceBudget.js +218 -0
- package/dist/prComments.d.ts +47 -0
- package/dist/prComments.d.ts.map +1 -0
- package/dist/prComments.js +233 -0
- package/dist/types/index.d.ts +12 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +18 -0
- package/package.json +10 -4
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ A CLI tool that analyzes React projects and detects common code smells, providin
|
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
- 🔍 **Detect Code Smells**: Identifies common React anti-patterns (
|
|
7
|
+
- 🔍 **Detect Code Smells**: Identifies common React anti-patterns (70+ smell types)
|
|
8
8
|
- 📊 **Technical Debt Score**: Grades your codebase from A to F
|
|
9
9
|
- 💡 **Refactoring Suggestions**: Actionable recommendations for each issue
|
|
10
10
|
- 📝 **Multiple Output Formats**: Console (colored), JSON, Markdown, and HTML
|
|
@@ -24,6 +24,17 @@ A CLI tool that analyzes React projects and detects common code smells, providin
|
|
|
24
24
|
- 🔗 **Dependency Graph Visualization**: Visual SVG/HTML of component and import relationships
|
|
25
25
|
- 📦 **Bundle Size Impact**: Per-component bundle size estimates and optimization suggestions
|
|
26
26
|
- ⚙️ **Custom Rules Engine**: Define project-specific code quality rules in configuration
|
|
27
|
+
- 🔧 **Interactive Fix Mode**: Review and apply fixes one by one with diff preview
|
|
28
|
+
- 💬 **GitHub PR Comments**: Auto-comment analysis results on pull requests
|
|
29
|
+
- 📊 **Performance Budget**: Set thresholds and enforce limits in CI/CD
|
|
30
|
+
- 📚 **Component Documentation**: Auto-generate docs from component analysis
|
|
31
|
+
- ⚛️ **React 19 Server Components**: Detect Server/Client boundary issues
|
|
32
|
+
- 🔮 **Context API Analysis**: Detect context overuse, missing memoization, re-render issues
|
|
33
|
+
- 🛡️ **Error Boundary Detection**: Find missing ErrorBoundary and Suspense wrappers
|
|
34
|
+
- 📋 **Form Validation Smells**: Detect uncontrolled inputs, missing validation
|
|
35
|
+
- 🗃️ **State Management Analysis**: Redux/Zustand anti-patterns, selector issues
|
|
36
|
+
- 🧪 **Testing Gap Detection**: Identify complex components likely needing tests
|
|
37
|
+
- 🤖 **AI Refactoring Suggestions**: LLM-powered smart refactoring recommendations
|
|
27
38
|
|
|
28
39
|
### Detected Code Smells
|
|
29
40
|
|
|
@@ -42,7 +53,13 @@ A CLI tool that analyzes React projects and detects common code smells, providin
|
|
|
42
53
|
| **Import Issues** | Circular dependencies, barrel file imports, excessive imports |
|
|
43
54
|
| **Unused Code** | Unused exports, dead imports |
|
|
44
55
|
| **Custom Rules** | User-defined code quality rules |
|
|
56
|
+
| **Server Components** | React 19 client/server boundary issues, async components |
|
|
45
57
|
| **Framework-Specific** | Next.js, React Native, Node.js, TypeScript issues |
|
|
58
|
+
| **Context API** | Context overuse, missing memoization, large context values |
|
|
59
|
+
| **Error Boundaries** | Missing ErrorBoundary, Suspense without fallback |
|
|
60
|
+
| **Form Validation** | Uncontrolled forms, missing validation, controlled inputs without onChange |
|
|
61
|
+
| **State Management** | Redux selector anti-patterns, derived state issues, state sync problems |
|
|
62
|
+
| **Testing Gaps** | Complex untestable components, side-effect heavy code, tight coupling |
|
|
46
63
|
|
|
47
64
|
## Installation
|
|
48
65
|
|
|
@@ -64,6 +81,26 @@ npm install -D react-code-smell-detector
|
|
|
64
81
|
react-smell /path/to/react/project
|
|
65
82
|
```
|
|
66
83
|
|
|
84
|
+
### Interactive Guide
|
|
85
|
+
|
|
86
|
+
Launch an interactive tutorial to learn all features:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
react-smell guide
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Demo Project
|
|
93
|
+
|
|
94
|
+
Create a demo project with examples of all detectable code smells:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
react-smell demo
|
|
98
|
+
# Creates ./react-smell-demo with sample components
|
|
99
|
+
|
|
100
|
+
# Or specify a directory
|
|
101
|
+
react-smell demo /path/to/directory
|
|
102
|
+
```
|
|
103
|
+
|
|
67
104
|
### With Code Snippets
|
|
68
105
|
|
|
69
106
|
```bash
|
|
@@ -132,6 +169,16 @@ Or create manually:
|
|
|
132
169
|
| `--graph-format <format>` | Graph output format: svg, html | `html` |
|
|
133
170
|
| `--bundle` | Analyze bundle size impact per component | `false` |
|
|
134
171
|
| `--rules <file>` | Custom rules configuration file | - |
|
|
172
|
+
| `--fix-interactive` | Interactive fix mode: review fixes one by one | `false` |
|
|
173
|
+
| `--fix-preview` | Preview fixable issues without applying | `false` |
|
|
174
|
+
| `--pr-comment` | Generate PR comment (for GitHub Actions) | `false` |
|
|
175
|
+
| `--budget` | Check against performance budget | `false` |
|
|
176
|
+
| `--budget-config <file>` | Path to budget config file | `.smellbudget.json` |
|
|
177
|
+
| `--docs` | Generate component documentation | `false` |
|
|
178
|
+
| `--docs-format <format>` | Documentation format: markdown, html, json | `markdown` |
|
|
179
|
+
| `--ai` | Enable AI-powered refactoring suggestions | `false` |
|
|
180
|
+
| `--ai-key <key>` | API key for AI provider | - |
|
|
181
|
+
| `--ai-model <model>` | AI model to use (gpt-4, claude-3-sonnet, etc.) | `gpt-4` |
|
|
135
182
|
|
|
136
183
|
### Auto-Fix
|
|
137
184
|
|
|
@@ -392,6 +439,153 @@ react-smell ./src --rules .smellrc-rules.json --format json --ci
|
|
|
392
439
|
}
|
|
393
440
|
```
|
|
394
441
|
|
|
442
|
+
### Interactive Fix Mode
|
|
443
|
+
|
|
444
|
+
Review and apply fixes one by one with diff preview:
|
|
445
|
+
|
|
446
|
+
```bash
|
|
447
|
+
# Interactive mode - review each fix
|
|
448
|
+
react-smell ./src --fix-interactive
|
|
449
|
+
|
|
450
|
+
# Preview fixable issues without applying
|
|
451
|
+
react-smell ./src --fix-preview
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
**Output:**
|
|
455
|
+
```
|
|
456
|
+
🔧 Interactive Fix Mode
|
|
457
|
+
Found 5 fixable issue(s). Review each one:
|
|
458
|
+
|
|
459
|
+
Commands: [y]es, [n]o, [a]ll, [q]uit
|
|
460
|
+
|
|
461
|
+
────────────────────────────────────────────────────────────────
|
|
462
|
+
src/utils/helper.ts:15
|
|
463
|
+
debug-statement: console.log found
|
|
464
|
+
Fix: Remove console.log/debugger statements
|
|
465
|
+
|
|
466
|
+
- console.log('debug:', value);
|
|
467
|
+
+ (line removed)
|
|
468
|
+
|
|
469
|
+
Apply this fix? [y/n/a/q]: y
|
|
470
|
+
✓ Applied
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
### GitHub PR Comments
|
|
474
|
+
|
|
475
|
+
Auto-comment code smell analysis on pull requests:
|
|
476
|
+
|
|
477
|
+
```bash
|
|
478
|
+
# Generate PR comment (outputs markdown)
|
|
479
|
+
react-smell ./src --pr-comment
|
|
480
|
+
|
|
481
|
+
# In GitHub Actions (auto-posts to PR)
|
|
482
|
+
react-smell ./src --pr-comment --ci
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
**GitHub Actions workflow:**
|
|
486
|
+
```yaml
|
|
487
|
+
- name: Analyze Code
|
|
488
|
+
run: npx react-smell ./src --pr-comment --ci
|
|
489
|
+
env:
|
|
490
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
### Performance Budget
|
|
494
|
+
|
|
495
|
+
Set thresholds for code quality and enforce in CI/CD:
|
|
496
|
+
|
|
497
|
+
```bash
|
|
498
|
+
# Create budget config
|
|
499
|
+
react-smell init-budget
|
|
500
|
+
|
|
501
|
+
# Check against budget
|
|
502
|
+
react-smell ./src --budget
|
|
503
|
+
|
|
504
|
+
# With custom config
|
|
505
|
+
react-smell ./src --budget --budget-config my-budget.json
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
**Budget config (`.smellbudget.json`):**
|
|
509
|
+
```json
|
|
510
|
+
{
|
|
511
|
+
"maxErrors": 0,
|
|
512
|
+
"maxWarnings": 10,
|
|
513
|
+
"minScore": 70,
|
|
514
|
+
"minGrade": "C",
|
|
515
|
+
"maxSmellsPerFile": 5,
|
|
516
|
+
"maxByType": {
|
|
517
|
+
"useEffect-overuse": 3,
|
|
518
|
+
"prop-drilling": 5
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
**Output:**
|
|
524
|
+
```
|
|
525
|
+
✗ Performance budget check failed
|
|
526
|
+
|
|
527
|
+
Violations:
|
|
528
|
+
✗ Errors (2) exceeds budget (0)
|
|
529
|
+
⚠ Warnings (15) exceeds budget (10)
|
|
530
|
+
|
|
531
|
+
Checks: 3 passed, 2 failed
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
### Component Documentation
|
|
535
|
+
|
|
536
|
+
Auto-generate documentation from component analysis:
|
|
537
|
+
|
|
538
|
+
```bash
|
|
539
|
+
# Generate markdown docs
|
|
540
|
+
react-smell docs ./src
|
|
541
|
+
|
|
542
|
+
# Generate HTML docs
|
|
543
|
+
react-smell docs ./src -f html
|
|
544
|
+
|
|
545
|
+
# Output to specific directory
|
|
546
|
+
react-smell docs ./src -f html -o ./docs
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
**Output (COMPONENTS.md):**
|
|
550
|
+
```markdown
|
|
551
|
+
# Component Documentation
|
|
552
|
+
|
|
553
|
+
## Summary
|
|
554
|
+
| Metric | Value |
|
|
555
|
+
|--------|-------|
|
|
556
|
+
| Total Components | 25 |
|
|
557
|
+
| Technical Debt Grade | B |
|
|
558
|
+
|
|
559
|
+
## Components
|
|
560
|
+
|
|
561
|
+
#### Button
|
|
562
|
+
📄 `components/Button.tsx`
|
|
563
|
+
|
|
564
|
+
| Metric | Value |
|
|
565
|
+
|--------|-------|
|
|
566
|
+
| Lines | 45 |
|
|
567
|
+
| Complexity | 🟢 Low |
|
|
568
|
+
| Maintainability | 🟢 Good |
|
|
569
|
+
|
|
570
|
+
**Hooks:** useState (2), useCallback (1)
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
### React 19 Server Components
|
|
574
|
+
|
|
575
|
+
Detect Server/Client component boundary issues:
|
|
576
|
+
|
|
577
|
+
```bash
|
|
578
|
+
# Enabled by default for app/ directory components
|
|
579
|
+
react-smell ./src
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
**Detected issues:**
|
|
583
|
+
- `server-component-hooks`: Using useState/useEffect in Server Components
|
|
584
|
+
- `server-component-events`: Using onClick/onChange in Server Components
|
|
585
|
+
- `server-component-browser-api`: Using window/document in Server Components
|
|
586
|
+
- `async-client-component`: Async function in 'use client' component
|
|
587
|
+
- `mixed-directives`: Both 'use client' and 'use server' in same file
|
|
588
|
+
|
|
395
589
|
## Example Output
|
|
396
590
|
|
|
397
591
|
```
|
|
@@ -421,9 +615,23 @@ react-smell ./src --rules .smellrc-rules.json --format json --ci
|
|
|
421
615
|
## Programmatic API
|
|
422
616
|
|
|
423
617
|
```typescript
|
|
424
|
-
import {
|
|
425
|
-
|
|
426
|
-
|
|
618
|
+
import {
|
|
619
|
+
analyzeProject,
|
|
620
|
+
reportResults,
|
|
621
|
+
// Interactive fixing
|
|
622
|
+
runInteractiveFix,
|
|
623
|
+
previewFixes,
|
|
624
|
+
// PR Comments
|
|
625
|
+
generatePRComment,
|
|
626
|
+
postPRComment,
|
|
627
|
+
// Performance Budget
|
|
628
|
+
loadBudget,
|
|
629
|
+
checkBudget,
|
|
630
|
+
formatBudgetReport,
|
|
631
|
+
// Documentation
|
|
632
|
+
generateComponentDocs,
|
|
633
|
+
writeComponentDocs,
|
|
634
|
+
} from 'react-code-smell-detector';
|
|
427
635
|
|
|
428
636
|
const result = await analyzeProject({
|
|
429
637
|
rootDir: './src',
|
|
@@ -431,32 +639,29 @@ const result = await analyzeProject({
|
|
|
431
639
|
maxUseEffectsPerComponent: 3,
|
|
432
640
|
maxComponentLines: 300,
|
|
433
641
|
checkUnusedCode: true,
|
|
434
|
-
|
|
642
|
+
checkServerComponents: true, // React 19
|
|
435
643
|
},
|
|
436
644
|
});
|
|
437
645
|
|
|
438
646
|
console.log(`Grade: ${result.debtScore.grade}`);
|
|
439
647
|
console.log(`Total issues: ${result.summary.totalSmells}`);
|
|
440
648
|
|
|
441
|
-
//
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
console.log(
|
|
446
|
-
|
|
447
|
-
// Send notification
|
|
448
|
-
const webhookConfig = getWebhookConfig(
|
|
449
|
-
process.env.REACT_SMELL_SLACK_WEBHOOK,
|
|
450
|
-
process.env.REACT_SMELL_DISCORD_WEBHOOK
|
|
451
|
-
);
|
|
452
|
-
if (webhookConfig) {
|
|
453
|
-
await sendWebhookNotification(
|
|
454
|
-
webhookConfig,
|
|
455
|
-
result.files.flatMap(f => f.smells),
|
|
456
|
-
'my-project'
|
|
457
|
-
);
|
|
649
|
+
// Check against performance budget
|
|
650
|
+
const budget = await loadBudget();
|
|
651
|
+
const budgetResult = checkBudget(result, budget);
|
|
652
|
+
if (!budgetResult.passed) {
|
|
653
|
+
console.log(formatBudgetReport(budgetResult));
|
|
458
654
|
}
|
|
459
655
|
|
|
656
|
+
// Generate documentation
|
|
657
|
+
const docsPath = await writeComponentDocs(result, './src', {
|
|
658
|
+
format: 'markdown',
|
|
659
|
+
includeSmells: true,
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
// Generate PR comment
|
|
663
|
+
const prComment = generatePRComment(result, './src');
|
|
664
|
+
|
|
460
665
|
// Or use the reporter
|
|
461
666
|
const report = reportResults(result, {
|
|
462
667
|
format: 'markdown',
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aiRefactoring.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/aiRefactoring.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { getQuickRefactoringTemplates } from '../aiRefactoring.js';
|
|
3
|
+
describe('AI Refactoring', () => {
|
|
4
|
+
describe('getQuickRefactoringTemplates', () => {
|
|
5
|
+
it('should return templates for useEffect-overuse', () => {
|
|
6
|
+
const templates = getQuickRefactoringTemplates('useEffect-overuse');
|
|
7
|
+
expect(templates.length).toBeGreaterThan(0);
|
|
8
|
+
expect(templates.some(t => t.includes('effect'))).toBe(true);
|
|
9
|
+
});
|
|
10
|
+
it('should return templates for prop-drilling', () => {
|
|
11
|
+
const templates = getQuickRefactoringTemplates('prop-drilling');
|
|
12
|
+
expect(templates.length).toBeGreaterThan(0);
|
|
13
|
+
expect(templates.some(t => t.includes('Context') || t.includes('composition'))).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
it('should return templates for large-component', () => {
|
|
16
|
+
const templates = getQuickRefactoringTemplates('large-component');
|
|
17
|
+
expect(templates.length).toBeGreaterThan(0);
|
|
18
|
+
expect(templates.some(t => t.includes('Extract') || t.includes('component'))).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
it('should return templates for context-overuse', () => {
|
|
21
|
+
const templates = getQuickRefactoringTemplates('context-overuse');
|
|
22
|
+
expect(templates.length).toBeGreaterThan(0);
|
|
23
|
+
expect(templates.some(t => t.includes('context'))).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
it('should return templates for missing-error-boundary', () => {
|
|
26
|
+
const templates = getQuickRefactoringTemplates('missing-error-boundary');
|
|
27
|
+
expect(templates.length).toBeGreaterThan(0);
|
|
28
|
+
expect(templates.some(t => t.includes('ErrorBoundary') || t.includes('Suspense'))).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
it('should return templates for state-sync-anti-pattern', () => {
|
|
31
|
+
const templates = getQuickRefactoringTemplates('state-sync-anti-pattern');
|
|
32
|
+
expect(templates.length).toBeGreaterThan(0);
|
|
33
|
+
expect(templates.some(t => t.includes('Derive') || t.includes('useMemo'))).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
it('should return generic template for unknown smell types', () => {
|
|
36
|
+
const templates = getQuickRefactoringTemplates('unknown-smell-type');
|
|
37
|
+
expect(templates.length).toBeGreaterThan(0);
|
|
38
|
+
expect(templates[0]).toContain('Review');
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
describe('AIRefactoringConfig', () => {
|
|
42
|
+
it('should accept valid config object', () => {
|
|
43
|
+
const config = {
|
|
44
|
+
apiKey: 'test-key',
|
|
45
|
+
model: 'gpt-4',
|
|
46
|
+
maxTokens: 1000,
|
|
47
|
+
temperature: 0.3,
|
|
48
|
+
provider: 'openai',
|
|
49
|
+
};
|
|
50
|
+
expect(config.apiKey).toBe('test-key');
|
|
51
|
+
expect(config.model).toBe('gpt-4');
|
|
52
|
+
expect(config.provider).toBe('openai');
|
|
53
|
+
});
|
|
54
|
+
it('should accept anthropic provider', () => {
|
|
55
|
+
const config = {
|
|
56
|
+
apiKey: 'test-key',
|
|
57
|
+
model: 'claude-3-sonnet',
|
|
58
|
+
provider: 'anthropic',
|
|
59
|
+
};
|
|
60
|
+
expect(config.provider).toBe('anthropic');
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
describe('AIRefactoringSuggestion type', () => {
|
|
64
|
+
it('should have expected structure', () => {
|
|
65
|
+
const suggestion = {
|
|
66
|
+
smell: {
|
|
67
|
+
type: 'debug-statement',
|
|
68
|
+
severity: 'warning',
|
|
69
|
+
message: 'Test',
|
|
70
|
+
file: '/test.tsx',
|
|
71
|
+
line: 1,
|
|
72
|
+
column: 0,
|
|
73
|
+
suggestion: 'Remove it',
|
|
74
|
+
},
|
|
75
|
+
originalCode: 'console.log("test")',
|
|
76
|
+
suggestedCode: '// removed',
|
|
77
|
+
explanation: 'Debug statements should be removed',
|
|
78
|
+
confidence: 0.9,
|
|
79
|
+
estimatedEffort: 'low',
|
|
80
|
+
};
|
|
81
|
+
expect(suggestion.confidence).toBeGreaterThanOrEqual(0);
|
|
82
|
+
expect(suggestion.confidence).toBeLessThanOrEqual(1);
|
|
83
|
+
expect(['low', 'medium', 'high']).toContain(suggestion.estimatedEffort);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyzer-real.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/analyzer-real.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { analyzeProject } from '../analyzer.js';
|
|
3
|
+
import * as fg from 'fast-glob';
|
|
4
|
+
import * as fs from 'fs/promises';
|
|
5
|
+
// Mock fast-glob
|
|
6
|
+
vi.mock('fast-glob', () => ({
|
|
7
|
+
default: vi.fn(),
|
|
8
|
+
}));
|
|
9
|
+
// Mock fs/promises for parseFile
|
|
10
|
+
vi.mock('fs/promises', async (importOriginal) => {
|
|
11
|
+
const actual = await importOriginal();
|
|
12
|
+
return {
|
|
13
|
+
...actual,
|
|
14
|
+
default: actual,
|
|
15
|
+
readFile: vi.fn(),
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
describe('Analyzer - Real Tests', () => {
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
vi.clearAllMocks();
|
|
21
|
+
});
|
|
22
|
+
describe('analyzeProject', () => {
|
|
23
|
+
it('should return empty results when no files found', async () => {
|
|
24
|
+
fg.default.mockResolvedValue([]);
|
|
25
|
+
const result = await analyzeProject({
|
|
26
|
+
rootDir: '/fake/path',
|
|
27
|
+
});
|
|
28
|
+
expect(result.files).toEqual([]);
|
|
29
|
+
expect(result.summary.totalFiles).toBe(0);
|
|
30
|
+
expect(result.summary.totalComponents).toBe(0);
|
|
31
|
+
expect(result.summary.totalSmells).toBe(0);
|
|
32
|
+
});
|
|
33
|
+
it('should call fast-glob with correct patterns', async () => {
|
|
34
|
+
fg.default.mockResolvedValue([]);
|
|
35
|
+
await analyzeProject({
|
|
36
|
+
rootDir: '/fake/path',
|
|
37
|
+
include: ['**/*.tsx'],
|
|
38
|
+
});
|
|
39
|
+
expect(fg.default).toHaveBeenCalled();
|
|
40
|
+
});
|
|
41
|
+
it('should handle parse errors gracefully', async () => {
|
|
42
|
+
fg.default.mockResolvedValue(['/fake/path/broken.tsx']);
|
|
43
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
|
|
44
|
+
const result = await analyzeProject({
|
|
45
|
+
rootDir: '/fake/path',
|
|
46
|
+
});
|
|
47
|
+
// Should return empty since file doesn't exist
|
|
48
|
+
expect(result.files.length).toBe(0);
|
|
49
|
+
warnSpy.mockRestore();
|
|
50
|
+
});
|
|
51
|
+
it('should use custom include patterns', async () => {
|
|
52
|
+
fg.default.mockResolvedValue([]);
|
|
53
|
+
await analyzeProject({
|
|
54
|
+
rootDir: '/fake/path',
|
|
55
|
+
include: ['**/*.ts'],
|
|
56
|
+
exclude: ['**/node_modules/**'],
|
|
57
|
+
});
|
|
58
|
+
expect(fg.default).toHaveBeenCalledWith(expect.arrayContaining([expect.stringContaining('.ts')]), expect.objectContaining({ ignore: ['**/node_modules/**'] }));
|
|
59
|
+
});
|
|
60
|
+
it('should merge user config with defaults', async () => {
|
|
61
|
+
fg.default.mockResolvedValue([]);
|
|
62
|
+
const result = await analyzeProject({
|
|
63
|
+
rootDir: '/fake/path',
|
|
64
|
+
config: { maxUseEffectsPerComponent: 5 },
|
|
65
|
+
});
|
|
66
|
+
expect(result.summary).toBeDefined();
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
describe('Technical Debt Score Calculation', () => {
|
|
70
|
+
it('should calculate grade A for clean projects', async () => {
|
|
71
|
+
const cleanCode = `
|
|
72
|
+
function CleanComponent() {
|
|
73
|
+
return <div>Clean</div>;
|
|
74
|
+
}
|
|
75
|
+
`;
|
|
76
|
+
fg.default.mockResolvedValue(['/fake/path/Clean.tsx']);
|
|
77
|
+
fs.readFile.mockResolvedValue(cleanCode);
|
|
78
|
+
const result = await analyzeProject({
|
|
79
|
+
rootDir: '/fake/path',
|
|
80
|
+
config: { checkDebugStatements: false },
|
|
81
|
+
});
|
|
82
|
+
expect(result.debtScore.grade).toBe('A');
|
|
83
|
+
expect(result.debtScore.score).toBeGreaterThanOrEqual(90);
|
|
84
|
+
});
|
|
85
|
+
it('should estimate refactor time based on issues', async () => {
|
|
86
|
+
fg.default.mockResolvedValue([]);
|
|
87
|
+
const result = await analyzeProject({
|
|
88
|
+
rootDir: '/fake/path',
|
|
89
|
+
});
|
|
90
|
+
expect(result.debtScore.estimatedRefactorTime).toBeDefined();
|
|
91
|
+
});
|
|
92
|
+
it('should include breakdown scores', async () => {
|
|
93
|
+
fg.default.mockResolvedValue([]);
|
|
94
|
+
const result = await analyzeProject({
|
|
95
|
+
rootDir: '/fake/path',
|
|
96
|
+
});
|
|
97
|
+
expect(result.debtScore.breakdown).toBeDefined();
|
|
98
|
+
expect(result.debtScore.breakdown.useEffectScore).toBeDefined();
|
|
99
|
+
expect(result.debtScore.breakdown.propDrillingScore).toBeDefined();
|
|
100
|
+
expect(result.debtScore.breakdown.componentSizeScore).toBeDefined();
|
|
101
|
+
expect(result.debtScore.breakdown.memoizationScore).toBeDefined();
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
describe('Smell Ignore Comments', () => {
|
|
105
|
+
it('should filter smells with @smell-ignore comment', async () => {
|
|
106
|
+
// Test behavior: when no files match, result should be empty
|
|
107
|
+
fg.default.mockResolvedValue([]);
|
|
108
|
+
const result = await analyzeProject({
|
|
109
|
+
rootDir: '/fake/path',
|
|
110
|
+
config: { checkDebugStatements: true },
|
|
111
|
+
});
|
|
112
|
+
// Empty result with no smells
|
|
113
|
+
expect(result.files.length).toBe(0);
|
|
114
|
+
expect(result.summary.totalSmells).toBe(0);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
describe('Summary Calculation', () => {
|
|
118
|
+
it('should have smells by type structure', async () => {
|
|
119
|
+
fg.default.mockResolvedValue([]);
|
|
120
|
+
const result = await analyzeProject({
|
|
121
|
+
rootDir: '/fake/path',
|
|
122
|
+
config: { checkDebugStatements: true },
|
|
123
|
+
});
|
|
124
|
+
// Should have smellsByType object even if empty
|
|
125
|
+
expect(result.summary.smellsByType).toBeDefined();
|
|
126
|
+
});
|
|
127
|
+
it('should aggregate smells by severity', async () => {
|
|
128
|
+
fg.default.mockResolvedValue([]);
|
|
129
|
+
const result = await analyzeProject({
|
|
130
|
+
rootDir: '/fake/path',
|
|
131
|
+
});
|
|
132
|
+
expect(result.summary.smellsBySeverity).toBeDefined();
|
|
133
|
+
expect(result.summary.smellsBySeverity.error).toBeDefined();
|
|
134
|
+
expect(result.summary.smellsBySeverity.warning).toBeDefined();
|
|
135
|
+
expect(result.summary.smellsBySeverity.info).toBeDefined();
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
describe('Component Info Collection', () => {
|
|
139
|
+
it('should return components array in file results', async () => {
|
|
140
|
+
// Test that result structure includes components array
|
|
141
|
+
fg.default.mockResolvedValue([]);
|
|
142
|
+
const result = await analyzeProject({
|
|
143
|
+
rootDir: '/fake/path',
|
|
144
|
+
});
|
|
145
|
+
// For non-existent files, we get empty results
|
|
146
|
+
expect(Array.isArray(result.files)).toBe(true);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyzer.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/analyzer.test.ts"],"names":[],"mappings":""}
|