react-code-smell-detector 1.3.1 → 1.4.2

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 (39) hide show
  1. package/README.md +294 -4
  2. package/dist/analyzer.d.ts.map +1 -1
  3. package/dist/analyzer.js +29 -1
  4. package/dist/baseline.d.ts +37 -0
  5. package/dist/baseline.d.ts.map +1 -0
  6. package/dist/baseline.js +112 -0
  7. package/dist/bundleAnalyzer.d.ts +25 -0
  8. package/dist/bundleAnalyzer.d.ts.map +1 -0
  9. package/dist/bundleAnalyzer.js +375 -0
  10. package/dist/cli.js +74 -0
  11. package/dist/customRules.d.ts +31 -0
  12. package/dist/customRules.d.ts.map +1 -0
  13. package/dist/customRules.js +289 -0
  14. package/dist/detectors/complexity.d.ts +0 -4
  15. package/dist/detectors/complexity.d.ts.map +1 -1
  16. package/dist/detectors/complexity.js +1 -1
  17. package/dist/detectors/deadCode.d.ts +0 -7
  18. package/dist/detectors/deadCode.d.ts.map +1 -1
  19. package/dist/detectors/deadCode.js +0 -24
  20. package/dist/detectors/index.d.ts +3 -2
  21. package/dist/detectors/index.d.ts.map +1 -1
  22. package/dist/detectors/index.js +3 -2
  23. package/dist/detectors/unusedCode.d.ts +7 -0
  24. package/dist/detectors/unusedCode.d.ts.map +1 -0
  25. package/dist/detectors/unusedCode.js +78 -0
  26. package/dist/git.d.ts +3 -0
  27. package/dist/git.d.ts.map +1 -1
  28. package/dist/git.js +13 -0
  29. package/dist/graphGenerator.d.ts +34 -0
  30. package/dist/graphGenerator.d.ts.map +1 -0
  31. package/dist/graphGenerator.js +320 -0
  32. package/dist/reporter.js +5 -0
  33. package/dist/types/index.d.ts +12 -1
  34. package/dist/types/index.d.ts.map +1 -1
  35. package/dist/types/index.js +17 -0
  36. package/dist/webhooks.d.ts +20 -0
  37. package/dist/webhooks.d.ts.map +1 -0
  38. package/dist/webhooks.js +199 -0
  39. package/package.json +3 -1
