react-code-smell-detector 1.4.1 โ 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.
- package/README.md +347 -22
- 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 +91 -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/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +34 -1
- package/dist/bundleAnalyzer.d.ts +25 -0
- package/dist/bundleAnalyzer.d.ts.map +1 -0
- package/dist/bundleAnalyzer.js +375 -0
- package/dist/cli.js +148 -1
- package/dist/customRules.d.ts +31 -0
- package/dist/customRules.d.ts.map +1 -0
- package/dist/customRules.js +289 -0
- package/dist/detectors/complexity.d.ts +0 -4
- package/dist/detectors/complexity.d.ts.map +1 -1
- package/dist/detectors/complexity.js +1 -1
- package/dist/detectors/deadCode.d.ts +0 -7
- package/dist/detectors/deadCode.d.ts.map +1 -1
- package/dist/detectors/deadCode.js +0 -24
- package/dist/detectors/index.d.ts +3 -2
- package/dist/detectors/index.d.ts.map +1 -1
- package/dist/detectors/index.js +4 -2
- 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/docGenerator.d.ts +37 -0
- package/dist/docGenerator.d.ts.map +1 -0
- package/dist/docGenerator.js +306 -0
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +0 -7
- package/dist/graphGenerator.d.ts +34 -0
- package/dist/graphGenerator.d.ts.map +1 -0
- package/dist/graphGenerator.js +320 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -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/reporter.js +2 -0
- package/dist/types/index.d.ts +7 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +10 -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
|
|
@@ -21,6 +21,20 @@ A CLI tool that analyzes React projects and detects common code smells, providin
|
|
|
21
21
|
- ๐๏ธ **Unused Code Detection**: Find unused exports and dead imports
|
|
22
22
|
- ๐ **Baseline Tracking**: Track code smell trends over time with git commit history
|
|
23
23
|
- ๐ฌ **Chat Notifications**: Send analysis results to Slack, Discord, or custom webhooks
|
|
24
|
+
- ๐ **Dependency Graph Visualization**: Visual SVG/HTML of component and import relationships
|
|
25
|
+
- ๐ฆ **Bundle Size Impact**: Per-component bundle size estimates and optimization suggestions
|
|
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
|
|
24
38
|
|
|
25
39
|
### Detected Code Smells
|
|
26
40
|
|
|
@@ -38,7 +52,14 @@ A CLI tool that analyzes React projects and detects common code smells, providin
|
|
|
38
52
|
| **Code Complexity** | Cyclomatic complexity, cognitive complexity, deep nesting |
|
|
39
53
|
| **Import Issues** | Circular dependencies, barrel file imports, excessive imports |
|
|
40
54
|
| **Unused Code** | Unused exports, dead imports |
|
|
55
|
+
| **Custom Rules** | User-defined code quality rules |
|
|
56
|
+
| **Server Components** | React 19 client/server boundary issues, async components |
|
|
41
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 |
|
|
42
63
|
|
|
43
64
|
## Installation
|
|
44
65
|
|
|
@@ -124,6 +145,20 @@ Or create manually:
|
|
|
124
145
|
| `--discord <url>` | Discord webhook URL for notifications | - |
|
|
125
146
|
| `--webhook <url>` | Generic webhook URL for notifications | - |
|
|
126
147
|
| `--webhook-threshold <number>` | Only notify if smells exceed threshold | `10` |
|
|
148
|
+
| `--graph` | Generate dependency graph visualization | `false` |
|
|
149
|
+
| `--graph-format <format>` | Graph output format: svg, html | `html` |
|
|
150
|
+
| `--bundle` | Analyze bundle size impact per component | `false` |
|
|
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` |
|
|
127
162
|
|
|
128
163
|
### Auto-Fix
|
|
129
164
|
|
|
@@ -252,6 +287,285 @@ react-smell ./src \
|
|
|
252
287
|
--ci
|
|
253
288
|
```
|
|
254
289
|
|
|
290
|
+
### Dependency Graph Visualization
|
|
291
|
+
|
|
292
|
+
Generate interactive dependency graphs showing component and file relationships:
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
# Generate dependency graph
|
|
296
|
+
react-smell ./src --graph
|
|
297
|
+
|
|
298
|
+
# Auto-generates: dependency-graph.html
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
**Features:**
|
|
302
|
+
- Visual representation of file imports and dependencies
|
|
303
|
+
- Circular dependency detection and highlighting
|
|
304
|
+
- Force-directed layout for clarity
|
|
305
|
+
- Exportable SVG or HTML format
|
|
306
|
+
- Legend showing node types and relationships
|
|
307
|
+
|
|
308
|
+
**Example Usage:**
|
|
309
|
+
```bash
|
|
310
|
+
# Generate and analyze all at once
|
|
311
|
+
react-smell ./src --graph --bundle --baseline --ci
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Bundle Size Impact Analysis
|
|
315
|
+
|
|
316
|
+
Estimate per-component bundle size impact:
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
# Analyze bundle impact
|
|
320
|
+
react-smell ./src --bundle
|
|
321
|
+
|
|
322
|
+
# Auto-generates: bundle-analysis.html
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
**Features:**
|
|
326
|
+
- Estimated size per component (in bytes)
|
|
327
|
+
- Line of code (LOC) analysis
|
|
328
|
+
- Dependency complexity scoring
|
|
329
|
+
- Impact level classification (low/medium/high/critical)
|
|
330
|
+
- Recommendations for optimization
|
|
331
|
+
- Breakdown of largest components
|
|
332
|
+
|
|
333
|
+
**Impact Levels:**
|
|
334
|
+
- ๐ข **Low**: <2KB, <150 LOC, low complexity
|
|
335
|
+
- ๐ก **Medium**: 2-5KB, 150-300 LOC, medium complexity
|
|
336
|
+
- ๐ **High**: 5-10KB, 300-500 LOC, high complexity
|
|
337
|
+
- ๐ด **Critical**: >10KB, >500 LOC, very complex
|
|
338
|
+
|
|
339
|
+
### Custom Rules Engine
|
|
340
|
+
|
|
341
|
+
Define project-specific code quality rules:
|
|
342
|
+
|
|
343
|
+
**Configuration File (.smellrc-rules.json):**
|
|
344
|
+
```json
|
|
345
|
+
{
|
|
346
|
+
"rules": [
|
|
347
|
+
{
|
|
348
|
+
"name": "no-hardcoded-strings",
|
|
349
|
+
"description": "Prevent hardcoded strings (use i18n)",
|
|
350
|
+
"severity": "warning",
|
|
351
|
+
"pattern": "\"(hello|world|test)\"",
|
|
352
|
+
"patternType": "regex",
|
|
353
|
+
"enabled": true
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
"name": "require-display-name",
|
|
357
|
+
"description": "All components must have displayName",
|
|
358
|
+
"severity": "info",
|
|
359
|
+
"pattern": "displayName",
|
|
360
|
+
"patternType": "text",
|
|
361
|
+
"enabled": true
|
|
362
|
+
}
|
|
363
|
+
]
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
**CLI Usage:**
|
|
368
|
+
```bash
|
|
369
|
+
# Use custom rules
|
|
370
|
+
react-smell ./src --rules .smellrc-rules.json
|
|
371
|
+
|
|
372
|
+
# Combined with other features
|
|
373
|
+
react-smell ./src --rules .smellrc-rules.json --format json --ci
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
**Rule Properties:**
|
|
377
|
+
|
|
378
|
+
| Property | Type | Description |
|
|
379
|
+
|----------|------|-------------|
|
|
380
|
+
| `name` | string | Unique rule identifier |
|
|
381
|
+
| `description` | string | Human-readable explanation |
|
|
382
|
+
| `severity` | string | error, warning, or info |
|
|
383
|
+
| `pattern` | string | Regex or text pattern |
|
|
384
|
+
| `patternType` | string | regex, text, or ast |
|
|
385
|
+
| `enabled` | boolean | Enable/disable rule |
|
|
386
|
+
|
|
387
|
+
**Pattern Types:**
|
|
388
|
+
|
|
389
|
+
- **regex**: Regular expression matching (e.g., `"hardcoded.*string"`)
|
|
390
|
+
- **text**: Simple string matching
|
|
391
|
+
- **ast**: Babel AST node type matching (e.g., `FunctionExpression`)
|
|
392
|
+
|
|
393
|
+
**Real-World Examples:**
|
|
394
|
+
|
|
395
|
+
```json
|
|
396
|
+
{
|
|
397
|
+
"rules": [
|
|
398
|
+
{
|
|
399
|
+
"name": "no-console-in-production",
|
|
400
|
+
"pattern": "console\\.(log|warn|info)",
|
|
401
|
+
"patternType": "regex",
|
|
402
|
+
"severity": "error"
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
"name": "enforce-prop-types",
|
|
406
|
+
"pattern": "PropTypes",
|
|
407
|
+
"patternType": "text",
|
|
408
|
+
"severity": "warning",
|
|
409
|
+
"message": "Consider using TypeScript instead of PropTypes"
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
"name": "limit-nesting",
|
|
413
|
+
"description": "Flag deeply nested JSX",
|
|
414
|
+
"pattern": "<.*>.*<.*>.*<.*>.*<.*>.*<",
|
|
415
|
+
"patternType": "regex",
|
|
416
|
+
"severity": "info"
|
|
417
|
+
}
|
|
418
|
+
]
|
|
419
|
+
}
|
|
420
|
+
```
|
|
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
|
+
|
|
255
569
|
## Example Output
|
|
256
570
|
|
|
257
571
|
```
|
|
@@ -281,9 +595,23 @@ react-smell ./src \
|
|
|
281
595
|
## Programmatic API
|
|
282
596
|
|
|
283
597
|
```typescript
|
|
284
|
-
import {
|
|
285
|
-
|
|
286
|
-
|
|
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';
|
|
287
615
|
|
|
288
616
|
const result = await analyzeProject({
|
|
289
617
|
rootDir: './src',
|
|
@@ -291,32 +619,29 @@ const result = await analyzeProject({
|
|
|
291
619
|
maxUseEffectsPerComponent: 3,
|
|
292
620
|
maxComponentLines: 300,
|
|
293
621
|
checkUnusedCode: true,
|
|
294
|
-
|
|
622
|
+
checkServerComponents: true, // React 19
|
|
295
623
|
},
|
|
296
624
|
});
|
|
297
625
|
|
|
298
626
|
console.log(`Grade: ${result.debtScore.grade}`);
|
|
299
627
|
console.log(`Total issues: ${result.summary.totalSmells}`);
|
|
300
628
|
|
|
301
|
-
//
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
console.log(
|
|
306
|
-
|
|
307
|
-
// Send notification
|
|
308
|
-
const webhookConfig = getWebhookConfig(
|
|
309
|
-
process.env.REACT_SMELL_SLACK_WEBHOOK,
|
|
310
|
-
process.env.REACT_SMELL_DISCORD_WEBHOOK
|
|
311
|
-
);
|
|
312
|
-
if (webhookConfig) {
|
|
313
|
-
await sendWebhookNotification(
|
|
314
|
-
webhookConfig,
|
|
315
|
-
result.files.flatMap(f => f.smells),
|
|
316
|
-
'my-project'
|
|
317
|
-
);
|
|
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));
|
|
318
634
|
}
|
|
319
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
|
+
|
|
320
645
|
// Or use the reporter
|
|
321
646
|
const report = reportResults(result, {
|
|
322
647
|
format: 'markdown',
|
|
@@ -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 @@
|
|
|
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 @@
|
|
|
1
|
+
{"version":3,"file":"prComments.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/prComments.test.ts"],"names":[],"mappings":""}
|