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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhooks.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/webhooks.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,209 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import { sendWebhookNotification, getWebhookConfig } from '../webhooks.js';
3
+ // Mock https module
4
+ vi.mock('https', () => ({
5
+ default: {
6
+ request: vi.fn((options, callback) => {
7
+ const mockRes = { statusCode: 200 };
8
+ setTimeout(() => callback(mockRes), 0);
9
+ return {
10
+ on: vi.fn(),
11
+ write: vi.fn(),
12
+ end: vi.fn(),
13
+ };
14
+ }),
15
+ },
16
+ }));
17
+ describe('Webhooks', () => {
18
+ let envBackup;
19
+ beforeEach(() => {
20
+ vi.clearAllMocks();
21
+ envBackup = { ...process.env };
22
+ });
23
+ afterEach(() => {
24
+ process.env = envBackup;
25
+ });
26
+ const createSmell = (type, severity = 'warning') => ({
27
+ type: type,
28
+ severity,
29
+ message: 'Test smell',
30
+ file: '/test.tsx',
31
+ line: 1,
32
+ column: 0,
33
+ suggestion: 'Fix it',
34
+ });
35
+ const createManySmells = (count) => {
36
+ return Array.from({ length: count }, (_, i) => createSmell(`type-${i % 5}`, i % 3 === 0 ? 'error' : 'warning'));
37
+ };
38
+ describe('sendWebhookNotification', () => {
39
+ it('should return false when webhook url is empty', async () => {
40
+ const config = {
41
+ url: '',
42
+ type: 'slack',
43
+ };
44
+ const smells = [createSmell('debug-statement')];
45
+ const result = await sendWebhookNotification(config, smells, 'test-project');
46
+ expect(result).toBe(false);
47
+ });
48
+ it('should return false when below threshold', async () => {
49
+ const config = {
50
+ url: 'https://hooks.example.com/test',
51
+ type: 'slack',
52
+ threshold: 10,
53
+ };
54
+ const smells = [createSmell('debug-statement')];
55
+ const result = await sendWebhookNotification(config, smells, 'test-project');
56
+ expect(result).toBe(false);
57
+ });
58
+ it('should format slack message correctly', async () => {
59
+ const config = {
60
+ url: 'https://hooks.slack.com/services/test',
61
+ type: 'slack',
62
+ };
63
+ const smells = createManySmells(5);
64
+ const result = await sendWebhookNotification(config, smells, 'test-project', {
65
+ branch: 'main',
66
+ commit: 'abc123def456',
67
+ author: 'Test User',
68
+ });
69
+ expect(typeof result).toBe('boolean');
70
+ });
71
+ it('should format discord message correctly', async () => {
72
+ const config = {
73
+ url: 'https://discord.com/api/webhooks/test',
74
+ type: 'discord',
75
+ };
76
+ const smells = createManySmells(15);
77
+ const result = await sendWebhookNotification(config, smells, 'test-project', {
78
+ branch: 'develop',
79
+ author: 'Dev User',
80
+ });
81
+ expect(typeof result).toBe('boolean');
82
+ });
83
+ it('should format generic message correctly', async () => {
84
+ const config = {
85
+ url: 'https://example.com/webhook',
86
+ type: 'generic',
87
+ };
88
+ const smells = createManySmells(25);
89
+ const result = await sendWebhookNotification(config, smells, 'test-project', {
90
+ branch: 'feature/test',
91
+ commit: 'xyz789',
92
+ });
93
+ expect(typeof result).toBe('boolean');
94
+ });
95
+ it('should handle high smell count (danger color)', async () => {
96
+ const config = {
97
+ url: 'https://hooks.slack.com/services/test',
98
+ type: 'slack',
99
+ };
100
+ const smells = createManySmells(25);
101
+ const result = await sendWebhookNotification(config, smells, 'test-project');
102
+ expect(typeof result).toBe('boolean');
103
+ });
104
+ it('should handle critical smell count (50+)', async () => {
105
+ const config = {
106
+ url: 'https://hooks.slack.com/services/test',
107
+ type: 'slack',
108
+ };
109
+ const smells = createManySmells(55);
110
+ const result = await sendWebhookNotification(config, smells, 'test-project');
111
+ expect(typeof result).toBe('boolean');
112
+ });
113
+ it('should handle zero smells', async () => {
114
+ const config = {
115
+ url: 'https://hooks.slack.com/services/test',
116
+ type: 'slack',
117
+ };
118
+ const result = await sendWebhookNotification(config, [], 'test-project');
119
+ expect(typeof result).toBe('boolean');
120
+ });
121
+ it('should include details when flag is set', async () => {
122
+ const config = {
123
+ url: 'https://hooks.slack.com/services/test',
124
+ type: 'slack',
125
+ includeDetails: true,
126
+ };
127
+ const smells = [createSmell('debug-statement')];
128
+ const result = await sendWebhookNotification(config, smells, 'test-project');
129
+ expect(typeof result).toBe('boolean');
130
+ });
131
+ it('should handle discord with includeDetails', async () => {
132
+ const config = {
133
+ url: 'https://discord.com/api/webhooks/test',
134
+ type: 'discord',
135
+ includeDetails: true,
136
+ };
137
+ const smells = createManySmells(12);
138
+ const result = await sendWebhookNotification(config, smells, 'test-project');
139
+ expect(typeof result).toBe('boolean');
140
+ });
141
+ });
142
+ describe('getWebhookConfig', () => {
143
+ it('should return null when no webhook configured', () => {
144
+ delete process.env.REACT_SMELL_SLACK_WEBHOOK;
145
+ delete process.env.SLACK_WEBHOOK_URL;
146
+ delete process.env.REACT_SMELL_DISCORD_WEBHOOK;
147
+ delete process.env.DISCORD_WEBHOOK_URL;
148
+ delete process.env.REACT_SMELL_WEBHOOK;
149
+ const config = getWebhookConfig();
150
+ expect(config).toBeNull();
151
+ });
152
+ it('should return slack config when slack URL provided as argument', () => {
153
+ const config = getWebhookConfig('https://hooks.slack.com/test');
154
+ expect(config).not.toBeNull();
155
+ expect(config?.type).toBe('slack');
156
+ expect(config?.url).toBe('https://hooks.slack.com/test');
157
+ });
158
+ it('should return discord config when discord URL provided as argument', () => {
159
+ const config = getWebhookConfig(undefined, 'https://discord.com/api/webhooks/test');
160
+ expect(config).not.toBeNull();
161
+ expect(config?.type).toBe('discord');
162
+ });
163
+ it('should return generic config when generic URL provided as argument', () => {
164
+ const config = getWebhookConfig(undefined, undefined, 'https://example.com/webhook');
165
+ expect(config).not.toBeNull();
166
+ expect(config?.type).toBe('generic');
167
+ });
168
+ it('should prefer slack over discord', () => {
169
+ const config = getWebhookConfig('https://hooks.slack.com/test', 'https://discord.com/api/webhooks/test');
170
+ expect(config?.type).toBe('slack');
171
+ });
172
+ it('should read from REACT_SMELL_SLACK_WEBHOOK env var', () => {
173
+ process.env.REACT_SMELL_SLACK_WEBHOOK = 'https://hooks.slack.com/env';
174
+ const config = getWebhookConfig();
175
+ expect(config?.type).toBe('slack');
176
+ expect(config?.url).toBe('https://hooks.slack.com/env');
177
+ });
178
+ it('should read from SLACK_WEBHOOK_URL env var', () => {
179
+ delete process.env.REACT_SMELL_SLACK_WEBHOOK;
180
+ process.env.SLACK_WEBHOOK_URL = 'https://hooks.slack.com/env2';
181
+ const config = getWebhookConfig();
182
+ expect(config?.type).toBe('slack');
183
+ });
184
+ it('should read from REACT_SMELL_DISCORD_WEBHOOK env var', () => {
185
+ delete process.env.REACT_SMELL_SLACK_WEBHOOK;
186
+ delete process.env.SLACK_WEBHOOK_URL;
187
+ process.env.REACT_SMELL_DISCORD_WEBHOOK = 'https://discord.com/api/webhooks/env';
188
+ const config = getWebhookConfig();
189
+ expect(config?.type).toBe('discord');
190
+ });
191
+ it('should read from DISCORD_WEBHOOK_URL env var', () => {
192
+ delete process.env.REACT_SMELL_SLACK_WEBHOOK;
193
+ delete process.env.SLACK_WEBHOOK_URL;
194
+ delete process.env.REACT_SMELL_DISCORD_WEBHOOK;
195
+ process.env.DISCORD_WEBHOOK_URL = 'https://discord.com/api/webhooks/env2';
196
+ const config = getWebhookConfig();
197
+ expect(config?.type).toBe('discord');
198
+ });
199
+ it('should read from REACT_SMELL_WEBHOOK env var', () => {
200
+ delete process.env.REACT_SMELL_SLACK_WEBHOOK;
201
+ delete process.env.SLACK_WEBHOOK_URL;
202
+ delete process.env.REACT_SMELL_DISCORD_WEBHOOK;
203
+ delete process.env.DISCORD_WEBHOOK_URL;
204
+ process.env.REACT_SMELL_WEBHOOK = 'https://example.com/generic';
205
+ const config = getWebhookConfig();
206
+ expect(config?.type).toBe('generic');
207
+ });
208
+ });
209
+ });
@@ -0,0 +1,29 @@
1
+ import { CodeSmell, AnalysisResult } from './types/index.js';
2
+ export interface AIRefactoringSuggestion {
3
+ smell: CodeSmell;
4
+ originalCode: string;
5
+ suggestedCode: string;
6
+ explanation: string;
7
+ confidence: number;
8
+ estimatedEffort: 'low' | 'medium' | 'high';
9
+ }
10
+ export interface AIRefactoringConfig {
11
+ apiKey: string;
12
+ model: string;
13
+ maxTokens?: number;
14
+ temperature?: number;
15
+ provider?: 'openai' | 'anthropic' | 'azure';
16
+ }
17
+ /**
18
+ * Generate AI-powered refactoring suggestions for detected code smells
19
+ */
20
+ export declare function generateAIRefactoringSuggestions(smells: CodeSmell[], sourceCode: string, config: AIRefactoringConfig): Promise<AIRefactoringSuggestion[]>;
21
+ /**
22
+ * Generate a summary report with AI insights
23
+ */
24
+ export declare function generateAIAnalysisReport(result: AnalysisResult, config: AIRefactoringConfig): Promise<string>;
25
+ /**
26
+ * Get refactoring suggestions for specific smell types
27
+ */
28
+ export declare function getQuickRefactoringTemplates(smellType: string): string[];
29
+ //# sourceMappingURL=aiRefactoring.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aiRefactoring.d.ts","sourceRoot":"","sources":["../src/aiRefactoring.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAkB,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAE7E,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,SAAS,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;CAC5C;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,QAAQ,GAAG,WAAW,GAAG,OAAO,CAAC;CAC7C;AAED;;GAEG;AACH,wBAAsB,gCAAgC,CACpD,MAAM,EAAE,SAAS,EAAE,EACnB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,mBAAmB,GAC1B,OAAO,CAAC,uBAAuB,EAAE,CAAC,CAmBpC;AAyND;;GAEG;AACH,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE,mBAAmB,GAC1B,OAAO,CAAC,MAAM,CAAC,CA6CjB;AAED;;GAEG;AACH,wBAAgB,4BAA4B,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAmCxE"}
@@ -0,0 +1,290 @@
1
+ /**
2
+ * Generate AI-powered refactoring suggestions for detected code smells
3
+ */
4
+ export async function generateAIRefactoringSuggestions(smells, sourceCode, config) {
5
+ if (!config.apiKey) {
6
+ console.warn('AI refactoring: No API key provided');
7
+ return [];
8
+ }
9
+ const suggestions = [];
10
+ // Process smells in batches to avoid rate limits
11
+ const batchSize = 5;
12
+ for (let i = 0; i < smells.length; i += batchSize) {
13
+ const batch = smells.slice(i, i + batchSize);
14
+ const batchSuggestions = await Promise.all(batch.map(smell => generateSuggestionForSmell(smell, sourceCode, config)));
15
+ suggestions.push(...batchSuggestions.filter((s) => s !== null));
16
+ }
17
+ return suggestions;
18
+ }
19
+ /**
20
+ * Generate a refactoring suggestion for a single smell
21
+ */
22
+ async function generateSuggestionForSmell(smell, sourceCode, config) {
23
+ try {
24
+ const codeContext = extractCodeContext(sourceCode, smell.line, 10);
25
+ const prompt = buildRefactoringPrompt(smell, codeContext);
26
+ const response = await callLLMAPI(prompt, config);
27
+ if (!response)
28
+ return null;
29
+ return {
30
+ smell,
31
+ originalCode: codeContext,
32
+ suggestedCode: response.code || '',
33
+ explanation: response.explanation || smell.suggestion,
34
+ confidence: response.confidence || 0.7,
35
+ estimatedEffort: estimateEffort(smell.type),
36
+ };
37
+ }
38
+ catch (error) {
39
+ console.error(`AI refactoring failed for ${smell.type}:`, error);
40
+ return null;
41
+ }
42
+ }
43
+ /**
44
+ * Extract code context around a specific line
45
+ */
46
+ function extractCodeContext(sourceCode, line, contextLines) {
47
+ const lines = sourceCode.split('\n');
48
+ const start = Math.max(0, line - contextLines - 1);
49
+ const end = Math.min(lines.length, line + contextLines);
50
+ return lines.slice(start, end).join('\n');
51
+ }
52
+ /**
53
+ * Build the prompt for the LLM
54
+ */
55
+ function buildRefactoringPrompt(smell, codeContext) {
56
+ return `You are an expert React developer. Analyze this code smell and provide a refactored solution.
57
+
58
+ ## Code Smell Detected
59
+ - Type: ${smell.type}
60
+ - Severity: ${smell.severity}
61
+ - Message: ${smell.message}
62
+ - Suggestion: ${smell.suggestion}
63
+
64
+ ## Code Context (around line ${smell.line})
65
+ \`\`\`typescript
66
+ ${codeContext}
67
+ \`\`\`
68
+
69
+ ## Your Task
70
+ 1. Provide the refactored code that fixes this issue
71
+ 2. Explain why the refactoring improves the code
72
+ 3. Rate your confidence in this suggestion (0-1)
73
+
74
+ Respond in JSON format:
75
+ {
76
+ "code": "// refactored code here",
77
+ "explanation": "Why this refactoring helps...",
78
+ "confidence": 0.85
79
+ }`;
80
+ }
81
+ /**
82
+ * Call the LLM API (supports OpenAI and Anthropic)
83
+ */
84
+ async function callLLMAPI(prompt, config) {
85
+ const provider = config.provider || 'openai';
86
+ try {
87
+ if (provider === 'openai' || provider === 'azure') {
88
+ return await callOpenAI(prompt, config);
89
+ }
90
+ else if (provider === 'anthropic') {
91
+ return await callAnthropic(prompt, config);
92
+ }
93
+ }
94
+ catch (error) {
95
+ console.error('LLM API call failed:', error);
96
+ }
97
+ return null;
98
+ }
99
+ /**
100
+ * Call OpenAI API
101
+ */
102
+ async function callOpenAI(prompt, config) {
103
+ const response = await fetch('https://api.openai.com/v1/chat/completions', {
104
+ method: 'POST',
105
+ headers: {
106
+ 'Content-Type': 'application/json',
107
+ 'Authorization': `Bearer ${config.apiKey}`,
108
+ },
109
+ body: JSON.stringify({
110
+ model: config.model || 'gpt-4',
111
+ messages: [{ role: 'user', content: prompt }],
112
+ max_tokens: config.maxTokens || 1000,
113
+ temperature: config.temperature || 0.3,
114
+ }),
115
+ });
116
+ if (!response.ok) {
117
+ throw new Error(`OpenAI API error: ${response.status}`);
118
+ }
119
+ const data = await response.json();
120
+ const content = data.choices?.[0]?.message?.content;
121
+ if (!content)
122
+ return null;
123
+ try {
124
+ // Extract JSON from response (it might have markdown formatting)
125
+ const jsonMatch = content.match(/\{[\s\S]*\}/);
126
+ if (jsonMatch) {
127
+ return JSON.parse(jsonMatch[0]);
128
+ }
129
+ }
130
+ catch {
131
+ // If JSON parsing fails, extract what we can
132
+ return {
133
+ code: content,
134
+ explanation: 'AI-generated suggestion',
135
+ confidence: 0.5,
136
+ };
137
+ }
138
+ return null;
139
+ }
140
+ /**
141
+ * Call Anthropic API
142
+ */
143
+ async function callAnthropic(prompt, config) {
144
+ const response = await fetch('https://api.anthropic.com/v1/messages', {
145
+ method: 'POST',
146
+ headers: {
147
+ 'Content-Type': 'application/json',
148
+ 'x-api-key': config.apiKey,
149
+ 'anthropic-version': '2023-06-01',
150
+ },
151
+ body: JSON.stringify({
152
+ model: config.model || 'claude-3-sonnet-20240229',
153
+ max_tokens: config.maxTokens || 1000,
154
+ messages: [{ role: 'user', content: prompt }],
155
+ }),
156
+ });
157
+ if (!response.ok) {
158
+ throw new Error(`Anthropic API error: ${response.status}`);
159
+ }
160
+ const data = await response.json();
161
+ const content = data.content?.[0]?.text;
162
+ if (!content)
163
+ return null;
164
+ try {
165
+ const jsonMatch = content.match(/\{[\s\S]*\}/);
166
+ if (jsonMatch) {
167
+ return JSON.parse(jsonMatch[0]);
168
+ }
169
+ }
170
+ catch {
171
+ return {
172
+ code: content,
173
+ explanation: 'AI-generated suggestion',
174
+ confidence: 0.5,
175
+ };
176
+ }
177
+ return null;
178
+ }
179
+ /**
180
+ * Estimate the effort required to fix a smell
181
+ */
182
+ function estimateEffort(smellType) {
183
+ const lowEffort = [
184
+ 'debug-statement',
185
+ 'js-var-usage',
186
+ 'js-loose-equality',
187
+ 'a11y-missing-alt',
188
+ 'magic-value',
189
+ 'missing-key',
190
+ ];
191
+ const highEffort = [
192
+ 'prop-drilling',
193
+ 'large-component',
194
+ 'circular-dependency',
195
+ 'high-cyclomatic-complexity',
196
+ 'high-cognitive-complexity',
197
+ 'context-overuse',
198
+ 'state-sync-anti-pattern',
199
+ 'complex-untestable',
200
+ ];
201
+ if (lowEffort.includes(smellType))
202
+ return 'low';
203
+ if (highEffort.includes(smellType))
204
+ return 'high';
205
+ return 'medium';
206
+ }
207
+ /**
208
+ * Generate a summary report with AI insights
209
+ */
210
+ export async function generateAIAnalysisReport(result, config) {
211
+ if (!config.apiKey) {
212
+ return 'AI analysis not available: No API key provided';
213
+ }
214
+ const smellsSummary = Object.entries(result.summary.smellsByType)
215
+ .filter(([, count]) => count > 0)
216
+ .map(([type, count]) => `- ${type}: ${count}`)
217
+ .join('\n');
218
+ const prompt = `Analyze this React codebase analysis result and provide insights:
219
+
220
+ ## Summary
221
+ - Total Files: ${result.summary.totalFiles}
222
+ - Total Components: ${result.summary.totalComponents}
223
+ - Total Smells: ${result.summary.totalSmells}
224
+ - Technical Debt Grade: ${result.debtScore.grade}
225
+ - Debt Score: ${result.debtScore.score}/100
226
+
227
+ ## Smells by Type
228
+ ${smellsSummary}
229
+
230
+ ## Severity Distribution
231
+ - Errors: ${result.summary.smellsBySeverity.error}
232
+ - Warnings: ${result.summary.smellsBySeverity.warning}
233
+ - Info: ${result.summary.smellsBySeverity.info}
234
+
235
+ Provide:
236
+ 1. Key insights about the codebase health
237
+ 2. Top 3 priority areas to address
238
+ 3. Estimated effort to improve the grade
239
+ 4. Actionable recommendations
240
+
241
+ Keep the response concise and actionable.`;
242
+ try {
243
+ const response = await callLLMAPI(prompt, config);
244
+ if (response?.explanation) {
245
+ return response.explanation;
246
+ }
247
+ }
248
+ catch (error) {
249
+ console.error('AI analysis report failed:', error);
250
+ }
251
+ return 'AI analysis could not be generated';
252
+ }
253
+ /**
254
+ * Get refactoring suggestions for specific smell types
255
+ */
256
+ export function getQuickRefactoringTemplates(smellType) {
257
+ const templates = {
258
+ 'useEffect-overuse': [
259
+ 'Consider combining related effects into a single useEffect',
260
+ 'Extract complex effect logic into custom hooks',
261
+ 'Use useReducer for complex state logic instead of multiple effects',
262
+ ],
263
+ 'prop-drilling': [
264
+ 'Use React Context for deeply shared state',
265
+ 'Consider component composition (children pattern)',
266
+ 'Extract a custom hook for related props',
267
+ ],
268
+ 'large-component': [
269
+ 'Extract logical sections into separate components',
270
+ 'Move hooks to custom hook files',
271
+ 'Use component composition patterns',
272
+ ],
273
+ 'context-overuse': [
274
+ 'Split context into smaller, focused contexts',
275
+ 'Use component composition instead of context',
276
+ 'Consider using a state management library for complex needs',
277
+ ],
278
+ 'missing-error-boundary': [
279
+ 'Wrap async components with ErrorBoundary',
280
+ 'Add Suspense wrappers for lazy-loaded components',
281
+ 'Use react-error-boundary library for consistent handling',
282
+ ],
283
+ 'state-sync-anti-pattern': [
284
+ 'Derive values during render instead of syncing in effects',
285
+ 'Use a key prop to reset component state',
286
+ 'Consider useMemo for computed values',
287
+ ],
288
+ };
289
+ return templates[smellType] || ['Review the code smell suggestion for improvement ideas'];
290
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AA+BA,OAAO,EACL,cAAc,EAMd,cAAc,EAIf,MAAM,kBAAkB,CAAC;AAE1B,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;CAClC;AAED,wBAAsB,cAAc,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CA8DtF;AA0QD,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AAiCA,OAAO,EACL,cAAc,EAMd,cAAc,EAIf,MAAM,kBAAkB,CAAC;AAE1B,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;CAClC;AAED,wBAAsB,cAAc,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CA8DtF;AA0SD,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC"}
package/dist/analyzer.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import fg from 'fast-glob';
2
2
  import path from 'path';
3
3
  import { parseFile } from './parser/index.js';
4
- import { detectUseEffectOveruse, detectPropDrilling, analyzePropDrillingDepth, detectLargeComponent, detectUnmemoizedCalculations, detectMissingKeys, detectHooksRulesViolations, detectDependencyArrayIssues, detectNestedTernaries, detectDeadCode, detectMagicValues, detectNextjsIssues, detectReactNativeIssues, detectNodejsIssues, detectJavascriptIssues, detectTypescriptIssues, detectDebugStatements, detectSecurityIssues, detectAccessibilityIssues, detectComplexity, detectMemoryLeaks, detectImportIssues, detectUnusedCode, } from './detectors/index.js';
4
+ import { detectUseEffectOveruse, detectPropDrilling, analyzePropDrillingDepth, detectLargeComponent, detectUnmemoizedCalculations, detectMissingKeys, detectHooksRulesViolations, detectDependencyArrayIssues, detectNestedTernaries, detectDeadCode, detectMagicValues, detectNextjsIssues, detectReactNativeIssues, detectNodejsIssues, detectJavascriptIssues, detectTypescriptIssues, detectDebugStatements, detectSecurityIssues, detectAccessibilityIssues, detectComplexity, detectMemoryLeaks, detectImportIssues, detectUnusedCode, detectServerComponentIssues, detectAsyncComponentIssues, } from './detectors/index.js';
5
5
  import { parseCustomRules, detectCustomRuleViolations } from './customRules.js';
6
6
  import { buildDependencyGraph } from './graphGenerator.js';
7
7
  import { analyzeBundleImpact } from './bundleAnalyzer.js';
@@ -100,6 +100,9 @@ function analyzeFile(parseResult, filePath, config) {
100
100
  smells.push(...detectMemoryLeaks(component, filePath, sourceCode, config));
101
101
  smells.push(...detectImportIssues(component, filePath, sourceCode, config));
102
102
  smells.push(...detectUnusedCode(component, filePath, sourceCode, config));
103
+ // Server Components (React 19)
104
+ smells.push(...detectServerComponentIssues(component, filePath, sourceCode, config, imports));
105
+ smells.push(...detectAsyncComponentIssues(component, filePath, sourceCode, config));
103
106
  // Custom rules
104
107
  const customRules = parseCustomRules(config);
105
108
  if (customRules.length > 0) {
@@ -213,6 +216,35 @@ function calculateSummary(files) {
213
216
  // Unused code
214
217
  'unused-export': 0,
215
218
  'dead-import': 0,
219
+ // Server Components (React 19)
220
+ 'server-component-hooks': 0,
221
+ 'server-component-events': 0,
222
+ 'server-component-browser-api': 0,
223
+ 'async-client-component': 0,
224
+ 'mixed-directives': 0,
225
+ // Context API issues
226
+ 'context-overuse': 0,
227
+ 'context-in-loop': 0,
228
+ 'missing-context-memo': 0,
229
+ 'large-context-value': 0,
230
+ // Error Boundary issues
231
+ 'missing-error-boundary': 0,
232
+ 'error-boundary-missing-fallback': 0,
233
+ 'suspense-missing-fallback': 0,
234
+ // Form validation issues
235
+ 'uncontrolled-form': 0,
236
+ 'missing-form-validation': 0,
237
+ 'form-without-onsubmit': 0,
238
+ 'input-without-label': 0,
239
+ // State management issues
240
+ 'redux-in-render': 0,
241
+ 'excessive-redux-selectors': 0,
242
+ 'state-sync-anti-pattern': 0,
243
+ 'derived-state-in-state': 0,
244
+ // Testing gaps
245
+ 'complex-untestable': 0,
246
+ 'side-effect-heavy': 0,
247
+ 'tightly-coupled': 0,
216
248
  // Custom rules
217
249
  'custom-rule': 0,
218
250
  };