react-code-smell-detector 1.2.0 → 1.3.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 (58) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +46 -0
  3. package/dist/analyzer.d.ts.map +1 -1
  4. package/dist/analyzer.js +18 -1
  5. package/dist/cli.js +93 -26
  6. package/dist/detectors/complexity.d.ts +17 -0
  7. package/dist/detectors/complexity.d.ts.map +1 -0
  8. package/dist/detectors/complexity.js +69 -0
  9. package/dist/detectors/imports.d.ts +22 -0
  10. package/dist/detectors/imports.d.ts.map +1 -0
  11. package/dist/detectors/imports.js +210 -0
  12. package/dist/detectors/index.d.ts +3 -0
  13. package/dist/detectors/index.d.ts.map +1 -1
  14. package/dist/detectors/index.js +4 -0
  15. package/dist/detectors/memoryLeak.d.ts +7 -0
  16. package/dist/detectors/memoryLeak.d.ts.map +1 -0
  17. package/dist/detectors/memoryLeak.js +111 -0
  18. package/dist/fixer.d.ts +23 -0
  19. package/dist/fixer.d.ts.map +1 -0
  20. package/dist/fixer.js +133 -0
  21. package/dist/git.d.ts +28 -0
  22. package/dist/git.d.ts.map +1 -0
  23. package/dist/git.js +117 -0
  24. package/dist/reporter.js +13 -0
  25. package/dist/types/index.d.ts +7 -1
  26. package/dist/types/index.d.ts.map +1 -1
  27. package/dist/types/index.js +9 -0
  28. package/dist/watcher.d.ts +16 -0
  29. package/dist/watcher.d.ts.map +1 -0
  30. package/dist/watcher.js +89 -0
  31. package/package.json +8 -2
  32. package/src/analyzer.ts +0 -324
  33. package/src/cli.ts +0 -159
  34. package/src/detectors/accessibility.ts +0 -212
  35. package/src/detectors/deadCode.ts +0 -163
  36. package/src/detectors/debug.ts +0 -103
  37. package/src/detectors/dependencyArray.ts +0 -176
  38. package/src/detectors/hooksRules.ts +0 -101
  39. package/src/detectors/index.ts +0 -20
  40. package/src/detectors/javascript.ts +0 -169
  41. package/src/detectors/largeComponent.ts +0 -63
  42. package/src/detectors/magicValues.ts +0 -114
  43. package/src/detectors/memoization.ts +0 -177
  44. package/src/detectors/missingKey.ts +0 -105
  45. package/src/detectors/nestedTernary.ts +0 -75
  46. package/src/detectors/nextjs.ts +0 -124
  47. package/src/detectors/nodejs.ts +0 -199
  48. package/src/detectors/propDrilling.ts +0 -103
  49. package/src/detectors/reactNative.ts +0 -154
  50. package/src/detectors/security.ts +0 -179
  51. package/src/detectors/typescript.ts +0 -151
  52. package/src/detectors/useEffect.ts +0 -117
  53. package/src/htmlReporter.ts +0 -464
  54. package/src/index.ts +0 -4
  55. package/src/parser/index.ts +0 -195
  56. package/src/reporter.ts +0 -291
  57. package/src/types/index.ts +0 -165
  58. package/tsconfig.json +0 -19
