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.
- package/README.md +294 -4
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +29 -1
- package/dist/baseline.d.ts +37 -0
- package/dist/baseline.d.ts.map +1 -0
- package/dist/baseline.js +112 -0
- package/dist/bundleAnalyzer.d.ts +25 -0
- package/dist/bundleAnalyzer.d.ts.map +1 -0
- package/dist/bundleAnalyzer.js +375 -0
- package/dist/cli.js +74 -0
- package/dist/customRules.d.ts +31 -0
- package/dist/customRules.d.ts.map +1 -0
- package/dist/customRules.js +289 -0
- package/dist/detectors/complexity.d.ts +0 -4
- package/dist/detectors/complexity.d.ts.map +1 -1
- package/dist/detectors/complexity.js +1 -1
- package/dist/detectors/deadCode.d.ts +0 -7
- package/dist/detectors/deadCode.d.ts.map +1 -1
- package/dist/detectors/deadCode.js +0 -24
- package/dist/detectors/index.d.ts +3 -2
- package/dist/detectors/index.d.ts.map +1 -1
- package/dist/detectors/index.js +3 -2
- package/dist/detectors/unusedCode.d.ts +7 -0
- package/dist/detectors/unusedCode.d.ts.map +1 -0
- package/dist/detectors/unusedCode.js +78 -0
- package/dist/git.d.ts +3 -0
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +13 -0
- package/dist/graphGenerator.d.ts +34 -0
- package/dist/graphGenerator.d.ts.map +1 -0
- package/dist/graphGenerator.js +320 -0
- package/dist/reporter.js +5 -0
- package/dist/types/index.d.ts +12 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +17 -0
- package/dist/webhooks.d.ts +20 -0
- package/dist/webhooks.d.ts.map +1 -0
- package/dist/webhooks.js +199 -0
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ A CLI tool that analyzes React projects and detects common code smells, providin
|
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
- š **Detect Code Smells**: Identifies common React anti-patterns
|
|
7
|
+
- š **Detect Code Smells**: Identifies common React anti-patterns (51+ smell types)
|
|
8
8
|
- š **Technical Debt Score**: Grades your codebase from A to F
|
|
9
9
|
- š” **Refactoring Suggestions**: Actionable recommendations for each issue
|
|
10
10
|
- š **Multiple Output Formats**: Console (colored), JSON, Markdown, and HTML
|
|
@@ -18,6 +18,12 @@ A CLI tool that analyzes React projects and detects common code smells, providin
|
|
|
18
18
|
- š§® **Complexity Metrics**: Cyclomatic and cognitive complexity scoring
|
|
19
19
|
- š§ **Memory Leak Detection**: Find missing cleanup in useEffect
|
|
20
20
|
- š **Import Analysis**: Detect circular dependencies and barrel file issues
|
|
21
|
+
- šļø **Unused Code Detection**: Find unused exports and dead imports
|
|
22
|
+
- š **Baseline Tracking**: Track code smell trends over time with git commit history
|
|
23
|
+
- š¬ **Chat Notifications**: Send analysis results to Slack, Discord, or custom webhooks
|
|
24
|
+
- š **Dependency Graph Visualization**: Visual SVG/HTML of component and import relationships
|
|
25
|
+
- š¦ **Bundle Size Impact**: Per-component bundle size estimates and optimization suggestions
|
|
26
|
+
- āļø **Custom Rules Engine**: Define project-specific code quality rules in configuration
|
|
21
27
|
|
|
22
28
|
### Detected Code Smells
|
|
23
29
|
|
|
@@ -34,6 +40,8 @@ A CLI tool that analyzes React projects and detects common code smells, providin
|
|
|
34
40
|
| **Memory Leaks** | Missing cleanup for event listeners, timers, subscriptions |
|
|
35
41
|
| **Code Complexity** | Cyclomatic complexity, cognitive complexity, deep nesting |
|
|
36
42
|
| **Import Issues** | Circular dependencies, barrel file imports, excessive imports |
|
|
43
|
+
| **Unused Code** | Unused exports, dead imports |
|
|
44
|
+
| **Custom Rules** | User-defined code quality rules |
|
|
37
45
|
| **Framework-Specific** | Next.js, React Native, Node.js, TypeScript issues |
|
|
38
46
|
|
|
39
47
|
## Installation
|
|
@@ -115,6 +123,15 @@ Or create manually:
|
|
|
115
123
|
| `--include <patterns>` | Glob patterns to include | `**/*.tsx,**/*.jsx` |
|
|
116
124
|
| `--exclude <patterns>` | Glob patterns to exclude | `node_modules,dist` |
|
|
117
125
|
| `-o, --output <file>` | Write output to file | - |
|
|
126
|
+
| `--baseline` | Enable baseline tracking and trend analysis | `false` |
|
|
127
|
+
| `--slack <url>` | Slack webhook URL for notifications | - |
|
|
128
|
+
| `--discord <url>` | Discord webhook URL for notifications | - |
|
|
129
|
+
| `--webhook <url>` | Generic webhook URL for notifications | - |
|
|
130
|
+
| `--webhook-threshold <number>` | Only notify if smells exceed threshold | `10` |
|
|
131
|
+
| `--graph` | Generate dependency graph visualization | `false` |
|
|
132
|
+
| `--graph-format <format>` | Graph output format: svg, html | `html` |
|
|
133
|
+
| `--bundle` | Analyze bundle size impact per component | `false` |
|
|
134
|
+
| `--rules <file>` | Custom rules configuration file | - |
|
|
118
135
|
|
|
119
136
|
### Auto-Fix
|
|
120
137
|
|
|
@@ -150,6 +167,231 @@ Only analyze modified files:
|
|
|
150
167
|
react-smell ./src --changed
|
|
151
168
|
```
|
|
152
169
|
|
|
170
|
+
### Baseline Tracking & Trend Analysis
|
|
171
|
+
|
|
172
|
+
Track code smell trends over time with automatic baseline recording:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
# Enable baseline tracking
|
|
176
|
+
react-smell ./src --baseline
|
|
177
|
+
|
|
178
|
+
# Output includes trend analysis
|
|
179
|
+
š Code Smell Trend Analysis
|
|
180
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
181
|
+
Latest Run: 42 total smells
|
|
182
|
+
Trend: IMPROVING
|
|
183
|
+
Previous Run: 48 smells
|
|
184
|
+
Improved: 6 issues fixed
|
|
185
|
+
Worsened: 0 new issues
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**Features:**
|
|
189
|
+
- Stores history in `.smellrc-baseline.json`
|
|
190
|
+
- Tracks timestamps, commit hashes, and author names
|
|
191
|
+
- Automatic trend calculation (improving/worsening/stable)
|
|
192
|
+
- Keeps last 50 records with automatic cleanup
|
|
193
|
+
- Perfect for CI/CD pipelines to monitor progress
|
|
194
|
+
|
|
195
|
+
**Configuration:**
|
|
196
|
+
```json
|
|
197
|
+
{
|
|
198
|
+
"baselineEnabled": true,
|
|
199
|
+
"baselineThreshold": 5
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Chat Notifications
|
|
204
|
+
|
|
205
|
+
Send analysis results to Slack, Discord, or custom webhooks:
|
|
206
|
+
|
|
207
|
+
#### Slack Integration
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
react-smell ./src --slack https://hooks.slack.com/services/YOUR/WEBHOOK
|
|
211
|
+
|
|
212
|
+
# With threshold (only notify if issues exceed limit)
|
|
213
|
+
react-smell ./src --slack $SLACK_URL --webhook-threshold 20
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
**Features:**
|
|
217
|
+
- Rich formatted messages with severity levels
|
|
218
|
+
- Includes branch, commit hash, and author info
|
|
219
|
+
- Shows top 5 issue types by frequency
|
|
220
|
+
- Color-coded severity (Critical/High/Medium/Low)
|
|
221
|
+
|
|
222
|
+
#### Discord Integration
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
react-smell ./src --discord https://discord.com/api/webhooks/123/abc
|
|
226
|
+
|
|
227
|
+
# With environment variable
|
|
228
|
+
export REACT_SMELL_DISCORD_WEBHOOK=https://...
|
|
229
|
+
react-smell ./src --discord $REACT_SMELL_DISCORD_WEBHOOK
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
**Features:**
|
|
233
|
+
- Embedded messages with visual design
|
|
234
|
+
- Color-coded smells by severity
|
|
235
|
+
- Full metadata included (branch, author, commit)
|
|
236
|
+
|
|
237
|
+
#### Generic Webhooks
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
react-smell ./src --webhook https://example.com/webhook
|
|
241
|
+
|
|
242
|
+
# Custom platform webhooks
|
|
243
|
+
react-smell ./src --webhook $CUSTOM_URL --webhook-threshold 15
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
**Environment Variables:**
|
|
247
|
+
```bash
|
|
248
|
+
export REACT_SMELL_SLACK_WEBHOOK=https://...
|
|
249
|
+
export REACT_SMELL_DISCORD_WEBHOOK=https://...
|
|
250
|
+
export REACT_SMELL_WEBHOOK=https://...
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
**CI/CD Example with Notifications:**
|
|
254
|
+
```bash
|
|
255
|
+
# Run analysis, track baseline, and notify on failures
|
|
256
|
+
react-smell ./src \
|
|
257
|
+
--baseline \
|
|
258
|
+
--slack $SLACK_WEBHOOK \
|
|
259
|
+
--webhook-threshold 10 \
|
|
260
|
+
--ci
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Dependency Graph Visualization
|
|
264
|
+
|
|
265
|
+
Generate interactive dependency graphs showing component and file relationships:
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
# Generate dependency graph
|
|
269
|
+
react-smell ./src --graph
|
|
270
|
+
|
|
271
|
+
# Auto-generates: dependency-graph.html
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
**Features:**
|
|
275
|
+
- Visual representation of file imports and dependencies
|
|
276
|
+
- Circular dependency detection and highlighting
|
|
277
|
+
- Force-directed layout for clarity
|
|
278
|
+
- Exportable SVG or HTML format
|
|
279
|
+
- Legend showing node types and relationships
|
|
280
|
+
|
|
281
|
+
**Example Usage:**
|
|
282
|
+
```bash
|
|
283
|
+
# Generate and analyze all at once
|
|
284
|
+
react-smell ./src --graph --bundle --baseline --ci
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### Bundle Size Impact Analysis
|
|
288
|
+
|
|
289
|
+
Estimate per-component bundle size impact:
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
# Analyze bundle impact
|
|
293
|
+
react-smell ./src --bundle
|
|
294
|
+
|
|
295
|
+
# Auto-generates: bundle-analysis.html
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
**Features:**
|
|
299
|
+
- Estimated size per component (in bytes)
|
|
300
|
+
- Line of code (LOC) analysis
|
|
301
|
+
- Dependency complexity scoring
|
|
302
|
+
- Impact level classification (low/medium/high/critical)
|
|
303
|
+
- Recommendations for optimization
|
|
304
|
+
- Breakdown of largest components
|
|
305
|
+
|
|
306
|
+
**Impact Levels:**
|
|
307
|
+
- š¢ **Low**: <2KB, <150 LOC, low complexity
|
|
308
|
+
- š” **Medium**: 2-5KB, 150-300 LOC, medium complexity
|
|
309
|
+
- š **High**: 5-10KB, 300-500 LOC, high complexity
|
|
310
|
+
- š“ **Critical**: >10KB, >500 LOC, very complex
|
|
311
|
+
|
|
312
|
+
### Custom Rules Engine
|
|
313
|
+
|
|
314
|
+
Define project-specific code quality rules:
|
|
315
|
+
|
|
316
|
+
**Configuration File (.smellrc-rules.json):**
|
|
317
|
+
```json
|
|
318
|
+
{
|
|
319
|
+
"rules": [
|
|
320
|
+
{
|
|
321
|
+
"name": "no-hardcoded-strings",
|
|
322
|
+
"description": "Prevent hardcoded strings (use i18n)",
|
|
323
|
+
"severity": "warning",
|
|
324
|
+
"pattern": "\"(hello|world|test)\"",
|
|
325
|
+
"patternType": "regex",
|
|
326
|
+
"enabled": true
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
"name": "require-display-name",
|
|
330
|
+
"description": "All components must have displayName",
|
|
331
|
+
"severity": "info",
|
|
332
|
+
"pattern": "displayName",
|
|
333
|
+
"patternType": "text",
|
|
334
|
+
"enabled": true
|
|
335
|
+
}
|
|
336
|
+
]
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
**CLI Usage:**
|
|
341
|
+
```bash
|
|
342
|
+
# Use custom rules
|
|
343
|
+
react-smell ./src --rules .smellrc-rules.json
|
|
344
|
+
|
|
345
|
+
# Combined with other features
|
|
346
|
+
react-smell ./src --rules .smellrc-rules.json --format json --ci
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
**Rule Properties:**
|
|
350
|
+
|
|
351
|
+
| Property | Type | Description |
|
|
352
|
+
|----------|------|-------------|
|
|
353
|
+
| `name` | string | Unique rule identifier |
|
|
354
|
+
| `description` | string | Human-readable explanation |
|
|
355
|
+
| `severity` | string | error, warning, or info |
|
|
356
|
+
| `pattern` | string | Regex or text pattern |
|
|
357
|
+
| `patternType` | string | regex, text, or ast |
|
|
358
|
+
| `enabled` | boolean | Enable/disable rule |
|
|
359
|
+
|
|
360
|
+
**Pattern Types:**
|
|
361
|
+
|
|
362
|
+
- **regex**: Regular expression matching (e.g., `"hardcoded.*string"`)
|
|
363
|
+
- **text**: Simple string matching
|
|
364
|
+
- **ast**: Babel AST node type matching (e.g., `FunctionExpression`)
|
|
365
|
+
|
|
366
|
+
**Real-World Examples:**
|
|
367
|
+
|
|
368
|
+
```json
|
|
369
|
+
{
|
|
370
|
+
"rules": [
|
|
371
|
+
{
|
|
372
|
+
"name": "no-console-in-production",
|
|
373
|
+
"pattern": "console\\.(log|warn|info)",
|
|
374
|
+
"patternType": "regex",
|
|
375
|
+
"severity": "error"
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
"name": "enforce-prop-types",
|
|
379
|
+
"pattern": "PropTypes",
|
|
380
|
+
"patternType": "text",
|
|
381
|
+
"severity": "warning",
|
|
382
|
+
"message": "Consider using TypeScript instead of PropTypes"
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
"name": "limit-nesting",
|
|
386
|
+
"description": "Flag deeply nested JSX",
|
|
387
|
+
"pattern": "<.*>.*<.*>.*<.*>.*<.*>.*<",
|
|
388
|
+
"patternType": "regex",
|
|
389
|
+
"severity": "info"
|
|
390
|
+
}
|
|
391
|
+
]
|
|
392
|
+
}
|
|
393
|
+
```
|
|
394
|
+
|
|
153
395
|
## Example Output
|
|
154
396
|
|
|
155
397
|
```
|
|
@@ -180,18 +422,41 @@ react-smell ./src --changed
|
|
|
180
422
|
|
|
181
423
|
```typescript
|
|
182
424
|
import { analyzeProject, reportResults } from 'react-code-smell-detector';
|
|
425
|
+
import { initializeBaseline, recordBaseline, getTrendAnalysis, formatTrendReport } from 'react-code-smell-detector';
|
|
426
|
+
import { sendWebhookNotification, getWebhookConfig } from 'react-code-smell-detector';
|
|
183
427
|
|
|
184
428
|
const result = await analyzeProject({
|
|
185
429
|
rootDir: './src',
|
|
186
430
|
config: {
|
|
187
431
|
maxUseEffectsPerComponent: 3,
|
|
188
432
|
maxComponentLines: 300,
|
|
433
|
+
checkUnusedCode: true,
|
|
434
|
+
baselineEnabled: true,
|
|
189
435
|
},
|
|
190
436
|
});
|
|
191
437
|
|
|
192
438
|
console.log(`Grade: ${result.debtScore.grade}`);
|
|
193
439
|
console.log(`Total issues: ${result.summary.totalSmells}`);
|
|
194
440
|
|
|
441
|
+
// Track baseline
|
|
442
|
+
initializeBaseline('./src');
|
|
443
|
+
recordBaseline('./src', result.files.flatMap(f => f.smells));
|
|
444
|
+
const trend = getTrendAnalysis('./src');
|
|
445
|
+
console.log(formatTrendReport('./src'));
|
|
446
|
+
|
|
447
|
+
// Send notification
|
|
448
|
+
const webhookConfig = getWebhookConfig(
|
|
449
|
+
process.env.REACT_SMELL_SLACK_WEBHOOK,
|
|
450
|
+
process.env.REACT_SMELL_DISCORD_WEBHOOK
|
|
451
|
+
);
|
|
452
|
+
if (webhookConfig) {
|
|
453
|
+
await sendWebhookNotification(
|
|
454
|
+
webhookConfig,
|
|
455
|
+
result.files.flatMap(f => f.smells),
|
|
456
|
+
'my-project'
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
|
|
195
460
|
// Or use the reporter
|
|
196
461
|
const report = reportResults(result, {
|
|
197
462
|
format: 'markdown',
|
|
@@ -202,7 +467,7 @@ const report = reportResults(result, {
|
|
|
202
467
|
|
|
203
468
|
## CI/CD Integration
|
|
204
469
|
|
|
205
|
-
The tool provides flexible exit codes for CI/CD pipelines:
|
|
470
|
+
The tool provides flexible exit codes and notification capabilities for CI/CD pipelines:
|
|
206
471
|
|
|
207
472
|
```bash
|
|
208
473
|
# Fail on any issues (strict mode)
|
|
@@ -213,6 +478,9 @@ react-smell ./src --fail-on warning
|
|
|
213
478
|
|
|
214
479
|
# Generate HTML report and fail on errors
|
|
215
480
|
react-smell ./src -f html -o report.html --fail-on error
|
|
481
|
+
|
|
482
|
+
# Track trends and notify
|
|
483
|
+
react-smell ./src --baseline --slack $SLACK_WEBHOOK --ci
|
|
216
484
|
```
|
|
217
485
|
|
|
218
486
|
### GitHub Actions Example
|
|
@@ -227,6 +495,9 @@ jobs:
|
|
|
227
495
|
runs-on: ubuntu-latest
|
|
228
496
|
steps:
|
|
229
497
|
- uses: actions/checkout@v4
|
|
498
|
+
with:
|
|
499
|
+
fetch-depth: 0 # For git baseline tracking
|
|
500
|
+
|
|
230
501
|
- uses: actions/setup-node@v4
|
|
231
502
|
with:
|
|
232
503
|
node-version: '20'
|
|
@@ -234,8 +505,12 @@ jobs:
|
|
|
234
505
|
- name: Install dependencies
|
|
235
506
|
run: npm ci
|
|
236
507
|
|
|
237
|
-
- name: Check code smells
|
|
238
|
-
run: npx react-smell ./src -f html -o smell-report.html --fail-on warning
|
|
508
|
+
- name: Check code smells with baseline tracking
|
|
509
|
+
run: npx react-smell ./src -f html -o smell-report.html --baseline --fail-on warning
|
|
510
|
+
|
|
511
|
+
- name: Notify Slack on failure
|
|
512
|
+
if: failure()
|
|
513
|
+
run: npx react-smell ./src --slack ${{ secrets.SLACK_WEBHOOK }} --webhook-threshold 10
|
|
239
514
|
|
|
240
515
|
- name: Upload report
|
|
241
516
|
uses: actions/upload-artifact@v4
|
|
@@ -245,6 +520,21 @@ jobs:
|
|
|
245
520
|
path: smell-report.html
|
|
246
521
|
```
|
|
247
522
|
|
|
523
|
+
### GitLab CI Example
|
|
524
|
+
|
|
525
|
+
```yaml
|
|
526
|
+
code-quality:
|
|
527
|
+
image: node:20
|
|
528
|
+
script:
|
|
529
|
+
- npm ci
|
|
530
|
+
- npx react-smell ./src --baseline --fail-on warning
|
|
531
|
+
artifacts:
|
|
532
|
+
reports:
|
|
533
|
+
codequality: code-smell-report.json
|
|
534
|
+
after_script:
|
|
535
|
+
- npx react-smell ./src --discord $DISCORD_WEBHOOK || true
|
|
536
|
+
```
|
|
537
|
+
|
|
248
538
|
## Ignoring Issues
|
|
249
539
|
|
|
250
540
|
Use `@smell-ignore` comments to suppress specific issues:
|
package/dist/analyzer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"
|
|
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"}
|
package/dist/analyzer.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
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, } 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, } from './detectors/index.js';
|
|
5
|
+
import { parseCustomRules, detectCustomRuleViolations } from './customRules.js';
|
|
6
|
+
import { buildDependencyGraph } from './graphGenerator.js';
|
|
7
|
+
import { analyzeBundleImpact } from './bundleAnalyzer.js';
|
|
5
8
|
import { DEFAULT_CONFIG, } from './types/index.js';
|
|
6
9
|
export async function analyzeProject(options) {
|
|
7
10
|
const { rootDir, include = ['**/*.tsx', '**/*.jsx'], exclude = ['**/node_modules/**', '**/dist/**', '**/build/**', '**/*.test.*', '**/*.spec.*'], config: userConfig = {}, } = options;
|
|
@@ -30,6 +33,20 @@ export async function analyzeProject(options) {
|
|
|
30
33
|
// Calculate summary and score
|
|
31
34
|
const summary = calculateSummary(fileAnalyses);
|
|
32
35
|
const debtScore = calculateTechnicalDebtScore(fileAnalyses, summary);
|
|
36
|
+
// Generate dependency graph if requested
|
|
37
|
+
if (config.generateDependencyGraph) {
|
|
38
|
+
const importsData = fileAnalyses.map(f => ({ file: f.file, imports: f.imports }));
|
|
39
|
+
const graph = buildDependencyGraph(importsData, rootDir);
|
|
40
|
+
// Graph is available via extension (not in standard result yet)
|
|
41
|
+
global._dependencyGraph = graph;
|
|
42
|
+
}
|
|
43
|
+
// Analyze bundle size if requested
|
|
44
|
+
if (config.analyzeBundleSize) {
|
|
45
|
+
const allComponents = fileAnalyses.flatMap(f => f.components);
|
|
46
|
+
const bundleAnalysis = analyzeBundleImpact(fileAnalyses.map(f => ({ components: f.components, file: f.file })), fileAnalyses, '');
|
|
47
|
+
// Bundle analysis is available via extension
|
|
48
|
+
global._bundleAnalysis = bundleAnalysis;
|
|
49
|
+
}
|
|
33
50
|
return {
|
|
34
51
|
files: fileAnalyses,
|
|
35
52
|
summary,
|
|
@@ -82,6 +99,12 @@ function analyzeFile(parseResult, filePath, config) {
|
|
|
82
99
|
smells.push(...detectComplexity(component, filePath, sourceCode, config));
|
|
83
100
|
smells.push(...detectMemoryLeaks(component, filePath, sourceCode, config));
|
|
84
101
|
smells.push(...detectImportIssues(component, filePath, sourceCode, config));
|
|
102
|
+
smells.push(...detectUnusedCode(component, filePath, sourceCode, config));
|
|
103
|
+
// Custom rules
|
|
104
|
+
const customRules = parseCustomRules(config);
|
|
105
|
+
if (customRules.length > 0) {
|
|
106
|
+
smells.push(...detectCustomRuleViolations(component, filePath, sourceCode, customRules));
|
|
107
|
+
}
|
|
85
108
|
});
|
|
86
109
|
// Run cross-component analysis
|
|
87
110
|
smells.push(...analyzePropDrillingDepth(components, filePath, sourceCode, config));
|
|
@@ -187,6 +210,11 @@ function calculateSummary(files) {
|
|
|
187
210
|
'barrel-file-import': 0,
|
|
188
211
|
'namespace-import': 0,
|
|
189
212
|
'excessive-imports': 0,
|
|
213
|
+
// Unused code
|
|
214
|
+
'unused-export': 0,
|
|
215
|
+
'dead-import': 0,
|
|
216
|
+
// Custom rules
|
|
217
|
+
'custom-rule': 0,
|
|
190
218
|
};
|
|
191
219
|
const smellsBySeverity = {
|
|
192
220
|
error: 0,
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { CodeSmell } from './types/index.js';
|
|
2
|
+
export interface BaselineRecord {
|
|
3
|
+
timestamp: string;
|
|
4
|
+
commit?: string;
|
|
5
|
+
totalSmells: number;
|
|
6
|
+
byType: Record<string, number>;
|
|
7
|
+
smells: CodeSmell[];
|
|
8
|
+
}
|
|
9
|
+
export interface BaselineData {
|
|
10
|
+
version: '1.0';
|
|
11
|
+
records: BaselineRecord[];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Initialize baseline tracking
|
|
15
|
+
*/
|
|
16
|
+
export declare function initializeBaseline(projectRoot: string): void;
|
|
17
|
+
/**
|
|
18
|
+
* Record current analysis result
|
|
19
|
+
*/
|
|
20
|
+
export declare function recordBaseline(projectRoot: string, smells: CodeSmell[], commit?: string): BaselineRecord;
|
|
21
|
+
/**
|
|
22
|
+
* Get trend analysis compared to previous baseline
|
|
23
|
+
*/
|
|
24
|
+
export declare function getTrendAnalysis(projectRoot: string): {
|
|
25
|
+
improved: number;
|
|
26
|
+
worsened: number;
|
|
27
|
+
trend: 'improving' | 'worsening' | 'stable';
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Get baseline history
|
|
31
|
+
*/
|
|
32
|
+
export declare function getBaselineHistory(projectRoot: string): BaselineRecord[];
|
|
33
|
+
/**
|
|
34
|
+
* Format trend report
|
|
35
|
+
*/
|
|
36
|
+
export declare function formatTrendReport(projectRoot: string): string;
|
|
37
|
+
//# sourceMappingURL=baseline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"baseline.d.ts","sourceRoot":"","sources":["../src/baseline.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE7C,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,MAAM,EAAE,SAAS,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,KAAK,CAAC;IACf,OAAO,EAAE,cAAc,EAAE,CAAC;CAC3B;AAID;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAa5D;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,SAAS,EAAE,EACnB,MAAM,CAAC,EAAE,MAAM,GACd,cAAc,CAgChB;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,MAAM,GAClB;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,WAAW,GAAG,WAAW,GAAG,QAAQ,CAAA;CAAE,CAuBrF;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,cAAc,EAAE,CAQxE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAsB7D"}
|
package/dist/baseline.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
const BASELINE_FILE = '.smellrc-baseline.json';
|
|
4
|
+
/**
|
|
5
|
+
* Initialize baseline tracking
|
|
6
|
+
*/
|
|
7
|
+
export function initializeBaseline(projectRoot) {
|
|
8
|
+
const baselinePath = path.join(projectRoot, BASELINE_FILE);
|
|
9
|
+
try {
|
|
10
|
+
const stats = fs.statSync(baselinePath);
|
|
11
|
+
// File exists
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
// File doesn't exist, create it
|
|
15
|
+
const data = {
|
|
16
|
+
version: '1.0',
|
|
17
|
+
records: [],
|
|
18
|
+
};
|
|
19
|
+
fs.writeFileSync(baselinePath, JSON.stringify(data, null, 2));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Record current analysis result
|
|
24
|
+
*/
|
|
25
|
+
export function recordBaseline(projectRoot, smells, commit) {
|
|
26
|
+
const baselinePath = path.join(projectRoot, BASELINE_FILE);
|
|
27
|
+
let data;
|
|
28
|
+
try {
|
|
29
|
+
const content = fs.readFileSync(baselinePath, 'utf-8');
|
|
30
|
+
data = JSON.parse(content);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
data = { version: '1.0', records: [] };
|
|
34
|
+
}
|
|
35
|
+
const byType = {};
|
|
36
|
+
for (const smell of smells) {
|
|
37
|
+
byType[smell.type] = (byType[smell.type] || 0) + 1;
|
|
38
|
+
}
|
|
39
|
+
const record = {
|
|
40
|
+
timestamp: new Date().toISOString(),
|
|
41
|
+
commit,
|
|
42
|
+
totalSmells: smells.length,
|
|
43
|
+
byType,
|
|
44
|
+
smells: smells.slice(0, 100), // Keep first 100 for history
|
|
45
|
+
};
|
|
46
|
+
data.records.push(record);
|
|
47
|
+
// Keep last 50 records
|
|
48
|
+
if (data.records.length > 50) {
|
|
49
|
+
data.records = data.records.slice(-50);
|
|
50
|
+
}
|
|
51
|
+
fs.writeFileSync(baselinePath, JSON.stringify(data, null, 2));
|
|
52
|
+
return record;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Get trend analysis compared to previous baseline
|
|
56
|
+
*/
|
|
57
|
+
export function getTrendAnalysis(projectRoot) {
|
|
58
|
+
const baselinePath = path.join(projectRoot, BASELINE_FILE);
|
|
59
|
+
if (!fs.existsSync(baselinePath)) {
|
|
60
|
+
return { improved: 0, worsened: 0, trend: 'stable' };
|
|
61
|
+
}
|
|
62
|
+
const data = JSON.parse(fs.readFileSync(baselinePath, 'utf-8'));
|
|
63
|
+
if (data.records.length < 2) {
|
|
64
|
+
return { improved: 0, worsened: 0, trend: 'stable' };
|
|
65
|
+
}
|
|
66
|
+
const current = data.records[data.records.length - 1];
|
|
67
|
+
const previous = data.records[data.records.length - 2];
|
|
68
|
+
const diff = previous.totalSmells - current.totalSmells;
|
|
69
|
+
const improved = Math.max(0, diff);
|
|
70
|
+
const worsened = Math.max(0, -diff);
|
|
71
|
+
let trend = 'stable';
|
|
72
|
+
if (improved > worsened)
|
|
73
|
+
trend = 'improving';
|
|
74
|
+
if (worsened > improved)
|
|
75
|
+
trend = 'worsening';
|
|
76
|
+
return { improved, worsened, trend };
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Get baseline history
|
|
80
|
+
*/
|
|
81
|
+
export function getBaselineHistory(projectRoot) {
|
|
82
|
+
const baselinePath = path.join(projectRoot, BASELINE_FILE);
|
|
83
|
+
try {
|
|
84
|
+
const data = JSON.parse(fs.readFileSync(baselinePath, 'utf-8'));
|
|
85
|
+
return data.records;
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Format trend report
|
|
93
|
+
*/
|
|
94
|
+
export function formatTrendReport(projectRoot) {
|
|
95
|
+
const trend = getTrendAnalysis(projectRoot);
|
|
96
|
+
const history = getBaselineHistory(projectRoot);
|
|
97
|
+
if (history.length === 0) {
|
|
98
|
+
return 'No baseline history available yet.';
|
|
99
|
+
}
|
|
100
|
+
const latest = history[history.length - 1];
|
|
101
|
+
let report = `\nš Code Smell Trend Analysis\n`;
|
|
102
|
+
report += `āāāāāāāāāāāāāāāāāāāāāāāāāā\n`;
|
|
103
|
+
report += `Latest Run: ${latest.totalSmells} total smells\n`;
|
|
104
|
+
report += `Trend: ${trend.trend.toUpperCase()}\n`;
|
|
105
|
+
if (history.length >= 2) {
|
|
106
|
+
const previous = history[history.length - 2];
|
|
107
|
+
report += `Previous Run: ${previous.totalSmells} smells\n`;
|
|
108
|
+
report += `Improved: ${trend.improved} issues fixed\n`;
|
|
109
|
+
report += `Worsened: ${trend.worsened} new issues\n`;
|
|
110
|
+
}
|
|
111
|
+
return report;
|
|
112
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface ComponentBundleInfo {
|
|
2
|
+
name: string;
|
|
3
|
+
file: string;
|
|
4
|
+
estimatedSize: number;
|
|
5
|
+
dependencies: number;
|
|
6
|
+
exports: number;
|
|
7
|
+
complexity: number;
|
|
8
|
+
impact: 'low' | 'medium' | 'high' | 'critical';
|
|
9
|
+
}
|
|
10
|
+
export interface BundleAnalysisResult {
|
|
11
|
+
components: ComponentBundleInfo[];
|
|
12
|
+
totalEstimatedSize: number;
|
|
13
|
+
averageComponentSize: number;
|
|
14
|
+
largestComponents: ComponentBundleInfo[];
|
|
15
|
+
recommendations: string[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Estimate bundle size impact per component
|
|
19
|
+
*/
|
|
20
|
+
export declare function analyzeBundleImpact(components: any[], files: any[], sourceCode: string): BundleAnalysisResult;
|
|
21
|
+
/**
|
|
22
|
+
* Generate HTML bundle analysis report
|
|
23
|
+
*/
|
|
24
|
+
export declare function generateBundleReport(analysis: BundleAnalysisResult, projectName: string): string;
|
|
25
|
+
//# sourceMappingURL=bundleAnalyzer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundleAnalyzer.d.ts","sourceRoot":"","sources":["../src/bundleAnalyzer.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,CAAC;CAChD;AAED,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,mBAAmB,EAAE,CAAC;IAClC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,iBAAiB,EAAE,mBAAmB,EAAE,CAAC;IACzC,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,GAAG,EAAE,EACjB,KAAK,EAAE,GAAG,EAAE,EACZ,UAAU,EAAE,MAAM,GACjB,oBAAoB,CAuBtB;AAuJD;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,oBAAoB,EAC9B,WAAW,EAAE,MAAM,GAClB,MAAM,CAmOR"}
|