react-code-smell-detector 1.0.1 → 1.2.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 (53) hide show
  1. package/README.md +69 -11
  2. package/dist/analyzer.d.ts.map +1 -1
  3. package/dist/analyzer.js +78 -2
  4. package/dist/cli.js +43 -9
  5. package/dist/detectors/accessibility.d.ts +12 -0
  6. package/dist/detectors/accessibility.d.ts.map +1 -0
  7. package/dist/detectors/accessibility.js +191 -0
  8. package/dist/detectors/debug.d.ts +10 -0
  9. package/dist/detectors/debug.d.ts.map +1 -0
  10. package/dist/detectors/debug.js +87 -0
  11. package/dist/detectors/index.d.ts +8 -0
  12. package/dist/detectors/index.d.ts.map +1 -1
  13. package/dist/detectors/index.js +10 -0
  14. package/dist/detectors/javascript.d.ts +11 -0
  15. package/dist/detectors/javascript.d.ts.map +1 -0
  16. package/dist/detectors/javascript.js +148 -0
  17. package/dist/detectors/nextjs.d.ts +11 -0
  18. package/dist/detectors/nextjs.d.ts.map +1 -0
  19. package/dist/detectors/nextjs.js +103 -0
  20. package/dist/detectors/nodejs.d.ts +11 -0
  21. package/dist/detectors/nodejs.d.ts.map +1 -0
  22. package/dist/detectors/nodejs.js +169 -0
  23. package/dist/detectors/reactNative.d.ts +10 -0
  24. package/dist/detectors/reactNative.d.ts.map +1 -0
  25. package/dist/detectors/reactNative.js +135 -0
  26. package/dist/detectors/security.d.ts +12 -0
  27. package/dist/detectors/security.d.ts.map +1 -0
  28. package/dist/detectors/security.js +161 -0
  29. package/dist/detectors/typescript.d.ts +11 -0
  30. package/dist/detectors/typescript.d.ts.map +1 -0
  31. package/dist/detectors/typescript.js +135 -0
  32. package/dist/htmlReporter.d.ts +6 -0
  33. package/dist/htmlReporter.d.ts.map +1 -0
  34. package/dist/htmlReporter.js +453 -0
  35. package/dist/reporter.js +37 -0
  36. package/dist/types/index.d.ts +10 -1
  37. package/dist/types/index.d.ts.map +1 -1
  38. package/dist/types/index.js +11 -0
  39. package/package.json +2 -2
  40. package/src/analyzer.ts +91 -1
  41. package/src/cli.ts +43 -9
  42. package/src/detectors/accessibility.ts +212 -0
  43. package/src/detectors/debug.ts +103 -0
  44. package/src/detectors/index.ts +10 -0
  45. package/src/detectors/javascript.ts +169 -0
  46. package/src/detectors/nextjs.ts +124 -0
  47. package/src/detectors/nodejs.ts +199 -0
  48. package/src/detectors/reactNative.ts +154 -0
  49. package/src/detectors/security.ts +179 -0
  50. package/src/detectors/typescript.ts +151 -0
  51. package/src/htmlReporter.ts +464 -0
  52. package/src/reporter.ts +37 -0
  53. package/src/types/index.ts +61 -2