@@ -0,0 +1,320 @@
1
+ import _traverse from '@babel/traverse';
2
+ import path from 'path';
3
+ const traverse = typeof _traverse === 'function' ? _traverse : _traverse.default;
4
+ /**
5
+ * Build dependency graph from parsed components
6
+ */
7
+ export function buildDependencyGraph(files, rootDir) {
8
+ const nodes = new Map();
9
+ const edges = [];
10
+ // Create nodes
11
+ for (const file of files) {
12
+ const id = normalizeId(file.file, rootDir);
13
+ nodes.set(id, {
14
+ id,
15
+ file: file.file,
16
+ type: 'file',
17
+ imports: file.imports.map(imp => normalizeId(imp, rootDir)),
18
+ importedBy: [],
19
+ });
20
+ }
21
+ // Build edges
22
+ for (const file of files) {
23
+ const fromId = normalizeId(file.file, rootDir);
24
+ for (const imp of file.imports) {
25
+ const toId = normalizeId(imp, rootDir);
26
+ if (nodes.has(toId)) {
27
+ const toNode = nodes.get(toId);
28
+ toNode.importedBy.push(fromId);
29
+ edges.push({ from: fromId, to: toId, circular: false });
30
+ }
31
+ }
32
+ }
33
+ // Detect circular dependencies
34
+ const circularDependencies = detectCircularDeps(nodes);
35
+ for (const cycle of circularDependencies) {
36
+ for (let i = 0; i < cycle.length; i++) {
37
+ const from = cycle[i];
38
+ const to = cycle[(i + 1) % cycle.length];
39
+ const edge = edges.find(e => e.from === from && e.to === to);
40
+ if (edge)
41
+ edge.circular = true;
42
+ const node = nodes.get(from);
43
+ if (node)
44
+ node.isCircular = true;
45
+ }
46
+ }
47
+ return { nodes, edges, circularDependencies };
48
+ }
49
+ /**
50
+ * Generate SVG representation of dependency graph
51
+ */
52
+ export function generateDependencyGraph(graph, width = 1200, height = 800) {
53
+ const nodeArray = Array.from(graph.nodes.values());
54
+ // Simple force-directed layout
55
+ const positions = calculateNodePositions(nodeArray, graph.edges, width, height);
56
+ let svg = `<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">\n`;
57
+ svg += `<defs>\n`;
58
+ svg += `<marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">\n`;
59
+ svg += `<polygon points="0 0, 10 3.5, 0 7" fill="#666" />\n`;
60
+ svg += `</marker>\n`;
61
+ svg += `<marker id="arrowhead-circular" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">\n`;
62
+ svg += `<polygon points="0 0, 10 3.5, 0 7" fill="#d32f2f" />\n`;
63
+ svg += `</marker>\n`;
64
+ svg += `</defs>\n`;
65
+ // Draw edges
66
+ for (const edge of graph.edges) {
67
+ const fromPos = positions.get(edge.from);
68
+ const toPos = positions.get(edge.to);
69
+ if (fromPos && toPos) {
70
+ const color = edge.circular ? '#d32f2f' : '#999';
71
+ const marker = edge.circular ? 'url(#arrowhead-circular)' : 'url(#arrowhead)';
72
+ svg += `<line x1="${fromPos.x}" y1="${fromPos.y}" x2="${toPos.x}" y2="${toPos.y}" stroke="${color}" stroke-width="${edge.circular ? 2 : 1}" marker-end="${marker}" opacity="${edge.circular ? 1 : 0.6}" />\n`;
73
+ }
74
+ }
75
+ // Draw nodes
76
+ for (const node of nodeArray) {
77
+ const pos = positions.get(node.id);
78
+ if (pos) {
79
+ const fillColor = node.isCircular ? '#d32f2f' : '#1976d2';
80
+ const r = 25;
81
+ svg += `<circle cx="${pos.x}" cy="${pos.y}" r="${r}" fill="${fillColor}" opacity="0.8" />\n`;
82
+ // Node label
83
+ const label = path.basename(node.file, path.extname(node.file));
84
+ svg += `<text x="${pos.x}" y="${pos.y}" text-anchor="middle" dy="0.3em" fill="white" font-size="11" font-weight="bold">\n`;
85
+ svg += label.length > 12 ? label.substring(0, 10) + '..' : label;
86
+ svg += `</text>\n`;
87
+ // Circular indicator
88
+ if (node.isCircular) {
89
+ svg += `<circle cx="${pos.x}" cy="${pos.y}" r="${r + 3}" fill="none" stroke="#d32f2f" stroke-width="2" stroke-dasharray="5,5" />\n`;
90
+ }
91
+ }
92
+ }
93
+ // Legend
94
+ svg += `<g transform="translate(20, 20)">\n`;
95
+ svg += `<rect x="0" y="0" width="200" height="100" fill="white" stroke="#ccc" rx="4" />\n`;
96
+ svg += `<text x="10" y="20" font-weight="bold" font-size="12">Legend</text>\n`;
97
+ svg += `<circle cx="20" cy="40" r="6" fill="#1976d2" />\n`;
98
+ svg += `<text x="35" y="45" font-size="11">Regular File</text>\n`;
99
+ svg += `<circle cx="20" cy="65" r="6" fill="#d32f2f" />\n`;
100
+ svg += `<text x="35" y="70" font-size="11">Circular Dependency</text>\n`;
101
+ svg += `</g>\n`;
102
+ svg += `</svg>`;
103
+ return svg;
104
+ }
105
+ /**
106
+ * Generate HTML report with dependency graph
107
+ */
108
+ export function generateDependencyGraphHTML(graph, projectName, circularCount = 0) {
109
+ const svgContent = generateDependencyGraph(graph);
110
+ return `<!DOCTYPE html>
111
+ <html lang="en">
112
+ <head>
113
+ <meta charset="UTF-8">
114
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
115
+ <title>Dependency Graph - ${projectName}</title>
116
+ <style>
117
+ body {
118
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
119
+ background: #f5f5f5;
120
+ margin: 0;
121
+ padding: 20px;
122
+ }
123
+ .container {
124
+ max-width: 1400px;
125
+ margin: 0 auto;
126
+ background: white;
127
+ border-radius: 8px;
128
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
129
+ padding: 30px;
130
+ }
131
+ h1 {
132
+ color: #1976d2;
133
+ margin: 0 0 10px 0;
134
+ }
135
+ .stats {
136
+ display: grid;
137
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
138
+ gap: 20px;
139
+ margin: 20px 0 30px 0;
140
+ }
141
+ .stat-card {
142
+ background: #f8f9fa;
143
+ border-left: 4px solid #1976d2;
144
+ padding: 15px;
145
+ border-radius: 4px;
146
+ }
147
+ .stat-card.warning {
148
+ border-left-color: #d32f2f;
149
+ }
150
+ .stat-label {
151
+ font-size: 12px;
152
+ color: #666;
153
+ text-transform: uppercase;
154
+ }
155
+ .stat-value {
156
+ font-size: 28px;
157
+ font-weight: bold;
158
+ color: #1976d2;
159
+ }
160
+ .stat-card.warning .stat-value {
161
+ color: #d32f2f;
162
+ }
163
+ .graph-container {
164
+ border: 1px solid #e0e0e0;
165
+ border-radius: 4px;
166
+ overflow: auto;
167
+ background: #fafafa;
168
+ margin: 20px 0;
169
+ }
170
+ svg {
171
+ display: block;
172
+ margin: 0 auto;
173
+ }
174
+ .circular-list {
175
+ background: #ffebee;
176
+ border-left: 4px solid #d32f2f;
177
+ padding: 15px;
178
+ border-radius: 4px;
179
+ margin-top: 20px;
180
+ }
181
+ .circular-list h3 {
182
+ margin: 0 0 10px 0;
183
+ color: #d32f2f;
184
+ }
185
+ .cycle {
186
+ background: white;
187
+ padding: 8px 12px;
188
+ margin: 5px 0;
189
+ border-radius: 3px;
190
+ font-family: 'Courier New', monospace;
191
+ font-size: 12px;
192
+ }
193
+ </style>
194
+ </head>
195
+ <body>
196
+ <div class="container">
197
+ <h1>📊 Dependency Graph - ${projectName}</h1>
198
+ <p style="color: #666; margin-top: 5px;">Visual representation of file and component dependencies</p>
199
+
200
+ <div class="stats">
201
+ <div class="stat-card">
202
+ <div class="stat-label">Total Files</div>
203
+ <div class="stat-value">${graph.nodes.size}</div>
204
+ </div>
205
+ <div class="stat-card">
206
+ <div class="stat-label">Total Dependencies</div>
207
+ <div class="stat-value">${graph.edges.length}</div>
208
+ </div>
209
+ <div class="stat-card ${circularCount > 0 ? 'warning' : ''}">
210
+ <div class="stat-label">Circular Dependencies</div>
211
+ <div class="stat-value">${circularCount}</div>
212
+ </div>
213
+ </div>
214
+
215
+ <div class="graph-container">
216
+ ${svgContent}
217
+ </div>
218
+
219
+ ${circularCount > 0
220
+ ? `
221
+ <div class="circular-list">
222
+ <h3>⚠️ Circular Dependencies Detected</h3>
223
+ ${graph.circularDependencies.map(cycle => `<div class="cycle">${cycle.join(' → ')} → ${cycle[0]}</div>`).join('')}
224
+ </div>
225
+ `
226
+ : ''}
227
+ </div>
228
+ </body>
229
+ </html>`;
230
+ }
231
+ /**
232
+ * Calculate node positions using simple spring layout
233
+ */
234
+ function calculateNodePositions(nodes, edges, width, height) {
235
+ const positions = new Map();
236
+ // Initial random positions
237
+ for (const node of nodes) {
238
+ positions.set(node.id, {
239
+ x: Math.random() * width * 0.8 + width * 0.1,
240
+ y: Math.random() * height * 0.8 + height * 0.1,
241
+ });
242
+ }
243
+ // Simple spring simulation (few iterations)
244
+ const iterations = 50;
245
+ for (let iter = 0; iter < iterations; iter++) {
246
+ for (const node of nodes) {
247
+ let fx = 0;
248
+ let fy = 0;
249
+ const pos = positions.get(node.id);
250
+ // Repulsion from other nodes
251
+ for (const other of nodes) {
252
+ if (node.id === other.id)
253
+ continue;
254
+ const otherPos = positions.get(other.id);
255
+ const dx = pos.x - otherPos.x;
256
+ const dy = pos.y - otherPos.y;
257
+ const dist = Math.sqrt(dx * dx + dy * dy) || 1;
258
+ const force = 50 / dist;
259
+ fx += (dx / dist) * force;
260
+ fy += (dy / dist) * force;
261
+ }
262
+ // Attraction to connected nodes
263
+ for (const edge of edges) {
264
+ if (edge.from === node.id) {
265
+ const targetPos = positions.get(edge.to);
266
+ if (targetPos) {
267
+ const dx = targetPos.x - pos.x;
268
+ const dy = targetPos.y - pos.y;
269
+ const dist = Math.sqrt(dx * dx + dy * dy) || 1;
270
+ fx += (dx / dist) * 0.1;
271
+ fy += (dy / dist) * 0.1;
272
+ }
273
+ }
274
+ }
275
+ // Update position
276
+ pos.x += fx * 0.1;
277
+ pos.y += fy * 0.1;
278
+ pos.x = Math.max(30, Math.min(width - 30, pos.x));
279
+ pos.y = Math.max(30, Math.min(height - 30, pos.y));
280
+ }
281
+ }
282
+ return positions;
283
+ }
284
+ /**
285
+ * Detect circular dependencies using DFS
286
+ */
287
+ function detectCircularDeps(nodes) {
288
+ const cycles = [];
289
+ const visited = new Set();
290
+ const recursionStack = new Set();
291
+ const path = [];
292
+ function dfs(nodeId) {
293
+ visited.add(nodeId);
294
+ recursionStack.add(nodeId);
295
+ path.push(nodeId);
296
+ const node = nodes.get(nodeId);
297
+ if (node) {
298
+ for (const impId of node.imports) {
299
+ if (!visited.has(impId)) {
300
+ dfs(impId);
301
+ }
302
+ else if (recursionStack.has(impId)) {
303
+ const cycleStart = path.indexOf(impId);
304
+ cycles.push(path.slice(cycleStart));
305
+ }
306
+ }
307
+ }
308
+ recursionStack.delete(nodeId);
309
+ path.pop();
310
+ }
311
+ for (const nodeId of nodes.keys()) {
312
+ if (!visited.has(nodeId)) {
313
+ dfs(nodeId);
314
+ }
315
+ }
316
+ return cycles;
317
+ }
318
+ function normalizeId(filePath, rootDir) {
319
+ return path.relative(rootDir, filePath);
320
+ }
package/dist/reporter.js CHANGED
@@ -268,6 +268,11 @@ function formatSmellType(type) {
268
268
  'barrel-file-import': '📦 Barrel File Import',
269
269
  'namespace-import': '📦 Namespace Import',
270
270
  'excessive-imports': '📦 Excessive Imports',
271
+ // Unused Code
272
+ 'unused-export': '🗑️ Unused Export',
273
+ 'dead-import': '🗑️ Dead Import',
274
+ // Custom rules
275
+ 'custom-rule': '⚙️ Custom Rule',
271
276
  };
