react-code-smell-detector 1.4.2 โ†’ 1.5.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.
Files changed (38) hide show
  1. package/README.md +207 -22
  2. package/dist/__tests__/parser.test.d.ts +2 -0
  3. package/dist/__tests__/parser.test.d.ts.map +1 -0
  4. package/dist/__tests__/parser.test.js +56 -0
  5. package/dist/__tests__/performanceBudget.test.d.ts +2 -0
  6. package/dist/__tests__/performanceBudget.test.d.ts.map +1 -0
  7. package/dist/__tests__/performanceBudget.test.js +91 -0
  8. package/dist/__tests__/prComments.test.d.ts +2 -0
  9. package/dist/__tests__/prComments.test.d.ts.map +1 -0
  10. package/dist/__tests__/prComments.test.js +118 -0
  11. package/dist/analyzer.d.ts.map +1 -1
  12. package/dist/analyzer.js +10 -1
  13. package/dist/cli.js +106 -1
  14. package/dist/detectors/index.d.ts +1 -0
  15. package/dist/detectors/index.d.ts.map +1 -1
  16. package/dist/detectors/index.js +2 -0
  17. package/dist/detectors/serverComponents.d.ts +11 -0
  18. package/dist/detectors/serverComponents.d.ts.map +1 -0
  19. package/dist/detectors/serverComponents.js +222 -0
  20. package/dist/docGenerator.d.ts +37 -0
  21. package/dist/docGenerator.d.ts.map +1 -0
  22. package/dist/docGenerator.js +306 -0
  23. package/dist/index.d.ts +4 -0
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +4 -0
  26. package/dist/interactiveFixer.d.ts +20 -0
  27. package/dist/interactiveFixer.d.ts.map +1 -0
  28. package/dist/interactiveFixer.js +178 -0
  29. package/dist/performanceBudget.d.ts +54 -0
  30. package/dist/performanceBudget.d.ts.map +1 -0
  31. package/dist/performanceBudget.js +218 -0
  32. package/dist/prComments.d.ts +47 -0
  33. package/dist/prComments.d.ts.map +1 -0
  34. package/dist/prComments.js +233 -0
  35. package/dist/types/index.d.ts +2 -1
  36. package/dist/types/index.d.ts.map +1 -1
  37. package/dist/types/index.js +2 -0
  38. 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
 
@@ -132,6 +149,16 @@ Or create manually:
132
149
  | `--graph-format <format>` | Graph output format: svg, html | `html` |
133
150
  | `--bundle` | Analyze bundle size impact per component | `false` |
134
151
  | `--rules <file>` | Custom rules configuration file | - |
152
+ | `--fix-interactive` | Interactive fix mode: review fixes one by one | `false` |
153
+ | `--fix-preview` | Preview fixable issues without applying | `false` |
154
+ | `--pr-comment` | Generate PR comment (for GitHub Actions) | `false` |
155
+ | `--budget` | Check against performance budget | `false` |
156
+ | `--budget-config <file>` | Path to budget config file | `.smellbudget.json` |
157
+ | `--docs` | Generate component documentation | `false` |
158
+ | `--docs-format <format>` | Documentation format: markdown, html, json | `markdown` |
159
+ | `--ai` | Enable AI-powered refactoring suggestions | `false` |
160
+ | `--ai-key <key>` | API key for AI provider | - |
161
+ | `--ai-model <model>` | AI model to use (gpt-4, claude-3-sonnet, etc.) | `gpt-4` |
135
162
 
136
163
  ### Auto-Fix
137
164
 
@@ -392,6 +419,153 @@ react-smell ./src --rules .smellrc-rules.json --format json --ci
392
419
  }
