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/dist/guide.js ADDED
@@ -0,0 +1,922 @@
1
+ import chalk from 'chalk';
2
+ import fs from 'fs/promises';
3
+ import path from 'path';
4
+ import readline from 'readline';
5
+ /**
6
+ * Interactive guide for React Code Smell Detector
7
+ */
8
+ export async function runGuide() {
9
+ const rl = readline.createInterface({
10
+ input: process.stdin,
11
+ output: process.stdout,
12
+ });
13
+ const ask = (question) => {
14
+ return new Promise((resolve) => {
15
+ rl.question(question, (answer) => resolve(answer.trim()));
16
+ });
17
+ };
18
+ console.clear();
19
+ printHeader();
20
+ let running = true;
21
+ while (running) {
22
+ console.log('\n' + chalk.cyan('What would you like to learn about?') + '\n');
23
+ console.log(chalk.white(' 1. ') + 'Quick Start - Basic usage');
24
+ console.log(chalk.white(' 2. ') + 'Output Formats - JSON, Markdown, HTML');
25
+ console.log(chalk.white(' 3. ') + 'Auto-Fix - Fix simple issues automatically');
26
+ console.log(chalk.white(' 4. ') + 'Watch Mode - Real-time analysis');
27
+ console.log(chalk.white(' 5. ') + 'Git Integration - Analyze only changed files');
28
+ console.log(chalk.white(' 6. ') + 'CI/CD Integration - Pipelines & PR comments');
29
+ console.log(chalk.white(' 7. ') + 'Performance Budget - Enforce quality thresholds');
30
+ console.log(chalk.white(' 8. ') + 'Custom Rules - Define your own rules');
31
+ console.log(chalk.white(' 9. ') + 'Advanced Features - AI, Graphs, Bundle Analysis');
32
+ console.log(chalk.white(' 10. ') + 'Configuration - All config options');
33
+ console.log(chalk.white(' 11. ') + 'Create Demo Project');
34
+ console.log(chalk.white(' 0. ') + 'Exit\n');
35
+ const choice = await ask(chalk.yellow('Enter your choice (0-11): '));
36
+ switch (choice) {
37
+ case '1':
38
+ showQuickStart();
39
+ break;
40
+ case '2':
41
+ showOutputFormats();
42
+ break;
43
+ case '3':
44
+ showAutoFix();
45
+ break;
46
+ case '4':
47
+ showWatchMode();
48
+ break;
49
+ case '5':
50
+ showGitIntegration();
51
+ break;
52
+ case '6':
53
+ showCICD();
54
+ break;
55
+ case '7':
56
+ showPerformanceBudget();
57
+ break;
58
+ case '8':
59
+ showCustomRules();
60
+ break;
61
+ case '9':
62
+ showAdvancedFeatures();
63
+ break;
64
+ case '10':
65
+ showConfiguration();
66
+ break;
67
+ case '11':
68
+ rl.close();
69
+ await createDemoProject(process.cwd());
70
+ return;
71
+ case '0':
72
+ running = false;
73
+ break;
74
+ default:
75
+ console.log(chalk.red('\nInvalid choice. Please enter a number from 0-11.'));
76
+ }
77
+ if (running && choice !== '0') {
78
+ await ask(chalk.dim('\nPress Enter to continue...'));
79
+ }
80
+ }
81
+ console.log(chalk.green('\nHappy coding! 🚀\n'));
82
+ rl.close();
83
+ }
84
+ function printHeader() {
85
+ console.log(chalk.cyan.bold(`
86
+ ╔═══════════════════════════════════════════════════════════════╗
87
+ ║ 🔍 React Code Smell Detector Guide 🔍 ║
88
+ ║ Interactive Tutorial ║
89
+ ╚═══════════════════════════════════════════════════════════════╝
90
+ `));
91
+ console.log(chalk.white('Welcome! This guide will help you learn all features of'));
92
+ console.log(chalk.white('React Code Smell Detector with practical examples.\n'));
93
+ }
94
+ function showQuickStart() {
95
+ console.log(chalk.cyan.bold('\n═══ Quick Start ═══\n'));
96
+ console.log(chalk.white('1. Analyze current directory:'));
97
+ console.log(chalk.green(' $ react-smell .'));
98
+ console.log(chalk.white('\n2. Analyze a specific directory:'));
99
+ console.log(chalk.green(' $ react-smell ./src'));
100
+ console.log(chalk.white('\n3. Show code snippets with issues:'));
101
+ console.log(chalk.green(' $ react-smell ./src -s'));
102
+ console.log(chalk.white('\n4. Use a configuration file:'));
103
+ console.log(chalk.green(' $ react-smell ./src -c .smellrc.json'));
104
+ console.log(chalk.white('\n5. Create default configuration:'));
105
+ console.log(chalk.green(' $ react-smell init'));
106
+ printBox('Example Output', `
107
+ 📊 Analysis Summary
108
+ ├── Files analyzed: 24
109
+ ├── Components found: 45
110
+ ├── Code smells: 12
111
+ └── Technical Debt Grade: B (82/100)
112
+
113
+ 🔴 Error (2)
114
+ ├── security-xss: Avoid dangerouslySetInnerHTML
115
+ └── missing-key: Add key prop to list items
116
+
117
+ 🟡 Warning (7)
118
+ ├── useEffect-overuse: Too many effects
119
+ └── prop-drilling: Props passed through 4 levels
120
+ `);
121
+ }
122
+ function showOutputFormats() {
123
+ console.log(chalk.cyan.bold('\n═══ Output Formats ═══\n'));
124
+ console.log(chalk.white('1. Console (default) - Colored terminal output:'));
125
+ console.log(chalk.green(' $ react-smell ./src'));
126
+ console.log(chalk.white('\n2. JSON - Machine-readable for CI/CD:'));
127
+ console.log(chalk.green(' $ react-smell ./src -f json'));
128
+ console.log(chalk.green(' $ react-smell ./src -f json -o report.json'));
129
+ console.log(chalk.white('\n3. Markdown - For documentation:'));
130
+ console.log(chalk.green(' $ react-smell ./src -f markdown -o report.md'));
131
+ console.log(chalk.white('\n4. HTML - Beautiful visual report:'));
132
+ console.log(chalk.green(' $ react-smell ./src -f html -o report.html'));
133
+ printBox('JSON Output Example', `
134
+ {
135
+ "summary": {
136
+ "totalFiles": 24,
137
+ "totalComponents": 45,
138
+ "totalSmells": 12
139
+ },
140
+ "debtScore": {
141
+ "score": 82,
142
+ "grade": "B"
143
+ },
144
+ "files": [...]
145
+ }
146
+ `);
147
+ }
148
+ function showAutoFix() {
149
+ console.log(chalk.cyan.bold('\n═══ Auto-Fix Features ═══\n'));
150
+ console.log(chalk.white('Fixable issues: console.log, var→let, ==→===, missing alt\n'));
151
+ console.log(chalk.white('1. Auto-fix all fixable issues:'));
152
+ console.log(chalk.green(' $ react-smell ./src --fix'));
153
+ console.log(chalk.white('\n2. Interactive fix mode (review each fix):'));
154
+ console.log(chalk.green(' $ react-smell ./src --fix-interactive'));
155
+ console.log(chalk.white('\n3. Preview fixes without applying:'));
156
+ console.log(chalk.green(' $ react-smell ./src --fix-preview'));
157
+ printBox('Interactive Fix Example', `
158
+ 📝 Fix 1/5: debug-statement in Header.tsx:15
159
+
160
+ Before: console.log('User logged in');
161
+ After: // removed
162
+
163
+ [Y] Apply [N] Skip [A] Apply All [Q] Quit
164
+ `);
165
+ }
166
+ function showWatchMode() {
167
+ console.log(chalk.cyan.bold('\n═══ Watch Mode ═══\n'));
168
+ console.log(chalk.white('Re-analyze automatically when files change:\n'));
169
+ console.log(chalk.green(' $ react-smell ./src --watch'));
170
+ console.log(chalk.white('\nWith specific options:'));
171
+ console.log(chalk.green(' $ react-smell ./src --watch -s'));
172
+ console.log(chalk.green(' $ react-smell ./src --watch --max-effects 2'));
173
+ printBox('Watch Mode Output', `
174
+ 👀 Watching for changes in ./src...
175
+
176
+ [14:32:01] File changed: src/components/Header.tsx
177
+ [14:32:01] Analyzing...
178
+ [14:32:02] ✓ 3 smells found (1 error, 2 warnings)
179
+
180
+ Press Ctrl+C to stop watching.
181
+ `);
182
+ }
183
+ function showGitIntegration() {
184
+ console.log(chalk.cyan.bold('\n═══ Git Integration ═══\n'));
185
+ console.log(chalk.white('1. Analyze only git-modified files:'));
186
+ console.log(chalk.green(' $ react-smell --changed'));
187
+ console.log(chalk.white('\n2. Great for pre-commit hooks:'));
188
+ console.log(chalk.green(' # .husky/pre-commit'));
189
+ console.log(chalk.green(' react-smell --changed --ci --fail-on error'));
190
+ console.log(chalk.white('\n3. Baseline tracking (track trends over time):'));
191
+ console.log(chalk.green(' $ react-smell ./src --baseline'));
192
+ printBox('Trend Report', `
193
+ 📈 Code Quality Trend (last 5 commits)
194
+
195
+ Commit Date Smells Grade
196
+ ─────────────────────────────────────
197
+ a1b2c3d Feb 20 45 C
198
+ e4f5g6h Feb 19 42 C
199
+ i7j8k9l Feb 18 38 B
200
+ m0n1o2p Feb 17 35 B
201
+ q3r4s5t Feb 16 32 B
202
+
203
+ Trend: ⬆️ +13 smells (+40%) since Feb 16
204
+ `);
205
+ }
206
+ function showCICD() {
207
+ console.log(chalk.cyan.bold('\n═══ CI/CD Integration ═══\n'));
208
+ console.log(chalk.white('1. Exit with error code when issues found:'));
209
+ console.log(chalk.green(' $ react-smell ./src --ci'));
210
+ console.log(chalk.white('\n2. Custom severity threshold:'));
211
+ console.log(chalk.green(' $ react-smell ./src --ci --fail-on warning'));
212
+ console.log(chalk.white('\n3. GitHub Actions PR Comments:'));
213
+ console.log(chalk.green(' $ react-smell ./src --pr-comment'));
214
+ console.log(chalk.white('\n4. Slack/Discord notifications:'));
215
+ console.log(chalk.green(' $ react-smell ./src --slack $SLACK_WEBHOOK_URL'));
216
+ console.log(chalk.green(' $ react-smell ./src --discord $DISCORD_WEBHOOK_URL'));
217
+ printBox('GitHub Actions Workflow', `
218
+ name: Code Quality
219
+ on: [pull_request]
220
+ jobs:
221
+ analyze:
222
+ runs-on: ubuntu-latest
223
+ steps:
224
+ - uses: actions/checkout@v4
225
+ - uses: actions/setup-node@v4
226
+ - run: npm install -g react-code-smell-detector
227
+ - run: react-smell ./src --ci --pr-comment
228
+ env:
229
+ GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}
230
+ `);
231
+ }
232
+ function showPerformanceBudget() {
233
+ console.log(chalk.cyan.bold('\n═══ Performance Budget ═══\n'));
234
+ console.log(chalk.white('Set quality thresholds and enforce in CI/CD:\n'));
235
+ console.log(chalk.white('1. Create budget configuration:'));
236
+ console.log(chalk.green(' $ react-smell init-budget'));
237
+ console.log(chalk.white('\n2. Check against budget:'));
238
+ console.log(chalk.green(' $ react-smell ./src --budget'));
239
+ console.log(chalk.white('\n3. Custom budget config:'));
240
+ console.log(chalk.green(' $ react-smell ./src --budget --budget-config budget.json'));
241
+ printBox('.smellbudget.json Example', `
242
+ {
243
+ "maxTotalSmells": 50,
244
+ "maxErrors": 0,
245
+ "maxWarnings": 30,
246
+ "minGrade": "B",
247
+ "maxSmellsPerFile": 10,
248
+ "maxByType": {
249
+ "security-xss": 0,
250
+ "security-eval": 0,
251
+ "debug-statement": 0
252
+ }
253
+ }
254
+ `);
255
+ }
256
+ function showCustomRules() {
257
+ console.log(chalk.cyan.bold('\n═══ Custom Rules ═══\n'));
258
+ console.log(chalk.white('Define project-specific code quality rules:\n'));
259
+ console.log(chalk.white('1. Create rules file:'));
260
+ console.log(chalk.green(' $ touch .smellrules.json'));
261
+ console.log(chalk.white('\n2. Use custom rules:'));
262
+ console.log(chalk.green(' $ react-smell ./src --rules .smellrules.json'));
263
+ printBox('.smellrules.json Example', `
264
+ {
265
+ "rules": [
266
+ {
267
+ "id": "no-deprecated-api",
268
+ "name": "No Deprecated API",
269
+ "description": "Avoid using deprecated React APIs",
270
+ "severity": "warning",
271
+ "pattern": {
272
+ "type": "text",
273
+ "value": "componentWillMount"
274
+ }
275
+ },
276
+ {
277
+ "id": "no-inline-styles",
278
+ "name": "No Inline Styles",
279
+ "description": "Use CSS modules or styled-components",
280
+ "severity": "info",
281
+ "pattern": {
282
+ "type": "regex",
283
+ "value": "style=\\\\{\\\\{"
284
+ }
285
+ }
286
+ ]
287
+ }
288
+ `);
289
+ }
290
+ function showAdvancedFeatures() {
291
+ console.log(chalk.cyan.bold('\n═══ Advanced Features ═══\n'));
292
+ console.log(chalk.white('1. Dependency Graph Visualization:'));
293
+ console.log(chalk.green(' $ react-smell ./src --graph'));
294
+ console.log(chalk.green(' $ react-smell ./src --graph --graph-format svg -o deps.svg'));
295
+ console.log(chalk.white('\n2. Bundle Size Impact Analysis:'));
296
+ console.log(chalk.green(' $ react-smell ./src --bundle'));
297
+ console.log(chalk.white('\n3. Component Documentation Generation:'));
298
+ console.log(chalk.green(' $ react-smell docs ./src'));
299
+ console.log(chalk.green(' $ react-smell docs ./src -f html -o ./docs'));
300
+ console.log(chalk.white('\n4. AI-Powered Refactoring (requires API key):'));
301
+ console.log(chalk.green(' $ react-smell ./src --ai --ai-key $OPENAI_API_KEY'));
302
+ console.log(chalk.green(' $ react-smell ./src --ai --ai-model gpt-4'));
303
+ printBox('Bundle Analysis Output', `
304
+ 📦 Bundle Size Impact by Component
305
+
306
+ Component Size Dependencies
307
+ ────────────────────────────────────────────
308
+ Dashboard 45.2 KB lodash, recharts
309
+ UserProfile 12.8 KB formik, yup
310
+ Header 3.2 KB react-icons
311
+ Footer 1.5 KB -
312
+
313
+ ⚠️ Suggestions:
314
+ - Dashboard: Consider code-splitting recharts
315
+ - UserProfile: formik can be tree-shaken
316
+ `);
317
+ }
318
+ function showConfiguration() {
319
+ console.log(chalk.cyan.bold('\n═══ Configuration Options ═══\n'));
320
+ console.log(chalk.white('All CLI Options:'));
321
+ console.log(chalk.dim('─'.repeat(60)));
322
+ const options = [
323
+ ['-f, --format <fmt>', 'Output: console, json, markdown, html'],
324
+ ['-s, --snippets', 'Show code snippets'],
325
+ ['-c, --config <file>', 'Config file path'],
326
+ ['--ci', 'CI mode (exit code 1 on issues)'],
327
+ ['--fail-on <severity>', 'Exit threshold: error|warning|info'],
328
+ ['--fix', 'Auto-fix simple issues'],
329
+ ['--fix-interactive', 'Interactive fix mode'],
330
+ ['--fix-preview', 'Preview fixes'],
331
+ ['--watch', 'Watch mode'],
332
+ ['--changed', 'Git changed files only'],
333
+ ['--max-effects <n>', 'Max useEffects per component'],
334
+ ['--max-props <n>', 'Max props before warning'],
335
+ ['--max-lines <n>', 'Max lines per component'],
336
+ ['--include <patterns>', 'Glob patterns to include'],
337
+ ['--exclude <patterns>', 'Glob patterns to exclude'],
338
+ ['-o, --output <file>', 'Write to file'],
339
+ ['--baseline', 'Enable trend tracking'],
340
+ ['--slack <url>', 'Slack webhook'],
341
+ ['--discord <url>', 'Discord webhook'],
342
+ ['--graph', 'Generate dependency graph'],
343
+ ['--bundle', 'Bundle size analysis'],
344
+ ['--budget', 'Check performance budget'],
345
+ ['--docs', 'Generate documentation'],
346
+ ['--ai', 'AI refactoring suggestions'],
347
+ ];
348
+ for (const [flag, desc] of options) {
349
+ console.log(`${chalk.yellow(flag.padEnd(25))} ${chalk.white(desc)}`);
350
+ }
351
+ printBox('.smellrc.json Full Example', `
352
+ {
353
+ "maxUseEffectsPerComponent": 3,
354
+ "maxPropDrillingDepth": 3,
355
+ "maxComponentLines": 300,
356
+ "maxPropsCount": 7,
357
+ "checkMemoization": true,
358
+ "checkDebugStatements": true,
359
+ "checkSecurity": true,
360
+ "checkAccessibility": true,
361
+ "checkComplexity": true,
362
+ "maxCyclomaticComplexity": 10,
363
+ "checkMemoryLeaks": true,
364
+ "checkImports": true,
365
+ "checkContextApi": true,
366
+ "checkErrorBoundaries": true,
367
+ "checkForms": true,
368
+ "checkStateManagement": true,
369
+ "checkTestingGaps": true
370
+ }
371
+ `);
372
+ }
373
+ function printBox(title, content) {
374
+ console.log('\n' + chalk.cyan('┌─ ' + title + ' ' + '─'.repeat(Math.max(0, 50 - title.length)) + '┐'));
375
+ console.log(chalk.gray(content));
376
+ console.log(chalk.cyan('└' + '─'.repeat(55) + '┘'));
377
+ }
378
+ /**
379
+ * Create a demo project with examples of all detectable smells
380
+ */
381
+ export async function createDemoProject(targetDir) {
382
+ const demoDir = path.join(targetDir, 'react-smell-demo');
383
+ console.log(chalk.cyan.bold('\n🚀 Creating Demo Project\n'));
384
+ try {
385
+ await fs.mkdir(demoDir, { recursive: true });
386
+ await fs.mkdir(path.join(demoDir, 'src', 'components'), { recursive: true });
387
+ await fs.mkdir(path.join(demoDir, 'src', 'hooks'), { recursive: true });
388
+ // Create package.json
389
+ await fs.writeFile(path.join(demoDir, 'package.json'), JSON.stringify({
390
+ name: 'react-smell-demo',
391
+ version: '1.0.0',
392
+ description: 'Demo project for React Code Smell Detector',
393
+ scripts: {
394
+ 'analyze': 'react-smell ./src',
395
+ 'analyze:fix': 'react-smell ./src --fix',
396
+ 'analyze:html': 'react-smell ./src -f html -o report.html',
397
+ 'analyze:watch': 'react-smell ./src --watch',
398
+ 'analyze:ci': 'react-smell ./src --ci --budget',
399
+ },
400
+ }, null, 2));
401
+ // Create .smellrc.json
402
+ await fs.writeFile(path.join(demoDir, '.smellrc.json'), JSON.stringify({
403
+ maxUseEffectsPerComponent: 3,
404
+ maxPropDrillingDepth: 3,
405
+ maxComponentLines: 300,
406
+ checkDebugStatements: true,
407
+ checkSecurity: true,
408
+ checkAccessibility: true,
409
+ }, null, 2));
410
+ // Create .smellbudget.json
411
+ await fs.writeFile(path.join(demoDir, '.smellbudget.json'), JSON.stringify({
412
+ maxTotalSmells: 50,
413
+ maxErrors: 0,
414
+ minGrade: 'C',
415
+ maxByType: {
416
+ 'security-xss': 0,
417
+ 'security-eval': 0,
418
+ },
419
+ }, null, 2));
420
+ // Create demo components with various smells
421
+ await createDemoComponents(demoDir);
422
+ console.log(chalk.green('✓ Created demo project at: ' + demoDir));
423
+ console.log('\n' + chalk.cyan('📂 Project Structure:'));
424
+ console.log(`
425
+ ${demoDir}/
426
+ ├── package.json
427
+ ├── .smellrc.json
428
+ ├── .smellbudget.json
429
+ └── src/
430
+ ├── components/
431
+ │ ├── GoodComponent.tsx (Clean code example)
432
+ │ ├── BadComponent.tsx (Multiple smells)
433
+ │ ├── SecurityIssues.tsx (Security vulnerabilities)
434
+ │ ├── AccessibilityBad.tsx (A11y issues)
435
+ │ ├── ProDrillingExample.tsx (Prop drilling)
436
+ │ ├── ContextExample.tsx (Context API issues)
437
+ │ ├── FormIssues.tsx (Form validation smells)
438
+ │ └── StateManagement.tsx (State management issues)
439
+ └── hooks/
440
+ └── useComplexHook.ts (Complex hook example)
441
+ `);
442
+ console.log(chalk.cyan('\n🏃 Try these commands:\n'));
443
+ console.log(chalk.green(' cd react-smell-demo'));
444
+ console.log(chalk.green(' react-smell ./src'));
445
+ console.log(chalk.green(' react-smell ./src -s'));
446
+ console.log(chalk.green(' react-smell ./src --fix'));
447
+ console.log(chalk.green(' react-smell ./src -f html -o report.html'));
448
+ console.log(chalk.green(' react-smell ./src --budget'));
449
+ }
450
+ catch (error) {
451
+ console.error(chalk.red(`Error creating demo project: ${error.message}`));
452
+ throw error;
453
+ }
454
+ }
455
+ async function createDemoComponents(demoDir) {
456
+ const componentsDir = path.join(demoDir, 'src', 'components');
457
+ const hooksDir = path.join(demoDir, 'src', 'hooks');
458
+ // Good component (clean code)
459
+ await fs.writeFile(path.join(componentsDir, 'GoodComponent.tsx'), `
460
+ import React, { useState, useCallback, useMemo } from 'react';
461
+
462
+ interface Props {
463
+ title: string;
464
+ items: string[];
465
+ onItemClick: (item: string) => void;
466
+ }
467
+
468
+ /**
469
+ * A well-structured React component following best practices.
470
+ * This component demonstrates clean code patterns.
471
+ */
472
+ export function GoodComponent({ title, items, onItemClick }: Props) {
473
+ const [filter, setFilter] = useState('');
474
+
475
+ // Memoized filtered items
476
+ const filteredItems = useMemo(() =>
477
+ items.filter(item => item.toLowerCase().includes(filter.toLowerCase())),
478
+ [items, filter]
479
+ );
480
+
481
+ // Memoized callback
482
+ const handleClick = useCallback((item: string) => {
483
+ onItemClick(item);
484
+ }, [onItemClick]);
485
+
486
+ return (
487
+ <div className="good-component">
488
+ <h2>{title}</h2>
489
+ <input
490
+ type="text"
491
+ value={filter}
492
+ onChange={(e) => setFilter(e.target.value)}
493
+ placeholder="Filter items..."
494
+ aria-label="Filter items"
495
+ />
496
+ <ul>
497
+ {filteredItems.map((item) => (
498
+ <li key={item}>
499
+ <button onClick={() => handleClick(item)} type="button">
500
+ {item}
501
+ </button>
502
+ </li>
503
+ ))}
504
+ </ul>
505
+ </div>
506
+ );
507
+ }
508
+ `);
509
+ // Bad component with multiple smells
510
+ await fs.writeFile(path.join(componentsDir, 'BadComponent.tsx'), `
511
+ import React, { useState, useEffect } from 'react';
512
+
513
+ // 🚨 This component demonstrates multiple code smells
514
+
515
+ export function BadComponent(props) {
516
+ const [data, setData] = useState(null);
517
+ const [loading, setLoading] = useState(false);
518
+ const [error, setError] = useState(null);
519
+ const [count, setCount] = useState(0);
520
+
521
+ // ⚠️ Smell: Multiple useEffects (useEffect-overuse)
522
+ useEffect(() => {
523
+ console.log('Component mounted'); // ⚠️ Smell: debug-statement
524
+ setLoading(true);
525
+ fetch('/api/data')
526
+ .then(r => r.json())
527
+ .then(setData)
528
+ .finally(() => setLoading(false));
529
+ }, []);
530
+
531
+ useEffect(() => {
532
+ console.log('Data changed:', data); // ⚠️ Smell: debug-statement
533
+ }, [data]);
534
+
535
+ useEffect(() => {
536
+ document.title = 'Count: ' + count;
537
+ }, [count]);
538
+
539
+ useEffect(() => {
540
+ // ⚠️ Smell: memory-leak-timer (no cleanup)
541
+ setInterval(() => {
542
+ setCount(c => c + 1);
543
+ }, 1000);
544
+ }, []);
545
+
546
+ // ⚠️ Smell: js-var-usage
547
+ var items = data?.items || [];
548
+
549
+ // ⚠️ Smell: js-loose-equality
550
+ if (error == null && loading == false) {
551
+ return (
552
+ <div>
553
+ <h1>Data loaded</h1>
554
+ {/* ⚠️ Smell: missing-key */}
555
+ {items.map(item => (
556
+ <div>{item.name}</div>
557
+ ))}
558
+ {/* ⚠️ Smell: inline-function-prop */}
559
+ <button onClick={() => setCount(count + 1)}>
560
+ Count: {count}
561
+ </button>
562
+ </div>
563
+ );
564
+ }
565
+
566
+ // ⚠️ Smell: nested-ternary
567
+ return loading ? <div>Loading...</div> : error ? <div>Error!</div> : <div>Unknown state</div>;
568
+ }
569
+ `);
570
+ // Security issues component
571
+ await fs.writeFile(path.join(componentsDir, 'SecurityIssues.tsx'), `
572
+ import React from 'react';
573
+
574
+ // 🚨 This component demonstrates security vulnerabilities
575
+
576
+ export function SecurityIssues({ userInput, htmlContent }) {
577
+ // ⚠️ Smell: security-eval
578
+ const executeCode = (code: string) => {
579
+ return eval(code); // DANGEROUS!
580
+ };
581
+
582
+ // ⚠️ Smell: security-secrets (exposed API key pattern)
583
+ const API_KEY = 'sk_live_abc123def456';
584
+
585
+ return (
586
+ <div>
587
+ {/* ⚠️ Smell: security-xss */}
588
+ <div dangerouslySetInnerHTML={{ __html: htmlContent }} />
589
+
590
+ <button onClick={() => executeCode(userInput)}>
591
+ Execute
592
+ </button>
593
+ </div>
594
+ );
595
+ }
596
+ `);
597
+ // Accessibility issues component
598
+ await fs.writeFile(path.join(componentsDir, 'AccessibilityBad.tsx'), `
599
+ import React from 'react';
600
+
601
+ // 🚨 This component demonstrates accessibility issues
602
+
603
+ export function AccessibilityBad() {
604
+ return (
605
+ <div>
606
+ {/* ⚠️ Smell: a11y-missing-alt */}
607
+ <img src="/logo.png" />
608
+
609
+ {/* ⚠️ Smell: a11y-missing-label */}
610
+ <input type="email" placeholder="Enter email" />
611
+
612
+ {/* ⚠️ Smell: a11y-keyboard (click without keyboard) */}
613
+ <div onClick={() => alert('clicked')}>
614
+ Click me
615
+ </div>
616
+
617
+ {/* ⚠️ Smell: a11y-semantic */}
618
+ <div className="button" onClick={() => {}}>
619
+ This should be a button
620
+ </div>
621
+ </div>
622
+ );
623
+ }
624
+ `);
625
+ // Prop drilling example
626
+ await fs.writeFile(path.join(componentsDir, 'PropDrillingExample.tsx'), `
627
+ import React from 'react';
628
+
629
+ // 🚨 This demonstrates prop drilling (passing props through many levels)
630
+
631
+ interface User {
632
+ name: string;
633
+ email: string;
634
+ avatar: string;
635
+ }
636
+
637
+ // ⚠️ Smell: prop-drilling
638
+ export function PropDrillingExample({
639
+ user,
640
+ onUpdate,
641
+ onDelete,
642
+ onLogout,
643
+ theme,
644
+ language,
645
+ notifications,
646
+ settings
647
+ }: {
648
+ user: User;
649
+ onUpdate: () => void;
650
+ onDelete: () => void;
651
+ onLogout: () => void;
652
+ theme: string;
653
+ language: string;
654
+ notifications: boolean;
655
+ settings: object;
656
+ }) {
657
+ return (
658
+ <div>
659
+ <Level1
660
+ user={user}
661
+ onUpdate={onUpdate}
662
+ onDelete={onDelete}
663
+ onLogout={onLogout}
664
+ theme={theme}
665
+ language={language}
666
+ />
667
+ </div>
668
+ );
669
+ }
670
+
671
+ function Level1({ user, onUpdate, onDelete, onLogout, theme, language }) {
672
+ return (
673
+ <Level2
674
+ user={user}
675
+ onUpdate={onUpdate}
676
+ onDelete={onDelete}
677
+ onLogout={onLogout}
678
+ theme={theme}
679
+ />
680
+ );
681
+ }
682
+
683
+ function Level2({ user, onUpdate, onDelete, onLogout, theme }) {
684
+ return (
685
+ <Level3 user={user} onUpdate={onUpdate} onDelete={onDelete} />
686
+ );
687
+ }
688
+
689
+ function Level3({ user, onUpdate, onDelete }) {
690
+ return (
691
+ <div>
692
+ <h3>{user.name}</h3>
693
+ <button onClick={onUpdate}>Update</button>
694
+ <button onClick={onDelete}>Delete</button>
695
+ </div>
696
+ );
697
+ }
698
+ `);
699
+ // Context API issues
700
+ await fs.writeFile(path.join(componentsDir, 'ContextExample.tsx'), `
701
+ import React, { useContext, createContext } from 'react';
702
+
703
+ // 🚨 This demonstrates Context API issues
704
+
705
+ const ThemeContext = createContext({});
706
+ const UserContext = createContext({});
707
+ const AuthContext = createContext({});
708
+ const SettingsContext = createContext({});
709
+ const LanguageContext = createContext({});
710
+ const CartContext = createContext({});
711
+
712
+ // ⚠️ Smell: context-overuse (too many contexts)
713
+ export function ContextHeavyComponent() {
714
+ const theme = useContext(ThemeContext);
715
+ const user = useContext(UserContext);
716
+ const auth = useContext(AuthContext);
717
+ const settings = useContext(SettingsContext);
718
+ const language = useContext(LanguageContext);
719
+ const cart = useContext(CartContext);
720
+
721
+ return (
722
+ <div style={{ color: theme.color }}>
723
+ <h1>Welcome, {user.name}</h1>
724
+ <p>Language: {language.current}</p>
725
+ <p>Cart items: {cart.items.length}</p>
726
+ </div>
727
+ );
728
+ }
729
+
730
+ // ⚠️ Smell: large-context-value (inline object in provider)
731
+ export function ContextProviderBad({ children }) {
732
+ return (
733
+ <ThemeContext.Provider value={{ color: 'blue', fontSize: 14, fontFamily: 'Arial' }}>
734
+ {children}
735
+ </ThemeContext.Provider>
736
+ );
737
+ }
738
+ `);
739
+ // Form issues
740
+ await fs.writeFile(path.join(componentsDir, 'FormIssues.tsx'), `
741
+ import React, { useState } from 'react';
742
+
743
+ // 🚨 This demonstrates form validation issues
744
+
745
+ export function FormIssues() {
746
+ const [email, setEmail] = useState('');
747
+
748
+ return (
749
+ <div>
750
+ {/* ⚠️ Smell: form-without-onsubmit */}
751
+ <form>
752
+ {/* ⚠️ Smell: uncontrolled-form (value without onChange) */}
753
+ <input type="text" value="fixed value" />
754
+
755
+ {/* ⚠️ Smell: input-without-label */}
756
+ <input
757
+ type="email"
758
+ value={email}
759
+ onChange={e => setEmail(e.target.value)}
760
+ />
761
+
762
+ <button type="submit">Submit</button>
763
+ </form>
764
+
765
+ {/* ⚠️ Smell: missing-form-validation (no validation library) */}
766
+ <form onSubmit={(e) => {
767
+ e.preventDefault();
768
+ // No validation!
769
+ fetch('/api/submit', { method: 'POST', body: JSON.stringify({ email }) });
770
+ }}>
771
+ <input type="text" defaultValue="" />
772
+ <button type="submit">Send</button>
773
+ </form>
774
+ </div>
775
+ );
776
+ }
777
+ `);
778
+ // State management issues
779
+ await fs.writeFile(path.join(componentsDir, 'StateManagement.tsx'), `
780
+ import React, { useState, useEffect } from 'react';
781
+
782
+ // Simulating Redux hooks (these would come from react-redux)
783
+ const useSelector = (selector: any) => selector({ value: 1 });
784
+ const useDispatch = () => (action: any) => action;
785
+
786
+ // 🚨 This demonstrates state management anti-patterns
787
+
788
+ export function StateManagementBad() {
789
+ // ⚠️ Smell: excessive-redux-selectors (too many selectors)
790
+ const a = useSelector(state => state.a);
791
+ const b = useSelector(state => state.b);
792
+ const c = useSelector(state => state.c);
793
+ const d = useSelector(state => state.d);
794
+
795
+ const dispatch = useDispatch();
796
+
797
+ // ⚠️ Smell: derived-state-in-state
798
+ const [derivedValue, setDerivedValue] = useState(a + b);
799
+
800
+ // ⚠️ Smell: state-sync-anti-pattern
801
+ useEffect(() => {
802
+ setDerivedValue(a + b);
803
+ }, [a, b]);
804
+
805
+ return (
806
+ <div>
807
+ <p>Values: {a}, {b}, {c}, {d}</p>
808
+ <p>Derived: {derivedValue}</p>
809
+ {/* ⚠️ Smell: redux-in-render (inline dispatch) */}
810
+ <button onClick={() => dispatch({ type: 'INCREMENT' })}>
811
+ Increment
812
+ </button>
813
+ </div>
814
+ );
815
+ }
816
+ `);
817
+ // Complex hook with testing gaps
818
+ await fs.writeFile(path.join(hooksDir, 'useComplexHook.ts'), `
819
+ import { useState, useEffect, useCallback } from 'react';
820
+
821
+ // 🚨 This hook demonstrates testing gap issues
822
+
823
+ interface UseComplexHookReturn {
824
+ data: any;
825
+ loading: boolean;
826
+ error: Error | null;
827
+ refetch: () => void;
828
+ }
829
+
830
+ // ⚠️ Smell: side-effect-heavy, tightly-coupled, complex-untestable
831
+ export function useComplexHook(url: string): UseComplexHookReturn {
832
+ const [data, setData] = useState(null);
833
+ const [loading, setLoading] = useState(false);
834
+ const [error, setError] = useState<Error | null>(null);
835
+
836
+ // Multiple side effects
837
+ useEffect(() => {
838
+ const controller = new AbortController();
839
+
840
+ setLoading(true);
841
+ fetch(url, { signal: controller.signal })
842
+ .then(r => r.json())
843
+ .then(setData)
844
+ .catch(setError)
845
+ .finally(() => setLoading(false));
846
+
847
+ // At least this has cleanup!
848
+ return () => controller.abort();
849
+ }, [url]);
850
+
851
+ useEffect(() => {
852
+ // Side effect: localStorage
853
+ if (data) {
854
+ localStorage.setItem('cachedData', JSON.stringify(data));
855
+ }
856
+ }, [data]);
857
+
858
+ useEffect(() => {
859
+ // Side effect: analytics
860
+ console.log('Analytics:', { url, hasData: !!data }); // ⚠️ debug-statement
861
+ }, [url, data]);
862
+
863
+ const refetch = useCallback(() => {
864
+ setLoading(true);
865
+ fetch(url)
866
+ .then(r => r.json())
867
+ .then(setData)
868
+ .catch(setError)
869
+ .finally(() => setLoading(false));
870
+ }, [url]);
871
+
872
+ return { data, loading, error, refetch };
873
+ }
874
+ `);
875
+ // Create a README for the demo
876
+ await fs.writeFile(path.join(demoDir, 'README.md'), `# React Code Smell Detector Demo
877
+
878
+ This demo project contains examples of various code smells that can be detected.
879
+
880
+ ## Quick Start
881
+
882
+ \`\`\`bash
883
+ # Analyze the project
884
+ react-smell ./src
885
+
886
+ # With code snippets
887
+ react-smell ./src -s
888
+
889
+ # Auto-fix simple issues
890
+ react-smell ./src --fix
891
+
892
+ # Generate HTML report
893
+ react-smell ./src -f html -o report.html
894
+
895
+ # Check against budget
896
+ react-smell ./src --budget
897
+ \`\`\`
898
+
899
+ ## Demo Files
900
+
901
+ | File | Demonstrates |
902
+ |------|--------------|
903
+ | GoodComponent.tsx | Clean code (no smells) |
904
+ | BadComponent.tsx | Multiple common smells |
905
+ | SecurityIssues.tsx | Security vulnerabilities |
906
+ | AccessibilityBad.tsx | A11y issues |
907
+ | PropDrillingExample.tsx | Prop drilling |
908
+ | ContextExample.tsx | Context API issues |
909
+ | FormIssues.tsx | Form validation smells |
910
+ | StateManagement.tsx | State management anti-patterns |
911
+ | useComplexHook.ts | Testing gap issues |
912
+
913
+ ## Configuration Files
914
+
915
+ - \`.smellrc.json\` - Detector configuration
916
+ - \`.smellbudget.json\` - Performance budget
917
+
918
+ ## Learn More
919
+
920
+ Run \`react-smell guide\` for an interactive tutorial.
921
+ `);
922
+ }