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
package/dist/index.d.ts CHANGED
@@ -2,4 +2,9 @@ export { analyzeProject, type AnalyzerOptions } from './analyzer.js';
2
2
  export { reportResults, type ReporterOptions } from './reporter.js';
3
3
  export * from './types/index.js';
4
4
  export { parseFile, parseCode, type ParseResult, type ParsedComponent } from './parser/index.js';
5
+ export { runInteractiveFix, previewFixes, type InteractiveFixOptions } from './interactiveFixer.js';
6
+ export { generatePRComment, postPRComment, generateInlineComments, type PRCommentConfig } from './prComments.js';
7
+ export { loadBudget, checkBudget, formatBudgetReport, createBudgetConfig, type PerformanceBudget, type BudgetCheckResult } from './performanceBudget.js';
8
+ export { generateComponentDocs, writeComponentDocs, type DocGeneratorOptions } from './docGenerator.js';
9
+ export { generateAIRefactoringSuggestions, generateAIAnalysisReport, getQuickRefactoringTemplates, type AIRefactoringSuggestion, type AIRefactoringConfig } from './aiRefactoring.js';
5
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AACrE,OAAO,EAAE,aAAa,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AACpE,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,WAAW,EAAE,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AACrE,OAAO,EAAE,aAAa,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AACpE,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,WAAW,EAAE,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACjG,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,KAAK,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AACpG,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,sBAAsB,EAAE,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACjH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,KAAK,iBAAiB,EAAE,KAAK,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AACzJ,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,KAAK,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxG,OAAO,EACL,gCAAgC,EAChC,wBAAwB,EACxB,4BAA4B,EAC5B,KAAK,uBAAuB,EAC5B,KAAK,mBAAmB,EACzB,MAAM,oBAAoB,CAAC"}
package/dist/index.js CHANGED
@@ -2,3 +2,8 @@ export { analyzeProject } from './analyzer.js';
2
2
  export { reportResults } from './reporter.js';
3
3
  export * from './types/index.js';
4
4
  export { parseFile, parseCode } from './parser/index.js';