393
420
  ```
394
421
 
422
+ ### Interactive Fix Mode
423
+
424
+ Review and apply fixes one by one with diff preview:
425
+
426
+ ```bash
427
+ # Interactive mode - review each fix
428
+ react-smell ./src --fix-interactive
429
+
430
+ # Preview fixable issues without applying
431
+ react-smell ./src --fix-preview
432
+ ```
433
+
434
+ **Output:**
435
+ ```
436
+ ๐Ÿ”ง Interactive Fix Mode
437
+ Found 5 fixable issue(s). Review each one:
438
+
439
+ Commands: [y]es, [n]o, [a]ll, [q]uit
440
+
441
+ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
442
+ src/utils/helper.ts:15
443
+ debug-statement: console.log found
444
+ Fix: Remove console.log/debugger statements
445
+
446
+ - console.log('debug:', value);
447
+ + (line removed)
448
+
449
+ Apply this fix? [y/n/a/q]: y
450
+ โœ“ Applied
451
+ ```
452
+
453
+ ### GitHub PR Comments
454
+
455
+ Auto-comment code smell analysis on pull requests:
456
+
457
+ ```bash
458
+ # Generate PR comment (outputs markdown)
459
+ react-smell ./src --pr-comment
460
+
461
+ # In GitHub Actions (auto-posts to PR)
462
+ react-smell ./src --pr-comment --ci
463
+ ```
464
+
465
+ **GitHub Actions workflow:**
466
+ ```yaml
467
+ - name: Analyze Code
468
+ run: npx react-smell ./src --pr-comment --ci
469
+ env:
470
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
471
+ ```
472
+
473
+ ### Performance Budget
474
+
475
+ Set thresholds for code quality and enforce in CI/CD:
476
+
477
+ ```bash
478
+ # Create budget config
479
+ react-smell init-budget
480
+
481
+ # Check against budget
482
+ react-smell ./src --budget
483
+
484
+ # With custom config
485
+ react-smell ./src --budget --budget-config my-budget.json
486
+ ```
487
+
488
+ **Budget config (`.smellbudget.json`):**
489
+ ```json
490
+ {
491
+ "maxErrors": 0,
492
+ "maxWarnings": 10,
493
+ "minScore": 70,
494
+ "minGrade": "C",
495
+ "maxSmellsPerFile": 5,
496
+ "maxByType": {
497
+ "useEffect-overuse": 3,
498
+ "prop-drilling": 5
499
+ }
500
+ }
501
+ ```
502
+
503
+ **Output:**
504
+ ```
505
+ โœ— Performance budget check failed
506
+
507
+ Violations:
508
+ โœ— Errors (2) exceeds budget (0)
509
+ โš  Warnings (15) exceeds budget (10)
510
+
511
+ Checks: 3 passed, 2 failed
512
+ ```
513
+
514
+ ### Component Documentation
515
+
516
+ Auto-generate documentation from component analysis:
517
+
518
+ ```bash
519
+ # Generate markdown docs
520
+ react-smell docs ./src
521
+
522
+ # Generate HTML docs
523
+ react-smell docs ./src -f html
524
+
525
+ # Output to specific directory
526
+ react-smell docs ./src -f html -o ./docs
527
+ ```
528
+
529
+ **Output (COMPONENTS.md):**
530
+ ```markdown
531
+ # Component Documentation
532
+
533
+ ## Summary
534
+ | Metric | Value |
535
+ |--------|-------|
536
+ | Total Components | 25 |
537
+ | Technical Debt Grade | B |
538
+
539
+ ## Components
540
+
541
+ #### Button
542
+ ๐Ÿ“„ `components/Button.tsx`
543
+
544
+ | Metric | Value |
545
+ |--------|-------|
546
+ | Lines | 45 |
547
+ | Complexity | ๐ŸŸข Low |
548
+ | Maintainability | ๐ŸŸข Good |
549
+
550
+ **Hooks:** useState (2), useCallback (1)
551
+ ```
552
+
553
+ ### React 19 Server Components
554
+
555
+ Detect Server/Client component boundary issues:
556
+
557
+ ```bash
558
+ # Enabled by default for app/ directory components
559
+ react-smell ./src
560
+ ```
561
+
562
+ **Detected issues:**
563
+ - `server-component-hooks`: Using useState/useEffect in Server Components
564
+ - `server-component-events`: Using onClick/onChange in Server Components
565
+ - `server-component-browser-api`: Using window/document in Server Components
566
+ - `async-client-component`: Async function in 'use client' component
567
+ - `mixed-directives`: Both 'use client' and 'use server' in same file
568
+
395
569
  ## Example Output
396
570
 
397
571
  ```
