react-code-smell-detector 1.4.2 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +207 -22
- package/dist/__tests__/parser.test.d.ts +2 -0
- package/dist/__tests__/parser.test.d.ts.map +1 -0
- package/dist/__tests__/parser.test.js +56 -0
- package/dist/__tests__/performanceBudget.test.d.ts +2 -0
- package/dist/__tests__/performanceBudget.test.d.ts.map +1 -0
- package/dist/__tests__/performanceBudget.test.js +91 -0
- package/dist/__tests__/prComments.test.d.ts +2 -0
- package/dist/__tests__/prComments.test.d.ts.map +1 -0
- package/dist/__tests__/prComments.test.js +118 -0
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +10 -1
- package/dist/cli.js +106 -1
- package/dist/detectors/index.d.ts +1 -0
- package/dist/detectors/index.d.ts.map +1 -1
- package/dist/detectors/index.js +2 -0
- package/dist/detectors/serverComponents.d.ts +11 -0
- package/dist/detectors/serverComponents.d.ts.map +1 -0
- package/dist/detectors/serverComponents.js +222 -0
- package/dist/docGenerator.d.ts +37 -0
- package/dist/docGenerator.d.ts.map +1 -0
- package/dist/docGenerator.js +306 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/interactiveFixer.d.ts +20 -0
- package/dist/interactiveFixer.d.ts.map +1 -0
- package/dist/interactiveFixer.js +178 -0
- package/dist/performanceBudget.d.ts +54 -0
- package/dist/performanceBudget.d.ts.map +1 -0
- package/dist/performanceBudget.js +218 -0
- package/dist/prComments.d.ts +47 -0
- package/dist/prComments.d.ts.map +1 -0
- package/dist/prComments.js +233 -0
- package/dist/types/index.d.ts +2 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -0
- package/package.json +10 -4
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
const DEFAULT_BUDGET = {
|
|
5
|
+
maxErrors: 0,
|
|
6
|
+
minGrade: 'C',
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Load performance budget from config file
|
|
10
|
+
*/
|
|
11
|
+
export async function loadBudget(configPath) {
|
|
12
|
+
const paths = configPath
|
|
13
|
+
? [configPath]
|
|
14
|
+
: [
|
|
15
|
+
'.smellbudget.json',
|
|
16
|
+
'.smellrc.json',
|
|
17
|
+
'package.json',
|
|
18
|
+
];
|
|
19
|
+
for (const p of paths) {
|
|
20
|
+
try {
|
|
21
|
+
const fullPath = path.resolve(process.cwd(), p);
|
|
22
|
+
const content = await fs.readFile(fullPath, 'utf-8');
|
|
23
|
+
const parsed = JSON.parse(content);
|
|
24
|
+
// In package.json, look for "smellBudget" key
|
|
25
|
+
if (p === 'package.json') {
|
|
26
|
+
if (parsed.smellBudget) {
|
|
27
|
+
return { ...DEFAULT_BUDGET, ...parsed.smellBudget };
|
|
28
|
+
}
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
// In .smellrc.json, look for "budget" key
|
|
32
|
+
if (p === '.smellrc.json') {
|
|
33
|
+
if (parsed.budget) {
|
|
34
|
+
return { ...DEFAULT_BUDGET, ...parsed.budget };
|
|
35
|
+
}
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
return { ...DEFAULT_BUDGET, ...parsed };
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return DEFAULT_BUDGET;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Check analysis results against performance budget
|
|
48
|
+
*/
|
|
49
|
+
export function checkBudget(result, budget) {
|
|
50
|
+
const violations = [];
|
|
51
|
+
const { summary, debtScore } = result;
|
|
52
|
+
// Check total smells
|
|
53
|
+
if (budget.maxTotalSmells !== undefined && summary.totalSmells > budget.maxTotalSmells) {
|
|
54
|
+
violations.push({
|
|
55
|
+
rule: 'maxTotalSmells',
|
|
56
|
+
actual: summary.totalSmells,
|
|
57
|
+
threshold: budget.maxTotalSmells,
|
|
58
|
+
message: `Total issues (${summary.totalSmells}) exceeds budget (${budget.maxTotalSmells})`,
|
|
59
|
+
severity: 'error',
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
// Check errors
|
|
63
|
+
if (budget.maxErrors !== undefined && summary.smellsBySeverity.error > budget.maxErrors) {
|
|
64
|
+
violations.push({
|
|
65
|
+
rule: 'maxErrors',
|
|
66
|
+
actual: summary.smellsBySeverity.error,
|
|
67
|
+
threshold: budget.maxErrors,
|
|
68
|
+
message: `Errors (${summary.smellsBySeverity.error}) exceeds budget (${budget.maxErrors})`,
|
|
69
|
+
severity: 'error',
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
// Check warnings
|
|
73
|
+
if (budget.maxWarnings !== undefined && summary.smellsBySeverity.warning > budget.maxWarnings) {
|
|
74
|
+
violations.push({
|
|
75
|
+
rule: 'maxWarnings',
|
|
76
|
+
actual: summary.smellsBySeverity.warning,
|
|
77
|
+
threshold: budget.maxWarnings,
|
|
78
|
+
message: `Warnings (${summary.smellsBySeverity.warning}) exceeds budget (${budget.maxWarnings})`,
|
|
79
|
+
severity: 'warning',
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
// Check minimum score
|
|
83
|
+
if (budget.minScore !== undefined && debtScore.score < budget.minScore) {
|
|
84
|
+
violations.push({
|
|
85
|
+
rule: 'minScore',
|
|
86
|
+
actual: debtScore.score,
|
|
87
|
+
threshold: budget.minScore,
|
|
88
|
+
message: `Technical debt score (${debtScore.score}) is below minimum (${budget.minScore})`,
|
|
89
|
+
severity: 'error',
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
// Check minimum grade
|
|
93
|
+
if (budget.minGrade !== undefined) {
|
|
94
|
+
const gradeOrder = ['F', 'D', 'C', 'B', 'A'];
|
|
95
|
+
const actualIndex = gradeOrder.indexOf(debtScore.grade);
|
|
96
|
+
const minIndex = gradeOrder.indexOf(budget.minGrade);
|
|
97
|
+
if (actualIndex < minIndex) {
|
|
98
|
+
violations.push({
|
|
99
|
+
rule: 'minGrade',
|
|
100
|
+
actual: debtScore.grade,
|
|
101
|
+
threshold: budget.minGrade,
|
|
102
|
+
message: `Technical debt grade (${debtScore.grade}) is below minimum (${budget.minGrade})`,
|
|
103
|
+
severity: 'error',
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Check per-type thresholds
|
|
108
|
+
if (budget.maxByType) {
|
|
109
|
+
for (const [type, maxCount] of Object.entries(budget.maxByType)) {
|
|
110
|
+
const actual = summary.smellsByType[type] || 0;
|
|
111
|
+
if (maxCount !== undefined && actual > maxCount) {
|
|
112
|
+
violations.push({
|
|
113
|
+
rule: `maxByType.${type}`,
|
|
114
|
+
actual,
|
|
115
|
+
threshold: maxCount,
|
|
116
|
+
message: `${type} issues (${actual}) exceeds budget (${maxCount})`,
|
|
117
|
+
severity: 'warning',
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// Check max smells per file
|
|
123
|
+
if (budget.maxSmellsPerFile !== undefined) {
|
|
124
|
+
for (const file of result.files) {
|
|
125
|
+
if (file.smells.length > budget.maxSmellsPerFile) {
|
|
126
|
+
const relativePath = file.file.split('/').slice(-2).join('/');
|
|
127
|
+
violations.push({
|
|
128
|
+
rule: 'maxSmellsPerFile',
|
|
129
|
+
actual: file.smells.length,
|
|
130
|
+
threshold: budget.maxSmellsPerFile,
|
|
131
|
+
message: `${relativePath} has ${file.smells.length} issues (max: ${budget.maxSmellsPerFile})`,
|
|
132
|
+
severity: 'warning',
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const errorViolations = violations.filter(v => v.severity === 'error');
|
|
138
|
+
return {
|
|
139
|
+
passed: errorViolations.length === 0,
|
|
140
|
+
violations,
|
|
141
|
+
summary: {
|
|
142
|
+
total: violations.length,
|
|
143
|
+
passed: Object.keys(budget).length - violations.length,
|
|
144
|
+
failed: violations.length,
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Format budget check result for console output
|
|
150
|
+
*/
|
|
151
|
+
export function formatBudgetReport(result) {
|
|
152
|
+
let output = '';
|
|
153
|
+
if (result.passed) {
|
|
154
|
+
output += chalk.green('\n✓ Performance budget check passed\n');
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
output += chalk.red('\n✗ Performance budget check failed\n');
|
|
158
|
+
}
|
|
159
|
+
if (result.violations.length > 0) {
|
|
160
|
+
output += chalk.dim('\nViolations:\n');
|
|
161
|
+
for (const violation of result.violations) {
|
|
162
|
+
const icon = violation.severity === 'error' ? chalk.red('✗') : chalk.yellow('⚠');
|
|
163
|
+
output += ` ${icon} ${violation.message}\n`;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
output += chalk.dim(`\nChecks: ${result.summary.passed} passed, ${result.summary.failed} failed\n`);
|
|
167
|
+
return output;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Create a default budget config file
|
|
171
|
+
*/
|
|
172
|
+
export async function createBudgetConfig(targetPath) {
|
|
173
|
+
const config = {
|
|
174
|
+
maxErrors: 0,
|
|
175
|
+
maxWarnings: 10,
|
|
176
|
+
minScore: 70,
|
|
177
|
+
minGrade: 'C',
|
|
178
|
+
maxSmellsPerFile: 5,
|
|
179
|
+
maxByType: {
|
|
180
|
+
'useEffect-overuse': 3,
|
|
181
|
+
'prop-drilling': 5,
|
|
182
|
+
'large-component': 2,
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
const filePath = targetPath || '.smellbudget.json';
|
|
186
|
+
const fullPath = path.resolve(process.cwd(), filePath);
|
|
187
|
+
await fs.writeFile(fullPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
188
|
+
return fullPath;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Compare current results with baseline for growth limits
|
|
192
|
+
*/
|
|
193
|
+
export function checkGrowthLimits(current, baseline, budget) {
|
|
194
|
+
const violations = [];
|
|
195
|
+
const newSmells = current.totalSmells - baseline.totalSmells;
|
|
196
|
+
if (budget.maxNewSmells !== undefined && newSmells > budget.maxNewSmells) {
|
|
197
|
+
violations.push({
|
|
198
|
+
rule: 'maxNewSmells',
|
|
199
|
+
actual: newSmells,
|
|
200
|
+
threshold: budget.maxNewSmells,
|
|
201
|
+
message: `New issues (${newSmells}) exceeds allowed increase (${budget.maxNewSmells})`,
|
|
202
|
+
severity: 'error',
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
if (budget.allowedGrowthPercent !== undefined && baseline.totalSmells > 0) {
|
|
206
|
+
const growthPercent = ((newSmells / baseline.totalSmells) * 100);
|
|
207
|
+
if (growthPercent > budget.allowedGrowthPercent) {
|
|
208
|
+
violations.push({
|
|
209
|
+
rule: 'allowedGrowthPercent',
|
|
210
|
+
actual: `${growthPercent.toFixed(1)}%`,
|
|
211
|
+
threshold: `${budget.allowedGrowthPercent}%`,
|
|
212
|
+
message: `Issue growth (${growthPercent.toFixed(1)}%) exceeds allowed (${budget.allowedGrowthPercent}%)`,
|
|
213
|
+
severity: 'error',
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return violations;
|
|
218
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { CodeSmell, AnalysisResult } from './types/index.js';
|
|
2
|
+
export interface PRCommentConfig {
|
|
3
|
+
token: string;
|
|
4
|
+
repo: string;
|
|
5
|
+
owner: string;
|
|
6
|
+
prNumber: number;
|
|
7
|
+
commitSha?: string;
|
|
8
|
+
collapseThreshold?: number;
|
|
9
|
+
onlyNew?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface PRComment {
|
|
12
|
+
body: string;
|
|
13
|
+
path?: string;
|
|
14
|
+
line?: number;
|
|
15
|
+
side?: 'LEFT' | 'RIGHT';
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Generate a PR comment summary from analysis results
|
|
19
|
+
*/
|
|
20
|
+
export declare function generatePRComment(result: AnalysisResult, rootDir: string): string;
|
|
21
|
+
/**
|
|
22
|
+
* Generate inline review comments for specific lines
|
|
23
|
+
*/
|
|
24
|
+
export declare function generateInlineComments(smells: CodeSmell[], rootDir: string): PRComment[];
|
|
25
|
+
/**
|
|
26
|
+
* Post a comment to a GitHub PR using the GitHub API
|
|
27
|
+
*/
|
|
28
|
+
export declare function postPRComment(config: PRCommentConfig, comment: string): Promise<boolean>;
|
|
29
|
+
/**
|
|
30
|
+
* Post inline review comments to a GitHub PR
|
|
31
|
+
*/
|
|
32
|
+
export declare function postInlineComments(config: PRCommentConfig, comments: PRComment[]): Promise<{
|
|
33
|
+
success: number;
|
|
34
|
+
failed: number;
|
|
35
|
+
}>;
|
|
36
|
+
/**
|
|
37
|
+
* Parse GitHub repository info from environment or git remote
|
|
38
|
+
*/
|
|
39
|
+
export declare function parseGitHubInfo(): {
|
|
40
|
+
owner: string;
|
|
41
|
+
repo: string;
|
|
42
|
+
} | null;
|
|
43
|
+
/**
|
|
44
|
+
* Get PR number from environment (GitHub Actions)
|
|
45
|
+
*/
|
|
46
|
+
export declare function getPRNumber(): number | null;
|
|
47
|
+
//# sourceMappingURL=prComments.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prComments.d.ts","sourceRoot":"","sources":["../src/prComments.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAE7D,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACzB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAkFjF;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,CAiCxF;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CA4B9F;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,eAAe,EACvB,QAAQ,EAAE,SAAS,EAAE,GACpB,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAkD9C;AAgBD;;GAEG;AACH,wBAAgB,eAAe,IAAI;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CASxE;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,MAAM,GAAG,IAAI,CAyB3C"}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate a PR comment summary from analysis results
|
|
3
|
+
*/
|
|
4
|
+
export function generatePRComment(result, rootDir) {
|
|
5
|
+
const { summary, debtScore, files } = result;
|
|
6
|
+
let comment = '## 🔍 Code Smell Analysis Report\n\n';
|
|
7
|
+
// Grade badge
|
|
8
|
+
const gradeEmoji = getGradeEmoji(debtScore.grade);
|
|
9
|
+
comment += `### ${gradeEmoji} Technical Debt Grade: **${debtScore.grade}** (Score: ${debtScore.score}/100)\n\n`;
|
|
10
|
+
// Summary table
|
|
11
|
+
comment += '| Metric | Count |\n';
|
|
12
|
+
comment += '|--------|-------|\n';
|
|
13
|
+
comment += `| Files Analyzed | ${summary.totalFiles} |\n`;
|
|
14
|
+
comment += `| Components | ${summary.totalComponents} |\n`;
|
|
15
|
+
comment += `| Total Issues | ${summary.totalSmells} |\n`;
|
|
16
|
+
comment += `| 🔴 Errors | ${summary.smellsBySeverity.error} |\n`;
|
|
17
|
+
comment += `| 🟡 Warnings | ${summary.smellsBySeverity.warning} |\n`;
|
|
18
|
+
comment += `| 🔵 Info | ${summary.smellsBySeverity.info} |\n\n`;
|
|
19
|
+
// Estimated refactor time
|
|
20
|
+
comment += `**Estimated Refactor Time:** ${debtScore.estimatedRefactorTime}\n\n`;
|
|
21
|
+
// Top issues by type
|
|
22
|
+
const topIssues = Object.entries(summary.smellsByType)
|
|
23
|
+
.filter(([_, count]) => count > 0)
|
|
24
|
+
.sort((a, b) => b[1] - a[1])
|
|
25
|
+
.slice(0, 5);
|
|
26
|
+
if (topIssues.length > 0) {
|
|
27
|
+
comment += '### Top Issues\n\n';
|
|
28
|
+
for (const [type, count] of topIssues) {
|
|
29
|
+
comment += `- **${type}**: ${count} occurrence(s)\n`;
|
|
30
|
+
}
|
|
31
|
+
comment += '\n';
|
|
32
|
+
}
|
|
33
|
+
// Breakdown by file (collapsible if many)
|
|
34
|
+
const filesWithIssues = files.filter(f => f.smells.length > 0);
|
|
35
|
+
if (filesWithIssues.length > 0) {
|
|
36
|
+
comment += '<details>\n<summary>📁 Issues by File</summary>\n\n';
|
|
37
|
+
for (const file of filesWithIssues.slice(0, 10)) {
|
|
38
|
+
const relativePath = file.file.replace(rootDir, '').replace(/^\//, '');
|
|
39
|
+
const errors = file.smells.filter(s => s.severity === 'error').length;
|
|
40
|
+
const warnings = file.smells.filter(s => s.severity === 'warning').length;
|
|
41
|
+
comment += `#### \`${relativePath}\`\n`;
|
|
42
|
+
comment += `🔴 ${errors} errors, 🟡 ${warnings} warnings\n\n`;
|
|
43
|
+
for (const smell of file.smells.slice(0, 5)) {
|
|
44
|
+
const emoji = smell.severity === 'error' ? '🔴' : smell.severity === 'warning' ? '🟡' : '🔵';
|
|
45
|
+
comment += `- ${emoji} Line ${smell.line}: ${smell.message}\n`;
|
|
46
|
+
}
|
|
47
|
+
if (file.smells.length > 5) {
|
|
48
|
+
comment += `- ... and ${file.smells.length - 5} more\n`;
|
|
49
|
+
}
|
|
50
|
+
comment += '\n';
|
|
51
|
+
}
|
|
52
|
+
if (filesWithIssues.length > 10) {
|
|
53
|
+
comment += `\n*... and ${filesWithIssues.length - 10} more files with issues*\n`;
|
|
54
|
+
}
|
|
55
|
+
comment += '</details>\n\n';
|
|
56
|
+
}
|
|
57
|
+
// Score breakdown
|
|
58
|
+
comment += '<details>\n<summary>📊 Score Breakdown</summary>\n\n';
|
|
59
|
+
comment += '| Category | Score |\n';
|
|
60
|
+
comment += '|----------|-------|\n';
|
|
61
|
+
comment += `| useEffect Usage | ${debtScore.breakdown.useEffectScore}/100 |\n`;
|
|
62
|
+
comment += `| Prop Drilling | ${debtScore.breakdown.propDrillingScore}/100 |\n`;
|
|
63
|
+
comment += `| Component Size | ${debtScore.breakdown.componentSizeScore}/100 |\n`;
|
|
64
|
+
comment += `| Memoization | ${debtScore.breakdown.memoizationScore}/100 |\n\n`;
|
|
65
|
+
comment += '</details>\n\n';
|
|
66
|
+
// Footer
|
|
67
|
+
comment += '---\n';
|
|
68
|
+
comment += '*Generated by [react-code-smell-detector](https://github.com/vsthakur101/react-code-smell-detector)*';
|
|
69
|
+
return comment;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Generate inline review comments for specific lines
|
|
73
|
+
*/
|
|
74
|
+
export function generateInlineComments(smells, rootDir) {
|
|
75
|
+
const comments = [];
|
|
76
|
+
// Group by file and line to avoid duplicate comments
|
|
77
|
+
const grouped = new Map();
|
|
78
|
+
for (const smell of smells) {
|
|
79
|
+
const key = `${smell.file}:${smell.line}`;
|
|
80
|
+
const existing = grouped.get(key) || [];
|
|
81
|
+
existing.push(smell);
|
|
82
|
+
grouped.set(key, existing);
|
|
83
|
+
}
|
|
84
|
+
for (const [key, lineSmells] of grouped) {
|
|
85
|
+
const [file, lineStr] = key.split(':');
|
|
86
|
+
const line = parseInt(lineStr, 10);
|
|
87
|
+
const relativePath = file.replace(rootDir, '').replace(/^\//, '');
|
|
88
|
+
let body = '';
|
|
89
|
+
for (const smell of lineSmells) {
|
|
90
|
+
const emoji = smell.severity === 'error' ? '🔴' : smell.severity === 'warning' ? '🟡' : '🔵';
|
|
91
|
+
body += `${emoji} **${smell.type}**: ${smell.message}\n\n`;
|
|
92
|
+
body += `> 💡 ${smell.suggestion}\n\n`;
|
|
93
|
+
}
|
|
94
|
+
comments.push({
|
|
95
|
+
body: body.trim(),
|
|
96
|
+
path: relativePath,
|
|
97
|
+
line,
|
|
98
|
+
side: 'RIGHT',
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return comments;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Post a comment to a GitHub PR using the GitHub API
|
|
105
|
+
*/
|
|
106
|
+
export async function postPRComment(config, comment) {
|
|
107
|
+
const { token, owner, repo, prNumber } = config;
|
|
108
|
+
const url = `https://api.github.com/repos/${owner}/${repo}/issues/${prNumber}/comments`;
|
|
109
|
+
try {
|
|
110
|
+
const response = await fetch(url, {
|
|
111
|
+
method: 'POST',
|
|
112
|
+
headers: {
|
|
113
|
+
'Authorization': `Bearer ${token}`,
|
|
114
|
+
'Accept': 'application/vnd.github.v3+json',
|
|
115
|
+
'Content-Type': 'application/json',
|
|
116
|
+
'X-GitHub-Api-Version': '2022-11-28',
|
|
117
|
+
},
|
|
118
|
+
body: JSON.stringify({ body: comment }),
|
|
119
|
+
});
|
|
120
|
+
if (!response.ok) {
|
|
121
|
+
const error = await response.text();
|
|
122
|
+
console.error(`Failed to post PR comment: ${response.status} ${error}`);
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
console.error(`Error posting PR comment: ${error.message}`);
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Post inline review comments to a GitHub PR
|
|
134
|
+
*/
|
|
135
|
+
export async function postInlineComments(config, comments) {
|
|
136
|
+
const { token, owner, repo, prNumber, commitSha } = config;
|
|
137
|
+
if (!commitSha) {
|
|
138
|
+
console.error('Commit SHA is required for inline comments');
|
|
139
|
+
return { success: 0, failed: comments.length };
|
|
140
|
+
}
|
|
141
|
+
let success = 0;
|
|
142
|
+
let failed = 0;
|
|
143
|
+
// Create a review with all comments
|
|
144
|
+
const url = `https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}/reviews`;
|
|
145
|
+
const reviewComments = comments.map(c => ({
|
|
146
|
+
path: c.path,
|
|
147
|
+
line: c.line,
|
|
148
|
+
side: c.side || 'RIGHT',
|
|
149
|
+
body: c.body,
|
|
150
|
+
}));
|
|
151
|
+
try {
|
|
152
|
+
const response = await fetch(url, {
|
|
153
|
+
method: 'POST',
|
|
154
|
+
headers: {
|
|
155
|
+
'Authorization': `Bearer ${token}`,
|
|
156
|
+
'Accept': 'application/vnd.github.v3+json',
|
|
157
|
+
'Content-Type': 'application/json',
|
|
158
|
+
'X-GitHub-Api-Version': '2022-11-28',
|
|
159
|
+
},
|
|
160
|
+
body: JSON.stringify({
|
|
161
|
+
commit_id: commitSha,
|
|
162
|
+
event: 'COMMENT',
|
|
163
|
+
comments: reviewComments,
|
|
164
|
+
}),
|
|
165
|
+
});
|
|
166
|
+
if (!response.ok) {
|
|
167
|
+
const error = await response.text();
|
|
168
|
+
console.error(`Failed to post inline comments: ${response.status} ${error}`);
|
|
169
|
+
failed = comments.length;
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
success = comments.length;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
console.error(`Error posting inline comments: ${error.message}`);
|
|
177
|
+
failed = comments.length;
|
|
178
|
+
}
|
|
179
|
+
return { success, failed };
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Get emoji for grade
|
|
183
|
+
*/
|
|
184
|
+
function getGradeEmoji(grade) {
|
|
185
|
+
switch (grade) {
|
|
186
|
+
case 'A': return '🏆';
|
|
187
|
+
case 'B': return '✅';
|
|
188
|
+
case 'C': return '⚠️';
|
|
189
|
+
case 'D': return '🔶';
|
|
190
|
+
case 'F': return '🔴';
|
|
191
|
+
default: return '📊';
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Parse GitHub repository info from environment or git remote
|
|
196
|
+
*/
|
|
197
|
+
export function parseGitHubInfo() {
|
|
198
|
+
// Try GITHUB_REPOSITORY environment variable (set in GitHub Actions)
|
|
199
|
+
const ghRepo = process.env.GITHUB_REPOSITORY;
|
|
200
|
+
if (ghRepo) {
|
|
201
|
+
const [owner, repo] = ghRepo.split('/');
|
|
202
|
+
return { owner, repo };
|
|
203
|
+
}
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Get PR number from environment (GitHub Actions)
|
|
208
|
+
*/
|
|
209
|
+
export function getPRNumber() {
|
|
210
|
+
// GITHUB_REF format: refs/pull/{number}/merge
|
|
211
|
+
const ref = process.env.GITHUB_REF;
|
|
212
|
+
if (ref && ref.includes('/pull/')) {
|
|
213
|
+
const match = ref.match(/\/pull\/(\d+)\//);
|
|
214
|
+
if (match) {
|
|
215
|
+
return parseInt(match[1], 10);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
// Try GITHUB_EVENT_PATH for pull_request events
|
|
219
|
+
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
220
|
+
if (eventPath) {
|
|
221
|
+
try {
|
|
222
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
223
|
+
const event = require(eventPath);
|
|
224
|
+
if (event.pull_request?.number) {
|
|
225
|
+
return event.pull_request.number;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
// Ignore
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return null;
|
|
233
|
+
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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' | 'unused-export' | 'dead-import' | 'custom-rule';
|
|
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' | 'server-component-hooks' | 'server-component-events' | 'server-component-browser-api' | 'async-client-component' | 'mixed-directives' | 'custom-rule';
|
|
3
3
|
export interface CodeSmell {
|
|
4
4
|
type: SmellType;
|
|
5
5
|
severity: SmellSeverity;
|
|
@@ -92,6 +92,7 @@ export interface DetectorConfig {
|
|
|
92
92
|
analyzeBundleSize?: boolean;
|
|
93
93
|
maxComponentSize?: number;
|
|
94
94
|
customRules?: any[];
|
|
95
|
+
checkServerComponents: boolean;
|
|
95
96
|
}
|
|
96
97
|
export declare const DEFAULT_CONFIG: DetectorConfig;
|
|
97
98
|
//# 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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"}
|
package/dist/types/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-code-smell-detector",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Detect code smells in React projects - useEffect overuse, prop drilling, large components, security issues, accessibility, memory leaks, and more",
|
|
3
|
+
"version": "1.5.0",
|
|
4
|
+
"description": "Detect code smells in React projects - useEffect overuse, prop drilling, large components, security issues, accessibility, memory leaks, React 19 Server Components, and more",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"react-smell": "dist/cli.js"
|
|
@@ -16,7 +16,11 @@
|
|
|
16
16
|
"build": "tsc",
|
|
17
17
|
"dev": "tsc --watch",
|
|
18
18
|
"start": "node dist/cli.js",
|
|
19
|
-
"test": "vitest"
|
|
19
|
+
"test": "vitest",
|
|
20
|
+
"test:ui": "vitest --ui",
|
|
21
|
+
"test:coverage": "vitest run --coverage",
|
|
22
|
+
"test:report": "vitest run --reporter=html --outputFile.html=./test-report/index.html && open ./test-report/index.html",
|
|
23
|
+
"test:all": "vitest run --coverage --reporter=html --outputFile.html=./test-report/index.html"
|
|
20
24
|
},
|
|
21
25
|
"keywords": [
|
|
22
26
|
"react",
|
|
@@ -44,8 +48,10 @@
|
|
|
44
48
|
"devDependencies": {
|
|
45
49
|
"@types/babel__traverse": "^7.20.4",
|
|
46
50
|
"@types/node": "^20.10.0",
|
|
51
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
52
|
+
"@vitest/ui": "^4.0.18",
|
|
47
53
|
"typescript": "^5.3.0",
|
|
48
|
-
"vitest": "^
|
|
54
|
+
"vitest": "^4.0.18"
|
|
49
55
|
},
|
|
50
56
|
"engines": {
|
|
51
57
|
"node": ">=18.0.0"
|