5
+ export { runInteractiveFix, previewFixes } from './interactiveFixer.js';
6
+ export { generatePRComment, postPRComment, generateInlineComments } from './prComments.js';
7
+ export { loadBudget, checkBudget, formatBudgetReport, createBudgetConfig } from './performanceBudget.js';
8
+ export { generateComponentDocs, writeComponentDocs } from './docGenerator.js';
9
+ export { generateAIRefactoringSuggestions, generateAIAnalysisReport, getQuickRefactoringTemplates } from './aiRefactoring.js';
@@ -0,0 +1,20 @@
1
+ import { CodeSmell } from './types/index.js';
2
+ export interface InteractiveFixOptions {
3
+ smells: CodeSmell[];
4
+ rootDir: string;
5
+ showDiff?: boolean;
6
+ }
7
+ export interface InteractiveFixResult {
8
+ applied: number;
9
+ skipped: number;
10
+ total: number;
11
+ }
12
+ /**
13
+ * Interactive fix mode - review and apply fixes one by one
14
+ */
15
+ export declare function runInteractiveFix(options: InteractiveFixOptions): Promise<InteractiveFixResult>;
16
+ /**
17
+ * Preview all fixable issues without applying
18
+ */
19
+ export declare function previewFixes(smells: CodeSmell[], rootDir: string): void;
20
+ //# sourceMappingURL=interactiveFixer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interactiveFixer.d.ts","sourceRoot":"","sources":["../src/interactiveFixer.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAa,MAAM,kBAAkB,CAAC;AAIxD,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CA6HrG;AAoCD;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CA6BvE"}
@@ -0,0 +1,178 @@
1
+ import chalk from 'chalk';
2
+ import fs from 'fs/promises';
3
+ import { isFixable, describeFixAction } from './fixer.js';
4
+ import * as readline from 'readline';
5
+ /**
6
+ * Interactive fix mode - review and apply fixes one by one
7
+ */
8
+ export async function runInteractiveFix(options) {
9
+ const { smells, rootDir, showDiff = true } = options;
10
+ const fixableSmells = smells.filter(isFixable);
11
+ if (fixableSmells.length === 0) {
12
+ console.log(chalk.yellow('\nNo auto-fixable issues found.\n'));
13
+ return { applied: 0, skipped: 0, total: 0 };
14
+ }
15
+ console.log(chalk.cyan(`\n🔧 Interactive Fix Mode`));
16
+ console.log(chalk.dim(`Found ${fixableSmells.length} fixable issue(s). Review each one:\n`));
17
+ console.log(chalk.dim('Commands: [y]es, [n]o, [a]ll, [q]uit\n'));
18
+ const rl = readline.createInterface({
19
+ input: process.stdin,
20
+ output: process.stdout,
21
+ });
22
+ const question = (prompt) => {
23
+ return new Promise((resolve) => {
24
+ rl.question(prompt, resolve);
25
+ });
26
+ };
27
+ let applied = 0;
28
+ let skipped = 0;
29
+ let applyAll = false;
30
+ // Group smells by file for efficient processing
31
+ const smellsByFile = new Map();
32
+ for (const smell of fixableSmells) {
33
+ const existing = smellsByFile.get(smell.file) || [];
34
+ existing.push(smell);
35
+ smellsByFile.set(smell.file, existing);
36
+ }
37
+ for (const [file, fileSmells] of smellsByFile) {
38
+ let content = await fs.readFile(file, 'utf-8');
39
+ const lines = content.split('\n');
40
+ const relativePath = file.replace(rootDir, '').replace(/^\//, '');
41
+ // Sort by line descending to preserve line numbers when fixing
42
+ const sortedSmells = [...fileSmells].sort((a, b) => b.line - a.line);
43
+ for (const smell of sortedSmells) {
44
+ const lineIndex = smell.line - 1;
45
+ if (lineIndex < 0 || lineIndex >= lines.length) {
46
+ skipped++;
47
+ continue;
48
+ }
49
+ const originalLine = lines[lineIndex];
50
+ const fixedLine = getFixedLine(smell, originalLine);
51
+ if (fixedLine === originalLine) {
52
+ skipped++;
53
+ continue;
54
+ }
55
+ // Display the issue
56
+ console.log(chalk.white('─'.repeat(60)));
57
+ console.log(chalk.yellow(`${relativePath}:${smell.line}`));
58
+ console.log(chalk.red(` ${smell.type}: ${smell.message}`));
59
+ console.log(chalk.blue(` Fix: ${describeFixAction(smell.type)}`));
60
+ if (showDiff) {
61
+ console.log();
62
+ console.log(chalk.red(` - ${originalLine.trim()}`));
63
+ if (fixedLine !== null) {
64
+ console.log(chalk.green(` + ${fixedLine.trim()}`));
65
+ }
66
+ else {
67
+ console.log(chalk.green(` + (line removed)`));
68
+ }
69
+ console.log();
70
+ }
71
+ let shouldApply = applyAll;
72
+ if (!applyAll) {
73
+ const answer = await question(chalk.cyan('Apply this fix? [y/n/a/q]: '));
74
+ const choice = answer.toLowerCase().trim();
75
+ if (choice === 'q') {
76
+ console.log(chalk.yellow('\nQuitting interactive mode.\n'));
77
+ rl.close();
78
+ return { applied, skipped: skipped + (fixableSmells.length - applied - skipped), total: fixableSmells.length };
79
+ }
80
+ else if (choice === 'a') {
81
+ applyAll = true;
82
+ shouldApply = true;
83
+ }
84
+ else if (choice === 'y' || choice === 'yes') {
85
+ shouldApply = true;
86
+ }
87
+ else {
88
+ shouldApply = false;
89
+ }
90
+ }
91
+ if (shouldApply) {
92
+ if (fixedLine === null) {
93
+ lines.splice(lineIndex, 1);
94
+ }
95
+ else {
96
+ lines[lineIndex] = fixedLine;
97
+ }
98
+ applied++;
99
+ console.log(chalk.green(' ✓ Applied'));
100
+ }
101
+ else {
102
+ skipped++;
103
+ console.log(chalk.dim(' ○ Skipped'));
104
+ }
105
+ }
106
+ // Write back the file if any changes were made
107
+ if (applied > 0) {
108
+ await fs.writeFile(file, lines.join('\n'), 'utf-8');
109
+ }
110
+ }
111
+ rl.close();
112
+ console.log(chalk.white('\n' + '─'.repeat(60)));
113
+ console.log(chalk.green(`✓ Applied ${applied} fix(es)`));
114
+ console.log(chalk.dim(`○ Skipped ${skipped} issue(s)`));
115
+ console.log();
116
+ return { applied, skipped, total: fixableSmells.length };
117
+ }
118
+ /**
119
+ * Get the fixed version of a line
120
+ */
121
+ function getFixedLine(smell, line) {
122
+ switch (smell.type) {
123
+ case 'debug-statement':
124
+ return fixDebugStatement(line);
125
+ case 'js-var-usage':
126
+ return line.replace(/\bvar\s+/, 'let ');
127
+ case 'js-loose-equality':
128
+ return line
129
+ .replace(/([^=!])={2}([^=])/g, '$1===$2')
130
+ .replace(/!={1}([^=])/g, '!==$1');
131
+ case 'a11y-missing-alt':
132
+ return line.replace(/<img\s+(?![^>]*\balt\b)([^>]*?)(\/?>)/gi, '<img $1alt="" $2');
133
+ default:
134
+ return line;
135
+ }
136
+ }
137
+ function fixDebugStatement(line) {
138
+ const trimmed = line.trim();
139
+ if (/^console\.(log|debug|info|warn|error|trace|dir)\s*\(.*\);?\s*$/.test(trimmed)) {
140
+ return null;
141
+ }
142
+ if (/^debugger;?\s*$/.test(trimmed)) {
143
+ return null;
144
+ }
145
+ if (/console\.(log|debug|info|warn|error|trace|dir)\s*\(/.test(line)) {
146
+ return line.replace(/console\.(log|debug|info|warn|error|trace|dir)\s*\([^)]*\);?/g, '/* $& */');
147
+ }
148
+ return line;
149
+ }
150
+ /**
151
+ * Preview all fixable issues without applying
152
+ */
153
+ export function previewFixes(smells, rootDir) {
154
+ const fixableSmells = smells.filter(isFixable);
155
+ if (fixableSmells.length === 0) {
156
+ console.log(chalk.yellow('\nNo auto-fixable issues found.\n'));
157
+ return;
158
+ }
159
+ console.log(chalk.cyan(`\n📋 Fixable Issues Preview (${fixableSmells.length} total)\n`));
160
+ const grouped = new Map();
161
+ for (const smell of fixableSmells) {
162
+ const existing = grouped.get(smell.type) || [];
163
+ existing.push(smell);
164
+ grouped.set(smell.type, existing);
165
+ }
166
+ for (const [type, typeSmells] of grouped) {
167
+ console.log(chalk.yellow(`\n${type} (${typeSmells.length})`));
168
+ console.log(chalk.dim(` Fix: ${describeFixAction(type)}`));
169
+ for (const smell of typeSmells.slice(0, 5)) {
170
+ const relativePath = smell.file.replace(rootDir, '').replace(/^\//, '');
171
+ console.log(chalk.white(` ${relativePath}:${smell.line}`));
172
+ }
173
+ if (typeSmells.length > 5) {
174
+ console.log(chalk.dim(` ... and ${typeSmells.length - 5} more`));
175
+ }
176
+ }
177
+ console.log();
178
+ }
@@ -0,0 +1,54 @@
1
+ import { AnalysisResult, AnalysisSummary, SmellType } from './types/index.js';
2
+ export interface PerformanceBudget {
3
+ maxTotalSmells?: number;
4
+ maxErrors?: number;
5
+ maxWarnings?: number;
6
+ minScore?: number;
7
+ minGrade?: 'A' | 'B' | 'C' | 'D' | 'F';
8
+ maxByType?: Partial<Record<SmellType, number>>;
9
+ maxCyclomaticComplexity?: number;
10
+ maxCognitiveComplexity?: number;
11
+ maxComponentLines?: number;
12
+ maxPropsCount?: number;
13
+ maxUseEffectsPerComponent?: number;
14
+ maxSmellsPerFile?: number;
15
+ maxNewSmells?: number;
16
+ allowedGrowthPercent?: number;
17
+ }
18
+ export interface BudgetCheckResult {
19
+ passed: boolean;
20
+ violations: BudgetViolation[];
21
+ summary: {
22
+ total: number;
23
+ passed: number;
24
+ failed: number;
25
+ };
26
+ }
27
+ export interface BudgetViolation {
28
+ rule: string;
29
+ actual: number | string;
30
+ threshold: number | string;
31
+ message: string;
32
+ severity: 'error' | 'warning';
33
+ }
34
+ /**
35
+ * Load performance budget from config file
36
+ */
37
+ export declare function loadBudget(configPath?: string): Promise<PerformanceBudget>;
38
+ /**
39
+ * Check analysis results against performance budget
40
+ */
41
+ export declare function checkBudget(result: AnalysisResult, budget: PerformanceBudget): BudgetCheckResult;
42
+ /**
43
+ * Format budget check result for console output
44
+ */
45
+ export declare function formatBudgetReport(result: BudgetCheckResult): string;
46
+ /**
47
+ * Create a default budget config file
48
+ */
49
+ export declare function createBudgetConfig(targetPath?: string): Promise<string>;
50
+ /**
51
+ * Compare current results with baseline for growth limits
52
+ */
53
+ export declare function checkGrowthLimits(current: AnalysisSummary, baseline: AnalysisSummary, budget: PerformanceBudget): BudgetViolation[];
54
+ //# sourceMappingURL=performanceBudget.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"performanceBudget.d.ts","sourceRoot":"","sources":["../src/performanceBudget.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAE,eAAe,EAAsB,SAAS,EAAiB,MAAM,kBAAkB,CAAC;AAEjH,MAAM,WAAW,iBAAiB;IAEhC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;IAGvC,SAAS,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC;IAG/C,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAGhC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,yBAAyB,CAAC,EAAE,MAAM,CAAC;IAGnC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAG1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,eAAe,EAAE,CAAC;IAC9B,OAAO,EAAE;QACP,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;CAC/B;AAOD;;GAEG;AACH,wBAAsB,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAsChF;AAED;;GAEG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE,iBAAiB,GACxB,iBAAiB,CA6GnB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CAqBpE;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAoB7E;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,eAAe,EACxB,QAAQ,EAAE,eAAe,EACzB,MAAM,EAAE,iBAAiB,GACxB,eAAe,EAAE,CA6BnB"}
@@ -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"}