@@ -421,9 +595,23 @@ react-smell ./src --rules .smellrc-rules.json --format json --ci
421
595
  ## Programmatic API
422
596
 
423
597
  ```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';
598
+ import {
599
+ analyzeProject,
600
+ reportResults,
601
+ // Interactive fixing
602
+ runInteractiveFix,
603
+ previewFixes,
604
+ // PR Comments
605
+ generatePRComment,
606
+ postPRComment,
607
+ // Performance Budget
608
+ loadBudget,
609
+ checkBudget,
610
+ formatBudgetReport,
611
+ // Documentation
612
+ generateComponentDocs,
613
+ writeComponentDocs,
614
+ } from 'react-code-smell-detector';
427
615
 
428
616
  const result = await analyzeProject({
429
617
  rootDir: './src',
@@ -431,32 +619,29 @@ const result = await analyzeProject({
431
619
  maxUseEffectsPerComponent: 3,
432
620
  maxComponentLines: 300,
433
621
  checkUnusedCode: true,
434
- baselineEnabled: true,
622
+ checkServerComponents: true, // React 19
435
623
  },
436
624
  });
437
625
 
438
626
  console.log(`Grade: ${result.debtScore.grade}`);
439
627
  console.log(`Total issues: ${result.summary.totalSmells}`);
440
628
 
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
- );
629
+ // Check against performance budget
630
+ const budget = await loadBudget();
631
+ const budgetResult = checkBudget(result, budget);
632
+ if (!budgetResult.passed) {
633
+ console.log(formatBudgetReport(budgetResult));
458
634
  }
459
635
 
636
+ // Generate documentation
637
+ const docsPath = await writeComponentDocs(result, './src', {
638
+ format: 'markdown',
639
+ includeSmells: true,
640
+ });
641
+
642
+ // Generate PR comment
643
+ const prComment = generatePRComment(result, './src');
644
+
460
645
  // Or use the reporter
461
646
  const report = reportResults(result, {
462
647
  format: 'markdown',
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=parser.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/parser.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,56 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { parseCode } from '../parser/index.js';
3
+ describe('Parser', () => {
4
+ it('should parse a simple React component', () => {
5
+ const code = `
6
+ function Hello({ name }) {
7
+ return <div>Hello {name}</div>;
8
+ }
9
+ `;
10
+ const result = parseCode(code);
11
+ expect(result.components).toHaveLength(1);
12
+ expect(result.components[0].name).toBe('Hello');
13
+ });
14
+ it('should detect useState hook', () => {
15
+ const code = `
16
+ function Counter() {
17
+ const [count, setCount] = useState(0);
18
+ return <button onClick={() => setCount(count + 1)}>{count}</button>;
19
+ }
20
+ `;
21
+ const result = parseCode(code);
22
+ expect(result.components).toHaveLength(1);
23
+ expect(result.components[0].hooks.useState).toHaveLength(1);
24
+ });
25
+ it('should detect useEffect hook', () => {
26
+ const code = `
27
+ function Timer() {
28
+ const [time, setTime] = useState(0);
29
+ useEffect(() => {
30
+ const id = setInterval(() => setTime(t => t + 1), 1000);
31
+ return () => clearInterval(id);
32
+ }, []);
33
+ return <span>{time}</span>;
34
+ }
35
+ `;
36
+ const result = parseCode(code);
37
+ expect(result.components).toHaveLength(1);
38
+ expect(result.components[0].hooks.useEffect).toHaveLength(1);
39
+ });
40
+ it('should extract component props', () => {
41
+ const code = `
42
+ function Card({ title, description, onClick }) {
43
+ return (
44
+ <div onClick={onClick}>
45
+ <h1>{title}</h1>
46
+ <p>{description}</p>
47
+ </div>
48
+ );
49
+ }
50
+ `;
51
+ const result = parseCode(code);
52
+ expect(result.components[0].props).toContain('title');
53
+ expect(result.components[0].props).toContain('description');
54
+ expect(result.components[0].props).toContain('onClick');
55
+ });
56
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=performanceBudget.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"performanceBudget.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/performanceBudget.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,91 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { checkBudget } from '../performanceBudget.js';
3
+ describe('Performance Budget', () => {
4
+ const createMockResult = (overrides = {}) => ({
5
+ files: [],
6
+ summary: {
7
+ totalFiles: 5,
8
+ totalComponents: 10,
9
+ totalSmells: 3,
10
+ smellsByType: Object.fromEntries([
11
+ 'useEffect-overuse', 'prop-drilling', 'large-component', 'unmemoized-calculation',
12
+ 'missing-dependency', 'state-in-loop', 'inline-function-prop', 'deep-nesting',
13
+ 'missing-key', 'hooks-rules-violation', 'dependency-array-issue', 'nested-ternary',
14
+ 'dead-code', 'magic-value', 'debug-statement', 'todo-comment', 'security-xss',
15
+ 'security-eval', 'security-secrets', 'a11y-missing-alt', 'a11y-missing-label',
16
+ 'a11y-interactive-role', 'a11y-keyboard', 'a11y-semantic',
17
+ 'nextjs-client-server-boundary', 'nextjs-missing-metadata', 'nextjs-image-unoptimized',
18
+ 'nextjs-router-misuse', 'rn-inline-style', 'rn-missing-accessibility', 'rn-performance-issue',
19
+ 'nodejs-callback-hell', 'nodejs-unhandled-promise', 'nodejs-sync-io', 'nodejs-missing-error-handling',
20
+ 'js-var-usage', 'js-loose-equality', 'js-implicit-coercion', 'js-global-pollution',
21
+ 'ts-any-usage', 'ts-missing-return-type', 'ts-non-null-assertion', 'ts-type-assertion',
22
+ 'high-cyclomatic-complexity', 'high-cognitive-complexity',
23
+ 'memory-leak-event-listener', 'memory-leak-subscription', 'memory-leak-timer', 'memory-leak-async',
24
+ 'circular-dependency', 'barrel-file-import', 'namespace-import', 'excessive-imports',
25
+ 'unused-export', 'dead-import',
26
+ 'server-component-hooks', 'server-component-events', 'server-component-browser-api',
27
+ 'async-client-component', 'mixed-directives', 'custom-rule'
28
+ ].map(key => [key, 0])),
29
+ smellsBySeverity: { error: 0, warning: 2, info: 1 },
30
+ },
31
+ debtScore: {
32
+ score: 85,
33
+ grade: 'B',
34
+ breakdown: {
35
+ useEffectScore: 90,
36
+ propDrillingScore: 80,
37
+ componentSizeScore: 85,
38
+ memoizationScore: 85,
39
+ },
40
+ estimatedRefactorTime: '< 30 minutes',
41
+ },
42
+ ...overrides,
43
+ });
44
+ it('should pass when all budgets are met', () => {
45
+ const budget = {
46
+ maxTotalSmells: 10,
47
+ maxErrors: 5,
48
+ minGrade: 'C',
49
+ };
50
+ const result = checkBudget(createMockResult(), budget);
51
+ expect(result.passed).toBe(true);
52
+ expect(result.violations).toHaveLength(0);
53
+ });
54
+ it('should fail when total smells exceed budget', () => {
55
+ const budget = {
56
+ maxTotalSmells: 2,
57
+ };
58
+ const result = checkBudget(createMockResult(), budget);
59
+ expect(result.passed).toBe(false);
60
+ expect(result.violations).toHaveLength(1);
61
+ expect(result.violations[0].rule).toBe('maxTotalSmells');
62
+ });
63
+ it('should fail when errors exceed budget', () => {
64
+ const budget = {
65
+ maxErrors: 0,
66
+ };
67
+ const mockResult = createMockResult();
68
+ mockResult.summary.smellsBySeverity.error = 2;
69
+ const result = checkBudget(mockResult, budget);
70
+ expect(result.passed).toBe(false);
71
+ expect(result.violations[0].rule).toBe('maxErrors');
72
+ });
73
+ it('should fail when grade is below minimum', () => {
74
+ const budget = {
75
+ minGrade: 'A',
76
+ };
77
+ const result = checkBudget(createMockResult(), budget);
78
+ expect(result.passed).toBe(false);
79
+ expect(result.violations[0].rule).toBe('minGrade');
80
+ });
81
+ it('should allow warnings as non-blocking violations', () => {
82
+ const budget = {
83
+ maxWarnings: 1,
84
+ };
85
+ const result = checkBudget(createMockResult(), budget);
86
+ // Should have violation but still pass (warnings are non-blocking)
87
+ expect(result.violations).toHaveLength(1);
88
+ expect(result.violations[0].severity).toBe('warning');
89
+ expect(result.passed).toBe(true);
90
+ });
91
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=prComments.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prComments.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/prComments.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,118 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { generatePRComment, generateInlineComments } from '../prComments.js';
3
+ describe('PR Comments', () => {
4
+ const createMockResult = () => ({
5
+ files: [
6
+ {
7
+ file: '/src/components/Button.tsx',
8
+ components: [
9
+ {
10
+ name: 'Button',
11
+ file: '/src/components/Button.tsx',
12
+ startLine: 1,
13
+ endLine: 50,
14
+ lineCount: 50,
15
+ useEffectCount: 1,
16
+ useStateCount: 2,
17
+ useMemoCount: 0,
18
+ useCallbackCount: 0,
19
+ propsCount: 3,
20
+ propsDrillingDepth: 0,
21
+ hasExpensiveCalculation: false,
22
+ },
23
+ ],
24
+ smells: [
25
+ {
26
+ type: 'useEffect-overuse',
27
+ severity: 'warning',
28
+ message: 'Component has multiple useEffect hooks',
29
+ file: '/src/components/Button.tsx',
30
+ line: 10,
31
+ column: 0,
32
+ suggestion: 'Consider consolidating effects',
33
+ },
34
+ ],
35
+ imports: [],
36
+ },
37
+ ],
38
+ summary: {
39
+ totalFiles: 1,
40
+ totalComponents: 1,
41
+ totalSmells: 1,
42
+ smellsByType: {
43
+ 'useEffect-overuse': 1,
44
+ },
45
+ smellsBySeverity: { error: 0, warning: 1, info: 0 },
46
+ },
47
+ debtScore: {
48
+ score: 85,
49
+ grade: 'B',
50
+ breakdown: {
51
+ useEffectScore: 85,
52
+ propDrillingScore: 100,
53
+ componentSizeScore: 100,
54
+ memoizationScore: 100,
55
+ },
56
+ estimatedRefactorTime: '< 30 minutes',
57
+ },
58
+ });
59
+ it('should generate a markdown PR comment', () => {
60
+ const result = createMockResult();
61
+ const comment = generatePRComment(result, '/');
62
+ expect(comment).toContain('## ๐Ÿ” Code Smell Analysis Report');
63
+ expect(comment).toContain('Grade: **B**');
64
+ expect(comment).toContain('Total Issues | 1');
65
+ expect(comment).toContain('useEffect-overuse');
66
+ });
67
+ it('should include score breakdown', () => {
68
+ const result = createMockResult();
69
+ const comment = generatePRComment(result, '/');
70
+ expect(comment).toContain('Score Breakdown');
71
+ expect(comment).toContain('useEffect Usage');
72
+ expect(comment).toContain('85/100');
73
+ });
74
+ it('should generate inline comments for smells', () => {
75
+ const smells = [
76
+ {
77
+ type: 'debug-statement',
78
+ severity: 'warning',
79
+ message: 'console.log found',
80
+ file: '/src/utils/helper.ts',
81
+ line: 15,
82
+ column: 0,
83
+ suggestion: 'Remove debug statements',
84
+ },
85
+ ];
86
+ const comments = generateInlineComments(smells, '/');
87
+ expect(comments).toHaveLength(1);
88
+ expect(comments[0].path).toBe('src/utils/helper.ts');
89
+ expect(comments[0].line).toBe(15);
90
+ expect(comments[0].body).toContain('debug-statement');
91
+ });
92
+ it('should group multiple smells on same line', () => {
93
+ const smells = [
94
+ {
95
+ type: 'debug-statement',
96
+ severity: 'warning',
97
+ message: 'console.log found',
98
+ file: '/src/utils/helper.ts',
99
+ line: 15,
100
+ column: 0,
101
+ suggestion: 'Remove debug statements',
102
+ },
103
+ {
104
+ type: 'js-var-usage',
105
+ severity: 'warning',
106
+ message: 'var usage detected',
107
+ file: '/src/utils/helper.ts',
108
+ line: 15,
109
+ column: 0,
110
+ suggestion: 'Use let or const',
111
+ },
112
+ ];
113
+ const comments = generateInlineComments(smells, '/');
114
+ expect(comments).toHaveLength(1);
115
+ expect(comments[0].body).toContain('debug-statement');
116
+ expect(comments[0].body).toContain('js-var-usage');
117
+ });
118
+ });
@@ -1 +1 @@
1
- {"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AA+BA,OAAO,EACL,cAAc,EAMd,cAAc,EAIf,MAAM,kBAAkB,CAAC;AAE1B,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;CAClC;AAED,wBAAsB,cAAc,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CA8DtF;AA0QD,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AAiCA,OAAO,EACL,cAAc,EAMd,cAAc,EAIf,MAAM,kBAAkB,CAAC;AAE1B,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;CAClC;AAED,wBAAsB,cAAc,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CA8DtF;AAmRD,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC"}
package/dist/analyzer.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import fg from 'fast-glob';
2
2
  import path from 'path';
3
3
  import { parseFile } from './parser/index.js';
4
- import { detectUseEffectOveruse, detectPropDrilling, analyzePropDrillingDepth, detectLargeComponent, detectUnmemoizedCalculations, detectMissingKeys, detectHooksRulesViolations, detectDependencyArrayIssues, detectNestedTernaries, detectDeadCode, detectMagicValues, detectNextjsIssues, detectReactNativeIssues, detectNodejsIssues, detectJavascriptIssues, detectTypescriptIssues, detectDebugStatements, detectSecurityIssues, detectAccessibilityIssues, detectComplexity, detectMemoryLeaks, detectImportIssues, detectUnusedCode, } from './detectors/index.js';
4
+ import { detectUseEffectOveruse, detectPropDrilling, analyzePropDrillingDepth, detectLargeComponent, detectUnmemoizedCalculations, detectMissingKeys, detectHooksRulesViolations, detectDependencyArrayIssues, detectNestedTernaries, detectDeadCode, detectMagicValues, detectNextjsIssues, detectReactNativeIssues, detectNodejsIssues, detectJavascriptIssues, detectTypescriptIssues, detectDebugStatements, detectSecurityIssues, detectAccessibilityIssues, detectComplexity, detectMemoryLeaks, detectImportIssues, detectUnusedCode, detectServerComponentIssues, detectAsyncComponentIssues, } from './detectors/index.js';
5
5
  import { parseCustomRules, detectCustomRuleViolations } from './customRules.js';
6
6
  import { buildDependencyGraph } from './graphGenerator.js';
7
7
  import { analyzeBundleImpact } from './bundleAnalyzer.js';
@@ -100,6 +100,9 @@ function analyzeFile(parseResult, filePath, config) {
100
100
  smells.push(...detectMemoryLeaks(component, filePath, sourceCode, config));
101
101
  smells.push(...detectImportIssues(component, filePath, sourceCode, config));
102
102
  smells.push(...detectUnusedCode(component, filePath, sourceCode, config));
103
+ // Server Components (React 19)
104
+ smells.push(...detectServerComponentIssues(component, filePath, sourceCode, config, imports));
105
+ smells.push(...detectAsyncComponentIssues(component, filePath, sourceCode, config));
103
106
  // Custom rules
104
107
  const customRules = parseCustomRules(config);
105
108
  if (customRules.length > 0) {
@@ -213,6 +216,12 @@ function calculateSummary(files) {
213
216
  // Unused code
214
217
  'unused-export': 0,
215
218
  'dead-import': 0,
219
+ // Server Components (React 19)
220
+ 'server-component-hooks': 0,
221
+ 'server-component-events': 0,
222
+ 'server-component-browser-api': 0,
223
+ 'async-client-component': 0,
224
+ 'mixed-directives': 0,
216
225
  // Custom rules
217
226
  'custom-rule': 0,
218
227
  };