package/src/reporter.ts DELETED
@@ -1,291 +0,0 @@
1
- import chalk from 'chalk';
2
- import { AnalysisResult, CodeSmell, SmellSeverity } from './types/index.js';
3
- import path from 'path';
4
-
5
- export interface ReporterOptions {
6
- format: 'console' | 'json' | 'markdown';
7
- showCodeSnippets: boolean;
8
- rootDir: string;
9
- }
10
-
11
- export function reportResults(result: AnalysisResult, options: ReporterOptions): string {
12
- switch (options.format) {
13
- case 'json':
14
- return JSON.stringify(result, null, 2);
15
- case 'markdown':
16
- return formatMarkdown(result, options);
17
- case 'console':
18
- default:
19
- return formatConsole(result, options);
20
- }
21
- }
22
-
23
- function formatConsole(result: AnalysisResult, options: ReporterOptions): string {
24
- const lines: string[] = [];
25
- const { summary, debtScore, files } = result;
26
-
27
- // Header
28
- lines.push('');
29
- lines.push(chalk.bold.cyan('╔══════════════════════════════════════════════════════════════╗'));
30
- lines.push(chalk.bold.cyan('║') + chalk.bold.white(' 🔍 React Code Smell Detector Report ') + chalk.bold.cyan('║'));
31
- lines.push(chalk.bold.cyan('╚══════════════════════════════════════════════════════════════╝'));
32
- lines.push('');
33
-
34
- // Technical Debt Score
35
- const gradeColor = getGradeColor(debtScore.grade);
36
- lines.push(chalk.bold('📊 Technical Debt Score'));
37
- lines.push('');
38
- lines.push(` ${chalk.bold('Grade:')} ${gradeColor(debtScore.grade)} ${getScoreBar(debtScore.score)} ${debtScore.score}/100`);
39
- lines.push('');
40
- lines.push(chalk.dim(' Breakdown:'));
41
- lines.push(` ${chalk.yellow('⚡')} useEffect: ${getSmallBar(debtScore.breakdown.useEffectScore)} ${debtScore.breakdown.useEffectScore}`);
42
- lines.push(` ${chalk.blue('🔗')} Prop Drilling: ${getSmallBar(debtScore.breakdown.propDrillingScore)} ${debtScore.breakdown.propDrillingScore}`);
43
- lines.push(` ${chalk.magenta('📐')} Component Size:${getSmallBar(debtScore.breakdown.componentSizeScore)} ${debtScore.breakdown.componentSizeScore}`);
44
- lines.push(` ${chalk.green('💾')} Memoization: ${getSmallBar(debtScore.breakdown.memoizationScore)} ${debtScore.breakdown.memoizationScore}`);
45
- lines.push('');
46
- lines.push(` ${chalk.dim('Estimated refactor time:')} ${chalk.yellow(debtScore.estimatedRefactorTime)}`);
47
- lines.push('');
48
-
49
- // Summary
50
- lines.push(chalk.bold('📈 Summary'));
51
- lines.push('');
52
- lines.push(` Files analyzed: ${chalk.cyan(summary.totalFiles)}`);
53
- lines.push(` Components found: ${chalk.cyan(summary.totalComponents)}`);
54
- lines.push(` Total issues: ${getSeverityLabel(summary.totalSmells, summary.smellsBySeverity)}`);
55
- lines.push('');
56
-
57
- // Issues by type
58
- if (summary.totalSmells > 0) {
59
- lines.push(chalk.bold('🏷️ Issues by Type'));
60
- lines.push('');
61
- Object.entries(summary.smellsByType).forEach(([type, count]) => {
62
- if (count > 0) {
63
- lines.push(` ${chalk.dim('•')} ${formatSmellType(type)}: ${chalk.yellow(count)}`);
64
- }
65
- });
66
- lines.push('');
67
- }
68
-
69
- // Detailed findings
70
- if (files.some(f => f.smells.length > 0)) {
71
- lines.push(chalk.bold('📋 Detailed Findings'));
72
- lines.push('');
73
-
74
- files.forEach(file => {
75
- if (file.smells.length === 0) return;
76
-
77
- const relativePath = path.relative(options.rootDir, file.file);
78
- lines.push(chalk.bold.underline(relativePath));
79
- lines.push('');
80
-
81
- file.smells.forEach(smell => {
82
- const icon = getSeverityIcon(smell.severity);
83
- const color = getSeverityColor(smell.severity);
84
-
85
- lines.push(` ${icon} ${color(smell.message)}`);
86
- lines.push(` ${chalk.dim('Line')} ${smell.line} ${chalk.dim('•')} ${chalk.italic.cyan(smell.suggestion)}`);
87
-
88
- if (options.showCodeSnippets && smell.codeSnippet) {
89
- lines.push('');
90
- smell.codeSnippet.split('\n').forEach(line => {
91
- if (line.startsWith('>')) {
92
- lines.push(chalk.red(line));
93
- } else {
94
- lines.push(chalk.dim(line));
95
- }
96
- });
97
- }
98
- lines.push('');
99
- });
100
- });
101
- }
102
-
103
- // Footer
104
- if (summary.totalSmells === 0) {
105
- lines.push(chalk.green.bold('✨ No code smells detected! Your code looks great.'));
106
- } else {
107
- lines.push(chalk.dim('─'.repeat(64)));
108
- lines.push(chalk.dim(`Found ${summary.totalSmells} issue(s). Run with --help for more options.`));
109
- }
110
- lines.push('');
111
-
112
- return lines.join('\n');
113
- }
114
-
115
- function formatMarkdown(result: AnalysisResult, options: ReporterOptions): string {
116
- const lines: string[] = [];
117
- const { summary, debtScore, files } = result;
118
-
119
- lines.push('# React Code Smell Detector Report');
120
- lines.push('');
121
- lines.push('## Technical Debt Score');
122
- lines.push('');
123
- lines.push(`| Metric | Score |`);
124
- lines.push(`|--------|-------|`);
125
- lines.push(`| **Overall Grade** | **${debtScore.grade}** (${debtScore.score}/100) |`);
126
- lines.push(`| useEffect Usage | ${debtScore.breakdown.useEffectScore}/100 |`);
127
- lines.push(`| Prop Drilling | ${debtScore.breakdown.propDrillingScore}/100 |`);
128
- lines.push(`| Component Size | ${debtScore.breakdown.componentSizeScore}/100 |`);
129
- lines.push(`| Memoization | ${debtScore.breakdown.memoizationScore}/100 |`);
130
- lines.push('');
131
- lines.push(`**Estimated Refactor Time:** ${debtScore.estimatedRefactorTime}`);
132
- lines.push('');
133
-
134
- lines.push('## Summary');
135
- lines.push('');
136
- lines.push(`- **Files analyzed:** ${summary.totalFiles}`);
137
- lines.push(`- **Components found:** ${summary.totalComponents}`);
138
- lines.push(`- **Total issues:** ${summary.totalSmells}`);
139
- lines.push(` - Errors: ${summary.smellsBySeverity.error}`);
140
- lines.push(` - Warnings: ${summary.smellsBySeverity.warning}`);
141
- lines.push(` - Info: ${summary.smellsBySeverity.info}`);
142
- lines.push('');
143
-
144
- if (summary.totalSmells > 0) {
145
- lines.push('## Issues by Type');
146
- lines.push('');
147
- Object.entries(summary.smellsByType).forEach(([type, count]) => {
148
- if (count > 0) {
149
- lines.push(`- ${formatSmellType(type)}: ${count}`);
150
- }
151
- });
152
- lines.push('');
153
-
154
- lines.push('## Detailed Findings');
155
- lines.push('');
156
-
157
- files.forEach(file => {
158
- if (file.smells.length === 0) return;
159
-
160
- const relativePath = path.relative(options.rootDir, file.file);
161
- lines.push(`### ${relativePath}`);
162
- lines.push('');
163
-
164
- file.smells.forEach(smell => {
165
- const icon = smell.severity === 'error' ? '🔴' : smell.severity === 'warning' ? '🟡' : '🔵';
166
- lines.push(`#### ${icon} ${smell.message}`);
167
- lines.push('');
168
- lines.push(`- **Line:** ${smell.line}`);
169
- lines.push(`- **Severity:** ${smell.severity}`);
170
- lines.push(`- **Suggestion:** ${smell.suggestion}`);
171
-
172
- if (options.showCodeSnippets && smell.codeSnippet) {
173
- lines.push('');
174
- lines.push('```tsx');
175
- lines.push(smell.codeSnippet);
176
- lines.push('```');
177
- }
178
- lines.push('');
179
- });
180
- });
181
- }
182
-
183
- return lines.join('\n');
184
- }
185
-
186
- // Helper functions
187
- function getGradeColor(grade: string): (text: string) => string {
188
- switch (grade) {
189
- case 'A': return chalk.green.bold;
190
- case 'B': return chalk.greenBright.bold;
191
- case 'C': return chalk.yellow.bold;
192
- case 'D': return chalk.rgb(255, 165, 0).bold;
193
- case 'F': return chalk.red.bold;
194
- default: return chalk.white.bold;
195
- }
196
- }
197
-
198
- function getScoreBar(score: number): string {
199
- const filled = Math.round(score / 5);
200
- const empty = 20 - filled;
201
- const color = score >= 80 ? chalk.green : score >= 60 ? chalk.yellow : chalk.red;
202
- return color('█'.repeat(filled)) + chalk.dim('░'.repeat(empty));
203
- }
204
-
205
- function getSmallBar(score: number): string {
206
- const filled = Math.round(score / 10);
207
- const empty = 10 - filled;
208
- const color = score >= 80 ? chalk.green : score >= 60 ? chalk.yellow : chalk.red;
209
- return color('█'.repeat(filled)) + chalk.dim('░'.repeat(empty));
210
- }
211
-
212
- function getSeverityIcon(severity: SmellSeverity): string {
213
- switch (severity) {
214
- case 'error': return chalk.red('✖');
215
- case 'warning': return chalk.yellow('⚠');
216
- case 'info': return chalk.blue('ℹ');
217
- }
218
- }
219
-
220
- function getSeverityColor(severity: SmellSeverity): (text: string) => string {
221
- switch (severity) {
222
- case 'error': return chalk.red;
223
- case 'warning': return chalk.yellow;
224
- case 'info': return chalk.blue;
225
- }
226
- }
227
-
228
- function getSeverityLabel(total: number, bySeverity: Record<SmellSeverity, number>): string {
229
- const parts: string[] = [];
230
- if (bySeverity.error > 0) parts.push(chalk.red(`${bySeverity.error} error(s)`));
231
- if (bySeverity.warning > 0) parts.push(chalk.yellow(`${bySeverity.warning} warning(s)`));
232
- if (bySeverity.info > 0) parts.push(chalk.blue(`${bySeverity.info} info`));
233
- return parts.length > 0 ? parts.join(', ') : chalk.green('0');
234
- }
235
-
236
- function formatSmellType(type: string): string {
237
- const labels: Record<string, string> = {
238
- 'useEffect-overuse': '⚡ useEffect Overuse',
239
- 'prop-drilling': '🔗 Prop Drilling',
240
- 'large-component': '📐 Large Component',
241
- 'unmemoized-calculation': '💾 Unmemoized Calculation',
242
- 'missing-dependency': '🔍 Missing Dependency',
243
- 'state-in-loop': '🔄 State in Loop',
244
- 'inline-function-prop': '📎 Inline Function Prop',
245
- 'deep-nesting': '📊 Deep Nesting',
246
- 'missing-key': '🔑 Missing Key',
247
- 'hooks-rules-violation': '⚠️ Hooks Rules Violation',
248
- 'dependency-array-issue': '📋 Dependency Array Issue',
249
- 'nested-ternary': '❓ Nested Ternary',
250
- 'dead-code': '💀 Dead Code',
251
- 'magic-value': '🔢 Magic Value',
252
- // Next.js
253
- 'nextjs-client-server-boundary': '▲ Next.js Client/Server Boundary',
254
- 'nextjs-missing-metadata': '▲ Next.js Missing Metadata',
255
- 'nextjs-image-unoptimized': '▲ Next.js Unoptimized Image',
256
- 'nextjs-router-misuse': '▲ Next.js Router Misuse',
257
- // React Native
258
- 'rn-inline-style': '📱 RN Inline Style',
259
- 'rn-missing-accessibility': '📱 RN Missing Accessibility',
260
- 'rn-performance-issue': '📱 RN Performance Issue',
261
- // Node.js
262
- 'nodejs-callback-hell': '🟢 Node.js Callback Hell',
263
- 'nodejs-unhandled-promise': '🟢 Node.js Unhandled Promise',
264
- 'nodejs-sync-io': '🟢 Node.js Sync I/O',
265
- 'nodejs-missing-error-handling': '🟢 Node.js Missing Error Handling',
266
- // JavaScript
267
- 'js-var-usage': '📜 JS var Usage',
268
- 'js-loose-equality': '📜 JS Loose Equality',
269
- 'js-implicit-coercion': '📜 JS Implicit Coercion',
270
- 'js-global-pollution': '📜 JS Global Pollution',
271
- // TypeScript
272
- 'ts-any-usage': '🔷 TS any Usage',
273
- 'ts-missing-return-type': '🔷 TS Missing Return Type',
274
- 'ts-non-null-assertion': '🔷 TS Non-null Assertion',
275
- 'ts-type-assertion': '🔷 TS Type Assertion',
276
- // Debug
277
- 'debug-statement': '🐛 Debug Statement',
278
- 'todo-comment': '📝 TODO/FIXME Comment',
279
- // Security
280
- 'security-xss': '🔒 XSS Vulnerability',
281
- 'security-eval': '🔒 Eval Usage',
282
- 'security-secrets': '🔒 Exposed Secret',
283
- // Accessibility
284
- 'a11y-missing-alt': '♿ Missing Alt Text',
285
- 'a11y-missing-label': '♿ Missing Label',
286
- 'a11y-interactive-role': '♿ Interactive Role',
287
- 'a11y-keyboard': '♿ Keyboard Handler',
288
- 'a11y-semantic': '♿ Semantic HTML',
289
- };
290
- return labels[type] || type;
291
- }
@@ -1,165 +0,0 @@
1
- export type SmellSeverity = 'error' | 'warning' | 'info';
2
-
3
- export type SmellType =
4
- | 'useEffect-overuse'
5
- | 'prop-drilling'
6
- | 'large-component'
7
- | 'unmemoized-calculation'
8
- | 'missing-dependency'
9
- | 'state-in-loop'
10
- | 'inline-function-prop'
11
- | 'deep-nesting'
12
- | 'missing-key'
13
- | 'hooks-rules-violation'
14
- | 'dependency-array-issue'
15
- | 'nested-ternary'
16
- | 'dead-code'
17
- | 'magic-value'
18
- // Debug/cleanup
19
- | 'debug-statement'
20
- | 'todo-comment'
21
- // Security
22
- | 'security-xss'
23
- | 'security-eval'
24
- | 'security-secrets'
25
- // Accessibility
26
- | 'a11y-missing-alt'
27
- | 'a11y-missing-label'
28
- | 'a11y-interactive-role'
29
- | 'a11y-keyboard'
30
- | 'a11y-semantic'
31
- // Next.js specific
32
- | 'nextjs-client-server-boundary'
33
- | 'nextjs-missing-metadata'
34
- | 'nextjs-image-unoptimized'
35
- | 'nextjs-router-misuse'
36
- // React Native specific
37
- | 'rn-inline-style'
38
- | 'rn-missing-accessibility'
39
- | 'rn-performance-issue'
40
- // Node.js specific
41
- | 'nodejs-callback-hell'
42
- | 'nodejs-unhandled-promise'
43
- | 'nodejs-sync-io'
44
- | 'nodejs-missing-error-handling'
45
- // JavaScript specific
46
- | 'js-var-usage'
47
- | 'js-loose-equality'
48
- | 'js-implicit-coercion'
49
- | 'js-global-pollution'
50
- // TypeScript specific
51
- | 'ts-any-usage'
52
- | 'ts-missing-return-type'
53
- | 'ts-non-null-assertion'
54
- | 'ts-type-assertion';
55
-
56
- export interface CodeSmell {
57
- type: SmellType;
58
- severity: SmellSeverity;
59
- message: string;
60
- file: string;
61
- line: number;
62
- column: number;
63
- suggestion: string;
64
- codeSnippet?: string;
65
- }
66
-
67
- export interface ComponentInfo {
68
- name: string;
69
- file: string;
70
- startLine: number;
71
- endLine: number;
72
- lineCount: number;
73
- useEffectCount: number;
74
- useStateCount: number;
75
- useMemoCount: number;
76
- useCallbackCount: number;
77
- propsCount: number;
78
- propsDrillingDepth: number;
79
- hasExpensiveCalculation: boolean;
80
- }
81
-
82
- export interface FileAnalysis {
83
- file: string;
84
- components: ComponentInfo[];
85
- smells: CodeSmell[];
86
- imports: string[];
87
- }
88
-
89
- export interface AnalysisResult {
90
- files: FileAnalysis[];
91
- summary: AnalysisSummary;
92
- debtScore: TechnicalDebtScore;
93
- }
94
-
95
- export interface AnalysisSummary {
96
- totalFiles: number;
97
- totalComponents: number;
98
- totalSmells: number;
99
- smellsByType: Record<SmellType, number>;
100
- smellsBySeverity: Record<SmellSeverity, number>;
101
- }
102
-
103
- export interface TechnicalDebtScore {
104
- score: number; // 0-100, 100 = no debt
105
- grade: 'A' | 'B' | 'C' | 'D' | 'F';
106
- breakdown: {
107
- useEffectScore: number;
108
- propDrillingScore: number;
109
- componentSizeScore: number;
110
- memoizationScore: number;
111
- };
112
- estimatedRefactorTime: string; // e.g., "2-4 hours"
113
- }
114
-
115
- export interface DetectorConfig {
116
- maxUseEffectsPerComponent: number;
117
- maxPropDrillingDepth: number;
118
- maxComponentLines: number;
119
- maxPropsCount: number;
120
- checkMemoization: boolean;
121
- checkMissingKeys: boolean;
122
- checkHooksRules: boolean;
123
- checkDependencyArrays: boolean;
124
- maxTernaryDepth: number;
125
- checkDeadCode: boolean;
126
- checkMagicValues: boolean;
127
- magicNumberThreshold: number;
128
- // Framework detection
129
- checkNextjs: boolean;
130
- checkReactNative: boolean;
131
- checkNodejs: boolean;
132
- checkJavascript: boolean;
133
- checkTypescript: boolean;
134
- maxCallbackDepth: number;
135
- // New detectors
136
- checkDebugStatements: boolean;
137
- checkSecurity: boolean;
138
- checkAccessibility: boolean;
139
- }
140
-
141
- export const DEFAULT_CONFIG: DetectorConfig = {
142
- maxUseEffectsPerComponent: 3,
143
- maxPropDrillingDepth: 3,
144
- maxComponentLines: 300,
145
- maxPropsCount: 7,
146
- checkMemoization: true,
147
- checkMissingKeys: true,
148
- checkHooksRules: true,
149
- checkDependencyArrays: true,
150
- maxTernaryDepth: 2,
151
- checkDeadCode: true,
152
- checkMagicValues: true,
153
- magicNumberThreshold: 10,
154
- // Framework detection - auto-enabled based on project
155
- checkNextjs: true,
156
- checkReactNative: true,
157
- checkNodejs: true,
158
- checkJavascript: true,
159
- checkTypescript: true,
160
- maxCallbackDepth: 3,
161
- // New detectors
162
- checkDebugStatements: true,
163
- checkSecurity: true,
164
- checkAccessibility: true,
165
- };
package/tsconfig.json DELETED
@@ -1,19 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "ESNext",
5
- "moduleResolution": "bundler",
6
- "lib": ["ES2022"],
7
- "outDir": "./dist",
8
- "rootDir": "./src",
9
- "declaration": true,
10
- "declarationMap": true,
11
- "strict": true,
12
- "esModuleInterop": true,
13
- "skipLibCheck": true,
14
- "forceConsistentCasingInFileNames": true,
15
- "resolveJsonModule": true
16
- },
17
- "include": ["src/**/*"],
18
- "exclude": ["node_modules", "dist"]
19
- }