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.
Files changed (110) hide show
  1. package/README.md +227 -22
  2. package/dist/__tests__/aiRefactoring.test.d.ts +2 -0
  3. package/dist/__tests__/aiRefactoring.test.d.ts.map +1 -0
  4. package/dist/__tests__/aiRefactoring.test.js +86 -0
  5. package/dist/__tests__/analyzer-real.test.d.ts +2 -0
  6. package/dist/__tests__/analyzer-real.test.d.ts.map +1 -0
  7. package/dist/__tests__/analyzer-real.test.js +149 -0
  8. package/dist/__tests__/analyzer.test.d.ts +2 -0
  9. package/dist/__tests__/analyzer.test.d.ts.map +1 -0
  10. package/dist/__tests__/analyzer.test.js +173 -0
  11. package/dist/__tests__/baseline.test.d.ts +2 -0
  12. package/dist/__tests__/baseline.test.d.ts.map +1 -0
  13. package/dist/__tests__/baseline.test.js +136 -0
  14. package/dist/__tests__/bundleAnalyzer.test.d.ts +2 -0
  15. package/dist/__tests__/bundleAnalyzer.test.d.ts.map +1 -0
  16. package/dist/__tests__/bundleAnalyzer.test.js +182 -0
  17. package/dist/__tests__/customRules.test.d.ts +2 -0
  18. package/dist/__tests__/customRules.test.d.ts.map +1 -0
  19. package/dist/__tests__/customRules.test.js +283 -0
  20. package/dist/__tests__/detectors/index.test.d.ts +2 -0
  21. package/dist/__tests__/detectors/index.test.d.ts.map +1 -0
  22. package/dist/__tests__/detectors/index.test.js +1012 -0
  23. package/dist/__tests__/detectors/newDetectors.test.d.ts +2 -0
  24. package/dist/__tests__/detectors/newDetectors.test.d.ts.map +1 -0
  25. package/dist/__tests__/detectors/newDetectors.test.js +333 -0
  26. package/dist/__tests__/docGenerator.test.d.ts +2 -0
  27. package/dist/__tests__/docGenerator.test.d.ts.map +1 -0
  28. package/dist/__tests__/docGenerator.test.js +157 -0
  29. package/dist/__tests__/fixer.test.d.ts +2 -0
  30. package/dist/__tests__/fixer.test.d.ts.map +1 -0
  31. package/dist/__tests__/fixer.test.js +193 -0
  32. package/dist/__tests__/git.test.d.ts +2 -0
  33. package/dist/__tests__/git.test.d.ts.map +1 -0
  34. package/dist/__tests__/git.test.js +38 -0
  35. package/dist/__tests__/graphGenerator.test.d.ts +2 -0
  36. package/dist/__tests__/graphGenerator.test.d.ts.map +1 -0
  37. package/dist/__tests__/graphGenerator.test.js +190 -0
  38. package/dist/__tests__/htmlReporter.test.d.ts +2 -0
  39. package/dist/__tests__/htmlReporter.test.d.ts.map +1 -0
  40. package/dist/__tests__/htmlReporter.test.js +258 -0
  41. package/dist/__tests__/interactiveFixer.test.d.ts +2 -0
  42. package/dist/__tests__/interactiveFixer.test.d.ts.map +1 -0
  43. package/dist/__tests__/interactiveFixer.test.js +231 -0
  44. package/dist/__tests__/parser.test.d.ts +2 -0
  45. package/dist/__tests__/parser.test.d.ts.map +1 -0
  46. package/dist/__tests__/parser.test.js +56 -0
  47. package/dist/__tests__/performanceBudget.test.d.ts +2 -0
  48. package/dist/__tests__/performanceBudget.test.d.ts.map +1 -0
  49. package/dist/__tests__/performanceBudget.test.js +242 -0
  50. package/dist/__tests__/prComments.test.d.ts +2 -0
  51. package/dist/__tests__/prComments.test.d.ts.map +1 -0
  52. package/dist/__tests__/prComments.test.js +118 -0
  53. package/dist/__tests__/reporter.test.d.ts +2 -0
  54. package/dist/__tests__/reporter.test.d.ts.map +1 -0
  55. package/dist/__tests__/reporter.test.js +136 -0
  56. package/dist/__tests__/watcher.test.d.ts +2 -0
  57. package/dist/__tests__/watcher.test.d.ts.map +1 -0
  58. package/dist/__tests__/watcher.test.js +161 -0
  59. package/dist/__tests__/webhooks.test.d.ts +2 -0
  60. package/dist/__tests__/webhooks.test.d.ts.map +1 -0
  61. package/dist/__tests__/webhooks.test.js +209 -0
  62. package/dist/aiRefactoring.d.ts +29 -0
  63. package/dist/aiRefactoring.d.ts.map +1 -0
  64. package/dist/aiRefactoring.js +290 -0
  65. package/dist/analyzer.d.ts.map +1 -1
  66. package/dist/analyzer.js +33 -1
  67. package/dist/cli.js +123 -1
  68. package/dist/detectors/contextApi.d.ts +11 -0
  69. package/dist/detectors/contextApi.d.ts.map +1 -0
  70. package/dist/detectors/contextApi.js +151 -0
  71. package/dist/detectors/errorBoundary.d.ts +11 -0
  72. package/dist/detectors/errorBoundary.d.ts.map +1 -0
  73. package/dist/detectors/errorBoundary.js +167 -0
  74. package/dist/detectors/formValidation.d.ts +11 -0
  75. package/dist/detectors/formValidation.d.ts.map +1 -0
  76. package/dist/detectors/formValidation.js +193 -0
  77. package/dist/detectors/index.d.ts +6 -0
  78. package/dist/detectors/index.d.ts.map +1 -1
  79. package/dist/detectors/index.js +12 -0
  80. package/dist/detectors/serverComponents.d.ts +11 -0
  81. package/dist/detectors/serverComponents.d.ts.map +1 -0
  82. package/dist/detectors/serverComponents.js +222 -0
  83. package/dist/detectors/stateManagement.d.ts +11 -0
  84. package/dist/detectors/stateManagement.d.ts.map +1 -0
  85. package/dist/detectors/stateManagement.js +193 -0
  86. package/dist/detectors/testingGaps.d.ts +15 -0
  87. package/dist/detectors/testingGaps.d.ts.map +1 -0
  88. package/dist/detectors/testingGaps.js +182 -0
  89. package/dist/docGenerator.d.ts +37 -0
  90. package/dist/docGenerator.d.ts.map +1 -0
  91. package/dist/docGenerator.js +306 -0
  92. package/dist/guide.d.ts +9 -0
  93. package/dist/guide.d.ts.map +1 -0
  94. package/dist/guide.js +922 -0
  95. package/dist/index.d.ts +5 -0
  96. package/dist/index.d.ts.map +1 -1
  97. package/dist/index.js +5 -0
  98. package/dist/interactiveFixer.d.ts +20 -0
  99. package/dist/interactiveFixer.d.ts.map +1 -0
  100. package/dist/interactiveFixer.js +178 -0
  101. package/dist/performanceBudget.d.ts +54 -0
  102. package/dist/performanceBudget.d.ts.map +1 -0
  103. package/dist/performanceBudget.js +218 -0
  104. package/dist/prComments.d.ts +47 -0
  105. package/dist/prComments.d.ts.map +1 -0
  106. package/dist/prComments.js +233 -0
  107. package/dist/types/index.d.ts +12 -1
  108. package/dist/types/index.d.ts.map +1 -1
  109. package/dist/types/index.js +18 -0
  110. 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 (51+ smell types)
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 { analyzeProject, reportResults } from 'react-code-smell-detector';
425
- import { initializeBaseline, recordBaseline, getTrendAnalysis, formatTrendReport } from 'react-code-smell-detector';
426
- import { sendWebhookNotification, getWebhookConfig } from 'react-code-smell-detector';
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
- baselineEnabled: true,
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
- // Track baseline
442
- initializeBaseline('./src');
443
- recordBaseline('./src', result.files.flatMap(f => f.smells));
444
- const trend = getTrendAnalysis('./src');
445
- console.log(formatTrendReport('./src'));
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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=aiRefactoring.test.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=analyzer-real.test.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=analyzer.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyzer.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/analyzer.test.ts"],"names":[],"mappings":""}