272
277
  return labels[type] || type;
273
278
  }
@@ -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' | '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' | 'high-cyclomatic-complexity' | 'high-cognitive-complexity' | 'memory-leak-event-listener' | 'memory-leak-subscription' | 'memory-leak-timer' | 'memory-leak-async' | 'circular-dependency' | 'barrel-file-import' | 'namespace-import' | 'excessive-imports';
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' | 'high-cyclomatic-complexity' | 'high-cognitive-complexity' | 'memory-leak-event-listener' | 'memory-leak-subscription' | 'memory-leak-timer' | 'memory-leak-async' | 'circular-dependency' | 'barrel-file-import' | 'namespace-import' | 'excessive-imports' | 'unused-export' | 'dead-import' | 'custom-rule';
3
3
  export interface CodeSmell {
4
4
  type: SmellType;
5
5
  severity: SmellSeverity;
@@ -81,6 +81,17 @@ export interface DetectorConfig {
81
81
  maxNestingDepth: number;
82
82
  checkMemoryLeaks: boolean;
83
83
  checkImports: boolean;
84
+ checkUnusedCode: boolean;
85
+ baselineEnabled: boolean;
86
+ baselineThreshold?: number;
87
+ webhookUrl?: string;
88
+ webhookType?: 'slack' | 'discord' | 'generic';
89
+ webhookThreshold?: number;
90
+ generateDependencyGraph?: boolean;
91
+ graphOutputFormat?: 'svg' | 'html';
92
+ analyzeBundleSize?: boolean;
93
+ maxComponentSize?: number;
94
+ customRules?: any[];
84
95
  }
85
96
  export declare const DEFAULT_CONFIG: DetectorConfig;
86
97
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
@@ -31,4 +31,21 @@ export const DEFAULT_CONFIG = {
31
31
  checkMemoryLeaks: true,
32
32
  // Import analysis
33
33
  checkImports: true,
34
+ // Unused code analysis
35
+ checkUnusedCode: true,
36
+ // Baseline tracking
37
+ baselineEnabled: true,
38
+ baselineThreshold: 5,
39
+ // Webhook notifications
40
+ webhookUrl: undefined,
41
+ webhookType: 'slack',
42
+ webhookThreshold: 10,
43
+ // Graph visualization
44
+ generateDependencyGraph: false,
45
+ graphOutputFormat: 'html',
46
+ // Bundle analysis
47
+ analyzeBundleSize: false,
48
+ maxComponentSize: 10000,
49
+ // Custom rules
50
+ customRules: undefined,
34
51
  };
@@ -0,0 +1,20 @@
1
+ import { CodeSmell } from './types/index.js';
2
+ export interface WebhookConfig {
3
+ url: string;
4
+ type: 'slack' | 'discord' | 'generic';
5
+ threshold?: number;
6
+ includeDetails?: boolean;
7
+ }
8
+ /**
9
+ * Send analysis results to chat platform via webhook
10
+ */
11
+ export declare function sendWebhookNotification(whConfig: WebhookConfig, smells: CodeSmell[], projectName: string, metadata?: {
12
+ branch?: string;
13
+ commit?: string;
14
+ author?: string;
15
+ }): Promise<boolean>;
16
+ /**
17
+ * Parse webhook URL from environment or config
18
+ */
19
+ export declare function getWebhookConfig(slackUrl?: string, discordUrl?: string, genericUrl?: string): WebhookConfig | null;
20
+ //# sourceMappingURL=webhooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhooks.d.ts","sourceRoot":"","sources":["../src/webhooks.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE7C,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,OAAO,GAAG,SAAS,GAAG,SAAS,CAAC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,aAAa,EACvB,MAAM,EAAE,SAAS,EAAE,EACnB,WAAW,EAAE,MAAM,EACnB,QAAQ,CAAC,EAAE;IACT,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GACA,OAAO,CAAC,OAAO,CAAC,CAqBlB;AAmLD;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,CAAC,EAAE,MAAM,EACjB,UAAU,CAAC,EAAE,MAAM,EACnB,UAAU,CAAC,EAAE,MAAM,GAClB,aAAa,GAAG,IAAI,CAoBtB"}
@@ -0,0 +1,199 @@
1
+ import https from 'https';
2
+ /**
3
+ * Send analysis results to chat platform via webhook
4
+ */
5
+ export async function sendWebhookNotification(whConfig, smells, projectName, metadata) {
6
+ if (!whConfig.url)
7
+ return false;
8
+ // Check threshold
9
+ if (whConfig.threshold && smells.length < whConfig.threshold) {
10
+ return false;
11
+ }
12
+ try {
13
+ const payload = whConfig.type === 'slack'
14
+ ? formatSlackMessage(smells, projectName, metadata, whConfig.includeDetails)
15
+ : whConfig.type === 'discord'
16
+ ? formatDiscordMessage(smells, projectName, metadata, whConfig.includeDetails)
17
+ : formatGenericMessage(smells, projectName, metadata);
18
+ return await postToWebhook(whConfig.url, payload);
19
+ }
20
+ catch (error) {
21
+ console.error('Webhook notification failed:', error);
22
+ return false;
23
+ }
24
+ }
25
+ /**
26
+ * Post JSON to a webhook URL
27
+ */
28
+ function postToWebhook(url, payload) {
29
+ return new Promise((resolve) => {
30
+ try {
31
+ const data = JSON.stringify(payload);
32
+ const urlObj = new URL(url);
33
+ const options = {
34
+ hostname: urlObj.hostname,
35
+ path: urlObj.pathname + urlObj.search,
36
+ method: 'POST',
37
+ headers: {
38
+ 'Content-Type': 'application/json',
39
+ 'Content-Length': Buffer.byteLength(data),
40
+ },
41
+ };
42
+ const req = https.request(options, (res) => {
43
+ resolve(res.statusCode ? res.statusCode >= 200 && res.statusCode < 300 : false);
44
+ });
45
+ req.on('error', () => resolve(false));
46
+ req.write(data);
47
+ req.end();
48
+ }
49
+ catch (error) {
50
+ resolve(false);
51
+ }
52
+ });
53
+ }
54
+ function formatSlackMessage(smells, projectName, metadata, includeDetails = false) {
55
+ const smellSummary = summarizeSmells(smells);
56
+ const color = smells.length > 20 ? 'danger' : smells.length > 10 ? 'warning' : 'good';
57
+ const fields = [
58
+ {
59
+ title: 'Total Smells',
60
+ value: smells.length.toString(),
61
+ short: true,
62
+ },
63
+ {
64
+ title: 'Severity',
65
+ value: getSeverityEmoji(smells.length),
66
+ short: true,
67
+ },
68
+ ];
69
+ if (metadata?.branch) {
70
+ fields.push({
71
+ title: 'Branch',
72
+ value: metadata.branch,
73
+ short: true,
74
+ });
75
+ }
76
+ if (metadata?.commit) {
77
+ fields.push({
78
+ title: 'Commit',
79
+ value: metadata.commit.slice(0, 7),
80
+ short: true,
81
+ });
82
+ }
83
+ // Add top issues
84
+ const topTypes = Object.entries(smellSummary)
85
+ .sort(([, a], [, b]) => b - a)
86
+ .slice(0, 5);
87
+ if (topTypes.length > 0) {
88
+ fields.push({
89
+ title: 'Top Issues',
90
+ value: topTypes.map(([type, count]) => `• ${type}: ${count}`).join('\n'),
91
+ short: false,
92
+ });
93
+ }
94
+ return {
95
+ text: `Code Smell Analysis: ${projectName}`,
96
+ attachments: [
97
+ {
98
+ fallback: `${smells.length} code smells detected`,
99
+ color,
100
+ title: `${smells.length} Code Smells Detected`,
101
+ fields,
102
+ footer: 'React Code Smell Detector',
103
+ ts: Math.floor(Date.now() / 1000),
104
+ },
105
+ ],
106
+ };
107
+ }
108
+ function formatDiscordMessage(smells, projectName, metadata, includeDetails = false) {
109
+ const smellSummary = summarizeSmells(smells);
110
+ const color = smells.length > 20 ? 15671935 : smells.length > 10 ? 16776960 : 65280; // Red, Yellow, Green
111
+ const topTypes = Object.entries(smellSummary)
112
+ .sort(([, a], [, b]) => b - a)
113
+ .slice(0, 5);
114
+ let description = `**Total Smells:** ${smells.length}\n`;
115
+ if (metadata?.branch) {
116
+ description += `**Branch:** ${metadata.branch}\n`;
117
+ }
118
+ if (metadata?.author) {
119
+ description += `**Author:** ${metadata.author}\n`;
120
+ }
121
+ if (topTypes.length > 0) {
122
+ description += '\n**Top Issues:**\n';
123
+ description += topTypes.map(([type, count]) => `• ${type}: ${count}`).join('\n');
124
+ }
125
+ return {
126
+ content: `Code Smell Analysis for ${projectName}`,
127
+ embeds: [
128
+ {
129
+ title: `${smells.length} Code Smells Detected`,
130
+ description,
131
+ color,
132
+ footer: {
133
+ text: 'React Code Smell Detector',
134
+ },
135
+ timestamp: new Date().toISOString(),
136
+ },
137
+ ],
138
+ };
139
+ }
140
+ function formatGenericMessage(smells, projectName, metadata) {
141
+ const smellSummary = summarizeSmells(smells);
142
+ return {
143
+ project: projectName,
144
+ totalSmells: smells.length,
145
+ timestamp: new Date().toISOString(),
146
+ metadata,
147
+ summary: smellSummary,
148
+ severity: getSeverityLevel(smells.length),
149
+ };
150
+ }
151
+ function summarizeSmells(smells) {
152
+ const summary = {};
153
+ for (const smell of smells) {
154
+ summary[smell.type] = (summary[smell.type] || 0) + 1;
155
+ }
156
+ return summary;
157
+ }
158
+ function getSeverityEmoji(count) {
159
+ if (count > 50)
160
+ return '🔴 Critical';
161
+ if (count > 20)
162
+ return '🟠 High';
163
+ if (count > 10)
164
+ return '🟡 Medium';
165
+ if (count > 0)
166
+ return '🟢 Low';
167
+ return '✅ Excellent';
168
+ }
169
+ function getSeverityLevel(count) {
170
+ if (count > 50)
171
+ return 'critical';
172
+ if (count > 20)
173
+ return 'high';
174
+ if (count > 10)
175
+ return 'medium';
176
+ if (count > 0)
177
+ return 'low';
178
+ return 'excellent';
179
+ }
180
+ /**
181
+ * Parse webhook URL from environment or config
182
+ */
183
+ export function getWebhookConfig(slackUrl, discordUrl, genericUrl) {
184
+ const slack = slackUrl || process.env.REACT_SMELL_SLACK_WEBHOOK || process.env.SLACK_WEBHOOK_URL;
185
+ const discord = discordUrl ||
186
+ process.env.REACT_SMELL_DISCORD_WEBHOOK ||
187
+ process.env.DISCORD_WEBHOOK_URL;
188
+ const generic = genericUrl || process.env.REACT_SMELL_WEBHOOK;
189
+ if (slack) {
190
+ return { url: slack, type: 'slack' };
191
+ }
192
+ if (discord) {
193
+ return { url: discord, type: 'discord' };
194
+ }
195
+ if (generic) {
196
+ return { url: generic, type: 'generic' };
197
+ }
198
+ return null;
199
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-code-smell-detector",
3
- "version": "1.3.1",
3
+ "version": "1.4.2",
4
4
  "description": "Detect code smells in React projects - useEffect overuse, prop drilling, large components, security issues, accessibility, memory leaks, and more",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -37,6 +37,8 @@
37
37
  "chokidar": "^5.0.0",
38
38
  "commander": "^11.1.0",
39
39
  "fast-glob": "^3.3.2",
40
+ "fs-extra": "^11.3.3",
41
+ "node-fetch": "^3.3.2",
40
42
  "ora": "^8.0.1"
41
43
  },
42
44
  "devDependencies": {