@@ -0,0 +1,453 @@
1
+ import path from 'path';
2
+ /**
3
+ * Generate a beautiful HTML report with charts and styling
4
+ */
5
+ export function generateHTMLReport(result, rootDir) {
6
+ const { summary, debtScore, files } = result;
7
+ // Generate chart data
8
+ const smellTypeData = Object.entries(summary.smellsByType)
9
+ .filter(([_, count]) => count > 0)
10
+ .sort((a, b) => b[1] - a[1])
11
+ .slice(0, 10);
12
+ const severityColors = {
13
+ error: '#ef4444',
14
+ warning: '#f59e0b',
15
+ info: '#3b82f6',
16
+ };
17
+ const gradeColors = {
18
+ A: '#22c55e',
19
+ B: '#84cc16',
20
+ C: '#eab308',
21
+ D: '#f97316',
22
+ F: '#ef4444',
23
+ };
24
+ return `<!DOCTYPE html>
25
+ <html lang="en">
26
+ <head>
27
+ <meta charset="UTF-8">
28
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
29
+ <title>Code Smell Detector Report</title>
30
+ <style>
31
+ * {
32
+ margin: 0;
33
+ padding: 0;
34
+ box-sizing: border-box;
35
+ }
36
+
37
+ body {
38
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
39
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
40
+ min-height: 100vh;
41
+ color: #e2e8f0;
42
+ line-height: 1.6;
43
+ }
44
+
45
+ .container {
46
+ max-width: 1200px;
47
+ margin: 0 auto;
48
+ padding: 2rem;
49
+ }
50
+
51
+ header {
52
+ text-align: center;
53
+ padding: 3rem 0;
54
+ border-bottom: 1px solid rgba(255,255,255,0.1);
55
+ margin-bottom: 2rem;
56
+ }
57
+
58
+ h1 {
59
+ font-size: 2.5rem;
60
+ margin-bottom: 0.5rem;
61
+ background: linear-gradient(90deg, #60a5fa, #a78bfa);
62
+ -webkit-background-clip: text;
63
+ -webkit-text-fill-color: transparent;
64
+ }
65
+
66
+ .subtitle {
67
+ color: #94a3b8;
68
+ font-size: 1.1rem;
69
+ }
70
+
71
+ .dashboard {
72
+ display: grid;
73
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
74
+ gap: 1.5rem;
75
+ margin-bottom: 2rem;
76
+ }
77
+
78
+ .card {
79
+ background: rgba(255,255,255,0.05);
80
+ border-radius: 1rem;
81
+ padding: 1.5rem;
82
+ border: 1px solid rgba(255,255,255,0.1);
83
+ backdrop-filter: blur(10px);
84
+ }
85
+
86
+ .card h2 {
87
+ font-size: 1rem;
88
+ text-transform: uppercase;
89
+ letter-spacing: 0.1em;
90
+ color: #94a3b8;
91
+ margin-bottom: 1rem;
92
+ }
93
+
94
+ .grade-container {
95
+ display: flex;
96
+ align-items: center;
97
+ gap: 2rem;
98
+ }
99
+
100
+ .grade {
101
+ font-size: 5rem;
102
+ font-weight: 800;
103
+ color: ${gradeColors[debtScore.grade]};
104
+ text-shadow: 0 0 30px ${gradeColors[debtScore.grade]}40;
105
+ }
106
+
107
+ .score-details {
108
+ flex: 1;
109
+ }
110
+
111
+ .score-bar {
112
+ height: 8px;
113
+ background: rgba(255,255,255,0.1);
114
+ border-radius: 4px;
115
+ overflow: hidden;
116
+ margin: 0.5rem 0;
117
+ }
118
+
119
+ .score-bar-fill {
120
+ height: 100%;
121
+ border-radius: 4px;
122
+ transition: width 1s ease;
123
+ }
124
+
125
+ .score-label {
126
+ display: flex;
127
+ justify-content: space-between;
128
+ font-size: 0.9rem;
129
+ color: #94a3b8;
130
+ }
131
+
132
+ .stat-grid {
133
+ display: grid;
134
+ grid-template-columns: repeat(3, 1fr);
135
+ gap: 1rem;
136
+ text-align: center;
137
+ }
138
+
139
+ .stat-value {
140
+ font-size: 2rem;
141
+ font-weight: 700;
142
+ color: #60a5fa;
143
+ }
144
+
145
+ .stat-label {
146
+ font-size: 0.8rem;
147
+ color: #94a3b8;
148
+ text-transform: uppercase;
149
+ }
150
+
151
+ .severity-badges {
152
+ display: flex;
153
+ gap: 1rem;
154
+ margin-top: 1rem;
155
+ }
156
+
157
+ .severity-badge {
158
+ padding: 0.5rem 1rem;
159
+ border-radius: 2rem;
160
+ font-size: 0.9rem;
161
+ font-weight: 600;
162
+ }
163
+
164
+ .severity-error { background: rgba(239,68,68,0.2); color: #ef4444; }
165
+ .severity-warning { background: rgba(245,158,11,0.2); color: #f59e0b; }
166
+ .severity-info { background: rgba(59,130,246,0.2); color: #3b82f6; }
167
+
168
+ .issues-by-type {
169
+ margin-top: 1rem;
170
+ }
171
+
172
+ .type-bar {
173
+ display: flex;
174
+ align-items: center;
175
+ margin: 0.75rem 0;
176
+ }
177
+
178
+ .type-name {
179
+ flex: 1;
180
+ font-size: 0.9rem;
181
+ }
182
+
183
+ .type-bar-bg {
184
+ width: 150px;
185
+ height: 20px;
186
+ background: rgba(255,255,255,0.1);
187
+ border-radius: 4px;
188
+ overflow: hidden;
189
+ margin: 0 1rem;
190
+ }
191
+
192
+ .type-bar-fill {
193
+ height: 100%;
194
+ background: linear-gradient(90deg, #60a5fa, #a78bfa);
195
+ border-radius: 4px;
196
+ }
197
+
198
+ .type-count {
199
+ width: 40px;
200
+ text-align: right;
201
+ font-weight: 600;
202
+ }
203
+
204
+ .findings-section {
205
+ margin-top: 2rem;
206
+ }
207
+
208
+ .findings-section h2 {
209
+ font-size: 1.5rem;
210
+ margin-bottom: 1rem;
211
+ padding-bottom: 0.5rem;
212
+ border-bottom: 1px solid rgba(255,255,255,0.1);
213
+ }
214
+
215
+ .file-group {
216
+ margin-bottom: 1.5rem;
217
+ }
218
+
219
+ .file-header {
220
+ font-family: 'Monaco', 'Menlo', monospace;
221
+ font-size: 0.9rem;
222
+ color: #60a5fa;
223
+ padding: 0.75rem 1rem;
224
+ background: rgba(96,165,250,0.1);
225
+ border-radius: 0.5rem 0.5rem 0 0;
226
+ border: 1px solid rgba(96,165,250,0.2);
227
+ border-bottom: none;
228
+ }
229
+
230
+ .issue {
231
+ padding: 1rem;
232
+ background: rgba(255,255,255,0.03);
233
+ border: 1px solid rgba(255,255,255,0.1);
234
+ border-top: none;
235
+ }
236
+
237
+ .issue:last-child {
238
+ border-radius: 0 0 0.5rem 0.5rem;
239
+ }
240
+
241
+ .issue-header {
242
+ display: flex;
243
+ align-items: flex-start;
244
+ gap: 0.75rem;
245
+ }
246
+
247
+ .issue-icon {
248
+ font-size: 1rem;
249
+ padding: 0.25rem;
250
+ }
251
+
252
+ .issue-message {
253
+ flex: 1;
254
+ font-weight: 500;
255
+ }
256
+
257
+ .issue-line {
258
+ font-family: monospace;
259
+ font-size: 0.8rem;
260
+ color: #94a3b8;
261
+ }
262
+
263
+ .issue-suggestion {
264
+ margin-top: 0.5rem;
265
+ padding: 0.5rem 0.75rem;
266
+ background: rgba(167,139,250,0.1);
267
+ border-left: 3px solid #a78bfa;
268
+ font-size: 0.9rem;
269
+ color: #c4b5fd;
270
+ }
271
+
272
+ .code-snippet {
273
+ margin-top: 0.5rem;
274
+ padding: 0.75rem;
275
+ background: #0d1117;
276
+ border-radius: 0.5rem;
277
+ font-family: 'Monaco', 'Menlo', monospace;
278
+ font-size: 0.8rem;
279
+ overflow-x: auto;
280
+ white-space: pre;
281
+ color: #c9d1d9;
282
+ }
283
+
284
+ .refactor-time {
285
+ font-size: 1.1rem;
286
+ color: #f59e0b;
287
+ margin-top: 0.5rem;
288
+ }
289
+
290
+ footer {
291
+ text-align: center;
292
+ padding: 2rem;
293
+ color: #64748b;
294
+ font-size: 0.9rem;
295
+ }
296
+
297
+ @keyframes fadeIn {
298
+ from { opacity: 0; transform: translateY(10px); }
299
+ to { opacity: 1; transform: translateY(0); }
300
+ }
301
+
302
+ .card, .file-group {
303
+ animation: fadeIn 0.5s ease forwards;
304
+ }
305
+ </style>
306
+ </head>
307
+ <body>
308
+ <div class="container">
309
+ <header>
310
+ <h1>🔍 Code Smell Detector</h1>
311
+ <p class="subtitle">Analysis Report - ${new Date().toLocaleDateString()}</p>
312
+ </header>
313
+
314
+ <div class="dashboard">
315
+ <div class="card">
316
+ <h2>Technical Debt Score</h2>
317
+ <div class="grade-container">
318
+ <div class="grade">${debtScore.grade}</div>
319
+ <div class="score-details">
320
+ <div class="score-label"><span>Overall Score</span><span>${debtScore.score}/100</span></div>
321
+ <div class="score-bar">
322
+ <div class="score-bar-fill" style="width: ${debtScore.score}%; background: ${gradeColors[debtScore.grade]}"></div>
323
+ </div>
324
+ ${generateBreakdownBars(debtScore.breakdown)}
325
+ <p class="refactor-time">⏱️ Est. refactor time: ${debtScore.estimatedRefactorTime}</p>
326
+ </div>
327
+ </div>
328
+ </div>
329
+
330
+ <div class="card">
331
+ <h2>Summary</h2>
332
+ <div class="stat-grid">
333
+ <div>
334
+ <div class="stat-value">${summary.totalFiles}</div>
335
+ <div class="stat-label">Files</div>
336
+ </div>
337
+ <div>
338
+ <div class="stat-value">${summary.totalComponents}</div>
339
+ <div class="stat-label">Components</div>
340
+ </div>
341
+ <div>
342
+ <div class="stat-value">${summary.totalSmells}</div>
343
+ <div class="stat-label">Issues</div>
344
+ </div>
345
+ </div>
346
+ <div class="severity-badges">
347
+ <span class="severity-badge severity-error">${summary.smellsBySeverity.error} Errors</span>
348
+ <span class="severity-badge severity-warning">${summary.smellsBySeverity.warning} Warnings</span>
349
+ <span class="severity-badge severity-info">${summary.smellsBySeverity.info} Info</span>
350
+ </div>
351
+ </div>
352
+
353
+ <div class="card">
354
+ <h2>Issues by Type</h2>
355
+ <div class="issues-by-type">
356
+ ${smellTypeData.map(([type, count]) => {
357
+ const maxCount = Math.max(...smellTypeData.map(([_, c]) => c));
358
+ const percentage = (count / maxCount) * 100;
359
+ return `
360
+ <div class="type-bar">
361
+ <span class="type-name">${formatTypeLabel(type)}</span>
362
+ <div class="type-bar-bg">
363
+ <div class="type-bar-fill" style="width: ${percentage}%"></div>
364
+ </div>
365
+ <span class="type-count">${count}</span>
366
+ </div>
367
+ `;
368
+ }).join('')}
369
+ </div>
370
+ </div>
371
+ </div>
372
+
373
+ <section class="findings-section">
374
+ <h2>📋 Detailed Findings</h2>
375
+ ${files.filter(f => f.smells.length > 0).map(file => `
376
+ <div class="file-group">
377
+ <div class="file-header">${path.relative(rootDir, file.file)}</div>
378
+ ${file.smells.map(smell => `
379
+ <div class="issue">
380
+ <div class="issue-header">
381
+ <span class="issue-icon">${getSeverityIcon(smell.severity)}</span>
382
+ <span class="issue-message" style="color: ${severityColors[smell.severity]}">${escapeHtml(smell.message)}</span>
383
+ <span class="issue-line">Line ${smell.line}</span>
384
+ </div>
385
+ <div class="issue-suggestion">💡 ${escapeHtml(smell.suggestion)}</div>
386
+ ${smell.codeSnippet ? `<div class="code-snippet">${escapeHtml(smell.codeSnippet)}</div>` : ''}
387
+ </div>
388
+ `).join('')}
389
+ </div>
390
+ `).join('')}
391
+ </section>
392
+
393
+ <footer>
394
+ Generated by React Code Smell Detector • ${new Date().toISOString()}
395
+ </footer>
396
+ </div>
397
+ </body>
398
+ </html>`;
399
+ }
400
+ function generateBreakdownBars(breakdown) {
401
+ const items = [
402
+ { label: 'useEffect', score: breakdown.useEffectScore },
403
+ { label: 'Prop Drilling', score: breakdown.propDrillingScore },
404
+ { label: 'Component Size', score: breakdown.componentSizeScore },
405
+ { label: 'Memoization', score: breakdown.memoizationScore },
406
+ ];
407
+ return items.map(({ label, score }) => {
408
+ const color = score >= 80 ? '#22c55e' : score >= 60 ? '#eab308' : '#ef4444';
409
+ return `
410
+ <div class="score-label" style="margin-top: 0.5rem"><span>${label}</span><span>${score}</span></div>
411
+ <div class="score-bar">
412
+ <div class="score-bar-fill" style="width: ${score}%; background: ${color}"></div>
413
+ </div>
414
+ `;
415
+ }).join('');
416
+ }
417
+ function formatTypeLabel(type) {
418
+ const labels = {
419
+ 'useEffect-overuse': '⚡ useEffect',
420
+ 'prop-drilling': '🔗 Prop Drilling',
421
+ 'large-component': '📐 Large Component',
422
+ 'unmemoized-calculation': '💾 Unmemoized',
423
+ 'inline-function-prop': '📎 Inline Func',
424
+ 'deep-nesting': '📊 Deep Nesting',
425
+ 'missing-key': '🔑 Missing Key',
426
+ 'magic-value': '🔢 Magic Value',
427
+ 'debug-statement': '🐛 Debug',
428
+ 'todo-comment': '📝 TODO',
429
+ 'security-xss': '🔒 Security XSS',
430
+ 'security-eval': '🔒 Security Eval',
431
+ 'security-secrets': '🔑 Secrets',
432
+ 'a11y-missing-alt': '♿ Missing Alt',
433
+ 'a11y-missing-label': '♿ Missing Label',
434
+ 'ts-any-usage': '🔷 TS any',
435
+ 'ts-missing-return-type': '🔷 TS Return Type',
436
+ };
437
+ return labels[type] || type.replace(/-/g, ' ');
438
+ }
439
+ function getSeverityIcon(severity) {
440
+ switch (severity) {
441
+ case 'error': return '❌';
442
+ case 'warning': return '⚠️';
443
+ case 'info': return 'ℹ️';
444
+ }
445
+ }
446
+ function escapeHtml(text) {
447
+ return text
448
+ .replace(/&/g, '&amp;')
449
+ .replace(/</g, '&lt;')
450
+ .replace(/>/g, '&gt;')
451
+ .replace(/"/g, '&quot;')
452
+ .replace(/'/g, '&#039;');
453
+ }
package/dist/reporter.js CHANGED
@@ -218,6 +218,43 @@ function formatSmellType(type) {
218
218
  'nested-ternary': '❓ Nested Ternary',
219
219
  'dead-code': '💀 Dead Code',
220
220
  'magic-value': '🔢 Magic Value',
221
+ // Next.js
222
+ 'nextjs-client-server-boundary': '▲ Next.js Client/Server Boundary',
223
+ 'nextjs-missing-metadata': '▲ Next.js Missing Metadata',
224
+ 'nextjs-image-unoptimized': '▲ Next.js Unoptimized Image',
225
+ 'nextjs-router-misuse': '▲ Next.js Router Misuse',
226
+ // React Native
227
+ 'rn-inline-style': '📱 RN Inline Style',
228
+ 'rn-missing-accessibility': '📱 RN Missing Accessibility',
229
+ 'rn-performance-issue': '📱 RN Performance Issue',
230
+ // Node.js
231
+ 'nodejs-callback-hell': '🟢 Node.js Callback Hell',
232
+ 'nodejs-unhandled-promise': '🟢 Node.js Unhandled Promise',
233
+ 'nodejs-sync-io': '🟢 Node.js Sync I/O',
234
+ 'nodejs-missing-error-handling': '🟢 Node.js Missing Error Handling',
235
+ // JavaScript
236
+ 'js-var-usage': '📜 JS var Usage',
237
+ 'js-loose-equality': '📜 JS Loose Equality',
238
+ 'js-implicit-coercion': '📜 JS Implicit Coercion',
239
+ 'js-global-pollution': '📜 JS Global Pollution',
240
+ // TypeScript
241
+ 'ts-any-usage': '🔷 TS any Usage',
242
+ 'ts-missing-return-type': '🔷 TS Missing Return Type',
243
+ 'ts-non-null-assertion': '🔷 TS Non-null Assertion',
244
+ 'ts-type-assertion': '🔷 TS Type Assertion',
245
+ // Debug
246
+ 'debug-statement': '🐛 Debug Statement',
247
+ 'todo-comment': '📝 TODO/FIXME Comment',
248
+ // Security
249
+ 'security-xss': '🔒 XSS Vulnerability',
250
+ 'security-eval': '🔒 Eval Usage',
251
+ 'security-secrets': '🔒 Exposed Secret',
252
+ // Accessibility
253
+ 'a11y-missing-alt': '♿ Missing Alt Text',
254
+ 'a11y-missing-label': '♿ Missing Label',
255
+ 'a11y-interactive-role': '♿ Interactive Role',
256
+ 'a11y-keyboard': '♿ Keyboard Handler',
257
+ 'a11y-semantic': '♿ Semantic HTML',
221
258
  };
222
259
  return labels[type] || type;
223
260
  }
@@ -1,5 +1,5 @@
1
1
  export type SmellSeverity = 'error' | 'warning' | 'info';
2
- export type SmellType = 'useEffect-overuse' | 'prop-drilling' | 'large-component' | 'unmemoized-calculation' | 'missing-dependency' | 'state-in-loop' | 'inline-function-prop' | 'deep-nesting' | 'missing-key' | 'hooks-rules-violation' | 'dependency-array-issue' | 'nested-ternary' | 'dead-code' | 'magic-value';
2
+ export type SmellType = 'useEffect-overuse' | 'prop-drilling' | 'large-component' | 'unmemoized-calculation' | 'missing-dependency' | 'state-in-loop' | 'inline-function-prop' | 'deep-nesting' | 'missing-key' | 'hooks-rules-violation' | 'dependency-array-issue' | 'nested-ternary' | 'dead-code' | 'magic-value' | 'debug-statement' | 'todo-comment' | 'security-xss' | 'security-eval' | 'security-secrets' | 'a11y-missing-alt' | 'a11y-missing-label' | 'a11y-interactive-role' | 'a11y-keyboard' | 'a11y-semantic' | 'nextjs-client-server-boundary' | 'nextjs-missing-metadata' | 'nextjs-image-unoptimized' | 'nextjs-router-misuse' | 'rn-inline-style' | 'rn-missing-accessibility' | 'rn-performance-issue' | 'nodejs-callback-hell' | 'nodejs-unhandled-promise' | 'nodejs-sync-io' | 'nodejs-missing-error-handling' | 'js-var-usage' | 'js-loose-equality' | 'js-implicit-coercion' | 'js-global-pollution' | 'ts-any-usage' | 'ts-missing-return-type' | 'ts-non-null-assertion' | 'ts-type-assertion';
3
3
  export interface CodeSmell {
4
4
  type: SmellType;
5
5
  severity: SmellSeverity;
@@ -66,6 +66,15 @@ export interface DetectorConfig {
66
66
  checkDeadCode: boolean;
67
67
  checkMagicValues: boolean;
68
68
  magicNumberThreshold: number;
69
+ checkNextjs: boolean;
70
+ checkReactNative: boolean;
71
+ checkNodejs: boolean;
72
+ checkJavascript: boolean;
73
+ checkTypescript: boolean;
74
+ maxCallbackDepth: number;
75
+ checkDebugStatements: boolean;
76
+ checkSecurity: boolean;
77
+ checkAccessibility: boolean;
69
78
  }
70
79
  export declare const DEFAULT_CONFIG: DetectorConfig;
71
80
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAEzD,MAAM,MAAM,SAAS,GACjB,mBAAmB,GACnB,eAAe,GACf,iBAAiB,GACjB,wBAAwB,GACxB,oBAAoB,GACpB,eAAe,GACf,sBAAsB,GACtB,cAAc,GACd,aAAa,GACb,uBAAuB,GACvB,wBAAwB,GACxB,gBAAgB,GAChB,WAAW,GACX,aAAa,CAAC;AAElB,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,SAAS,CAAC;IAChB,QAAQ,EAAE,aAAa,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,uBAAuB,EAAE,OAAO,CAAC;CAClC;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,aAAa,EAAE,CAAC;IAC5B,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,OAAO,EAAE,eAAe,CAAC;IACzB,SAAS,EAAE,kBAAkB,CAAC;CAC/B;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IACxC,gBAAgB,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;CACjD;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;IACnC,SAAS,EAAE;QACT,cAAc,EAAE,MAAM,CAAC;QACvB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,kBAAkB,EAAE,MAAM,CAAC;QAC3B,gBAAgB,EAAE,MAAM,CAAC;KAC1B,CAAC;IACF,qBAAqB,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,cAAc;IAC7B,yBAAyB,EAAE,MAAM,CAAC;IAClC,oBAAoB,EAAE,MAAM,CAAC;IAC7B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,eAAe,EAAE,OAAO,CAAC;IACzB,qBAAqB,EAAE,OAAO,CAAC;IAC/B,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,OAAO,CAAC;IACvB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AAED,eAAO,MAAM,cAAc,EAAE,cAa5B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
@@ -11,4 +11,15 @@ export const DEFAULT_CONFIG = {
11
11
  checkDeadCode: true,
12
12
  checkMagicValues: true,
13
13
  magicNumberThreshold: 10,
14
+ // Framework detection - auto-enabled based on project
15
+ checkNextjs: true,
16
+ checkReactNative: true,
17
+ checkNodejs: true,
18
+ checkJavascript: true,
19
+ checkTypescript: true,
20
+ maxCallbackDepth: 3,
21
+ // New detectors
22
+ checkDebugStatements: true,
23
+ checkSecurity: true,
24
+ checkAccessibility: true,
14
25
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "react-code-smell-detector",
3
- "version": "1.0.1",
4
- "description": "Detect code smells in React projects - useEffect overuse, prop drilling, large components, and more",
3
+ "version": "1.2.0",
4
+ "description": "Detect code smells in React projects - useEffect overuse, prop drilling, large components, security issues, accessibility, and more",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
7
7
  "react-smell": "dist/cli.js"
package/src/analyzer.ts CHANGED
@@ -13,6 +13,14 @@ import {
13
13
  detectNestedTernaries,
14
14
  detectDeadCode,
15
15
  detectMagicValues,
16
+ detectNextjsIssues,
17
+ detectReactNativeIssues,
18
+ detectNodejsIssues,
19
+ detectJavascriptIssues,
20
+ detectTypescriptIssues,
21
+ detectDebugStatements,
22
+ detectSecurityIssues,
23
+ detectAccessibilityIssues,
16
24
  } from './detectors/index.js';
17
25
  import {
18
26
  AnalysisResult,
@@ -112,19 +120,64 @@ function analyzeFile(parseResult: ParseResult, filePath: string, config: Detecto
112
120
  smells.push(...detectNestedTernaries(component, filePath, sourceCode, config));
113
121
  smells.push(...detectDeadCode(component, filePath, sourceCode, config));
114
122
  smells.push(...detectMagicValues(component, filePath, sourceCode, config));
123
+ // Framework-specific detectors
124
+ smells.push(...detectNextjsIssues(component, filePath, sourceCode, config, imports));
125
+ smells.push(...detectReactNativeIssues(component, filePath, sourceCode, config, imports));
126
+ smells.push(...detectNodejsIssues(component, filePath, sourceCode, config, imports));
127
+ smells.push(...detectJavascriptIssues(component, filePath, sourceCode, config));
128
+ smells.push(...detectTypescriptIssues(component, filePath, sourceCode, config));
129
+ // Debug, Security, Accessibility
130
+ smells.push(...detectDebugStatements(component, filePath, sourceCode, config));
131
+ smells.push(...detectSecurityIssues(component, filePath, sourceCode, config));
132
+ smells.push(...detectAccessibilityIssues(component, filePath, sourceCode, config));
115
133
  });
116
134
 
117
135
  // Run cross-component analysis
118
136
  smells.push(...analyzePropDrillingDepth(components, filePath, sourceCode, config));
119
137
 
138
+ // Filter out smells with @smell-ignore comments
139
+ const filteredSmells = filterIgnoredSmells(smells, sourceCode);
140
+
120
141
  return {
121
142
  file: filePath,
122
143
  components: componentInfos,
123
- smells,
144
+ smells: filteredSmells,
124
145
  imports,
125
146
  };
126
147
  }
127
148
 
149
+ /**
150
+ * Filter out smells that have @smell-ignore comment on the preceding line
151
+ * Supports: // @smell-ignore, block comments with @smell-ignore, @smell-ignore [type]
152
+ */
153
+ function filterIgnoredSmells(smells: CodeSmell[], sourceCode: string): CodeSmell[] {
154
+ const lines = sourceCode.split('\n');
155
+
156
+ return smells.filter(smell => {
157
+ if (smell.line <= 1) return true;
158
+
159
+ // Check the line before and the same line for ignore comments
160
+ const lineIndex = smell.line - 1; // Convert to 0-indexed
161
+ const prevLine = lines[lineIndex - 1]?.trim() || '';
162
+ const currentLine = lines[lineIndex]?.trim() || '';
163
+
164
+ // Check for @smell-ignore patterns
165
+ const ignorePatterns = [
166
+ /@smell-ignore\s*$/, // @smell-ignore (ignore all)
167
+ /@smell-ignore\s+\*/, // @smell-ignore * (ignore all)
168
+ new RegExp(`@smell-ignore\\s+${smell.type}`), // @smell-ignore [specific-type]
169
+ ];
170
+
171
+ for (const pattern of ignorePatterns) {
172
+ if (pattern.test(prevLine) || pattern.test(currentLine)) {
173
+ return false; // Filter out this smell
174
+ }
175
+ }
176
+
177
+ return true; // Keep this smell
178
+ });
179
+ }
180
+
128
181
  function calculateSummary(files: FileAnalysis[]): AnalysisSummary {
129
182
  const smellsByType: Record<SmellType, number> = {
130
183
  'useEffect-overuse': 0,
@@ -141,6 +194,43 @@ function calculateSummary(files: FileAnalysis[]): AnalysisSummary {
141
194
  'nested-ternary': 0,
142
195
  'dead-code': 0,
143
196
  'magic-value': 0,
197
+ // Next.js
198
+ 'nextjs-client-server-boundary': 0,
199
+ 'nextjs-missing-metadata': 0,
200
+ 'nextjs-image-unoptimized': 0,
201
+ 'nextjs-router-misuse': 0,
202
+ // React Native
203
+ 'rn-inline-style': 0,
204
+ 'rn-missing-accessibility': 0,
205
+ 'rn-performance-issue': 0,
206
+ // Node.js
207
+ 'nodejs-callback-hell': 0,
208
+ 'nodejs-unhandled-promise': 0,
209
+ 'nodejs-sync-io': 0,
210
+ 'nodejs-missing-error-handling': 0,
211
+ // JavaScript
212
+ 'js-var-usage': 0,
213
+ 'js-loose-equality': 0,
214
+ 'js-implicit-coercion': 0,
215
+ 'js-global-pollution': 0,
216
+ // TypeScript
217
+ 'ts-any-usage': 0,
218
+ 'ts-missing-return-type': 0,
219
+ 'ts-non-null-assertion': 0,
220
+ 'ts-type-assertion': 0,
221
+ // Debug statements
222
+ 'debug-statement': 0,
223
+ 'todo-comment': 0,
224
+ // Security
225
+ 'security-xss': 0,
226
+ 'security-eval': 0,
227
+ 'security-secrets': 0,
228
+ // Accessibility
229
+ 'a11y-missing-alt': 0,
230
+ 'a11y-missing-label': 0,
231
+ 'a11y-interactive-role': 0,
232
+ 'a11y-keyboard': 0,
233
+ 'a11y-semantic': 0,
144
234
  };
145
235
 
146
236
  const smellsBySeverity: Record<SmellSeverity, number> = {