aios-core 4.2.13 → 4.2.14
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/.aios-core/core/code-intel/helpers/dev-helper.js +206 -0
- package/.aios-core/core/registry/registry-schema.json +166 -166
- package/.aios-core/core/synapse/diagnostics/collectors/hook-collector.js +3 -3
- package/.aios-core/data/entity-registry.yaml +27 -0
- package/.aios-core/development/scripts/approval-workflow.js +642 -642
- package/.aios-core/development/scripts/backup-manager.js +606 -606
- package/.aios-core/development/scripts/branch-manager.js +389 -389
- package/.aios-core/development/scripts/code-quality-improver.js +1311 -1311
- package/.aios-core/development/scripts/commit-message-generator.js +849 -849
- package/.aios-core/development/scripts/conflict-resolver.js +674 -674
- package/.aios-core/development/scripts/dependency-analyzer.js +637 -637
- package/.aios-core/development/scripts/diff-generator.js +351 -351
- package/.aios-core/development/scripts/elicitation-engine.js +384 -384
- package/.aios-core/development/scripts/elicitation-session-manager.js +299 -299
- package/.aios-core/development/scripts/git-wrapper.js +461 -461
- package/.aios-core/development/scripts/manifest-preview.js +244 -244
- package/.aios-core/development/scripts/metrics-tracker.js +775 -775
- package/.aios-core/development/scripts/modification-validator.js +554 -554
- package/.aios-core/development/scripts/pattern-learner.js +1224 -1224
- package/.aios-core/development/scripts/performance-analyzer.js +757 -757
- package/.aios-core/development/scripts/refactoring-suggester.js +1138 -1138
- package/.aios-core/development/scripts/rollback-handler.js +530 -530
- package/.aios-core/development/scripts/security-checker.js +358 -358
- package/.aios-core/development/scripts/template-engine.js +239 -239
- package/.aios-core/development/scripts/template-validator.js +278 -278
- package/.aios-core/development/scripts/test-generator.js +843 -843
- package/.aios-core/development/scripts/transaction-manager.js +589 -589
- package/.aios-core/development/scripts/usage-tracker.js +673 -673
- package/.aios-core/development/scripts/validate-filenames.js +226 -226
- package/.aios-core/development/scripts/version-tracker.js +526 -526
- package/.aios-core/development/scripts/yaml-validator.js +396 -396
- package/.aios-core/development/tasks/build-autonomous.md +10 -4
- package/.aios-core/development/tasks/create-service.md +23 -0
- package/.aios-core/development/tasks/dev-develop-story.md +12 -6
- package/.aios-core/development/tasks/dev-suggest-refactoring.md +7 -1
- package/.aios-core/development/tasks/publish-npm.md +3 -3
- package/.aios-core/hooks/unified/README.md +1 -1
- package/.aios-core/install-manifest.yaml +65 -61
- package/.aios-core/manifests/schema/manifest-schema.json +190 -190
- package/.aios-core/product/templates/component-react-tmpl.tsx +98 -98
- package/.aios-core/product/templates/engine/schemas/adr.schema.json +102 -102
- package/.aios-core/product/templates/engine/schemas/dbdr.schema.json +205 -205
- package/.aios-core/product/templates/engine/schemas/epic.schema.json +175 -175
- package/.aios-core/product/templates/engine/schemas/pmdr.schema.json +175 -175
- package/.aios-core/product/templates/engine/schemas/prd-v2.schema.json +300 -300
- package/.aios-core/product/templates/engine/schemas/prd.schema.json +152 -152
- package/.aios-core/product/templates/engine/schemas/story.schema.json +222 -222
- package/.aios-core/product/templates/engine/schemas/task.schema.json +154 -154
- package/.aios-core/product/templates/eslintrc-security.json +32 -32
- package/.aios-core/product/templates/github-actions-cd.yml +212 -212
- package/.aios-core/product/templates/github-actions-ci.yml +172 -172
- package/.aios-core/product/templates/shock-report-tmpl.html +502 -502
- package/.aios-core/product/templates/token-exports-css-tmpl.css +240 -240
- package/.aios-core/quality/schemas/quality-metrics.schema.json +233 -233
- package/.aios-core/scripts/migrate-framework-docs.sh +300 -300
- package/README.en.md +747 -0
- package/README.md +4 -2
- package/bin/aios.js +7 -4
- package/package.json +1 -1
- package/packages/aios-pro-cli/src/recover.js +1 -1
- package/packages/installer/src/wizard/ide-config-generator.js +6 -6
- package/packages/installer/src/wizard/pro-setup.js +3 -3
- package/scripts/package-synapse.js +5 -5
- package/scripts/validate-package-completeness.js +3 -3
- package/.aios-core/.session/current-session.json +0 -14
- package/.aios-core/data/registry-update-log.jsonl +0 -191
- package/.aios-core/docs/SHARD-TRANSLATION-GUIDE.md +0 -335
- package/.aios-core/docs/component-creation-guide.md +0 -458
- package/.aios-core/docs/session-update-pattern.md +0 -307
- package/.aios-core/docs/standards/AIOS-FRAMEWORK-MASTER.md +0 -1963
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1-SUMMARY.md +0 -1190
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1.md +0 -439
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO.md +0 -5398
- package/.aios-core/docs/standards/V3-ARCHITECTURAL-DECISIONS.md +0 -523
- package/.aios-core/docs/template-syntax.md +0 -267
- package/.aios-core/docs/troubleshooting-guide.md +0 -625
- package/.aios-core/infrastructure/tests/utilities-audit-results.json +0 -501
- package/.aios-core/manifests/agents.csv +0 -29
- package/.aios-core/manifests/tasks.csv +0 -198
- package/.aios-core/manifests/workers.csv +0 -204
- package/.claude/rules/agent-authority.md +0 -105
- package/.claude/rules/coderabbit-integration.md +0 -93
- package/.claude/rules/ids-principles.md +0 -112
- package/.claude/rules/story-lifecycle.md +0 -139
- package/.claude/rules/workflow-execution.md +0 -150
- package/pro/README.md +0 -66
- package/pro/license/degradation.js +0 -220
- package/pro/license/errors.js +0 -450
- package/pro/license/feature-gate.js +0 -354
- package/pro/license/index.js +0 -181
- package/pro/license/license-api.js +0 -651
- package/pro/license/license-cache.js +0 -523
- package/pro/license/license-crypto.js +0 -303
- package/scripts/glue/README.md +0 -355
- package/scripts/glue/compose-agent-prompt.cjs +0 -362
- /package/.claude/hooks/{precompact-session-digest.js → precompact-session-digest.cjs} +0 -0
- /package/.claude/hooks/{synapse-engine.js → synapse-engine.cjs} +0 -0
|
@@ -1,1312 +1,1312 @@
|
|
|
1
|
-
const fs = require('fs').promises;
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const chalk = require('chalk');
|
|
4
|
-
const { ESLint } = require('eslint');
|
|
5
|
-
const prettier = require('prettier');
|
|
6
|
-
const jscodeshift = require('jscodeshift');
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Automated code quality improvement system
|
|
10
|
-
* Applies automatic fixes and improvements to enhance code quality
|
|
11
|
-
*/
|
|
12
|
-
class CodeQualityImprover {
|
|
13
|
-
constructor(options = {}) {
|
|
14
|
-
this.rootPath = options.rootPath || process.cwd();
|
|
15
|
-
this.improvements = [];
|
|
16
|
-
this.metrics = new Map();
|
|
17
|
-
this.eslint = null;
|
|
18
|
-
this.prettierConfig = null;
|
|
19
|
-
this.improvementPatterns = new Map();
|
|
20
|
-
this.initializePatterns();
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Initialize improvement patterns
|
|
25
|
-
*/
|
|
26
|
-
initializePatterns() {
|
|
27
|
-
// Code formatting improvements
|
|
28
|
-
this.improvementPatterns.set('formatting', {
|
|
29
|
-
name: 'Code Formatting',
|
|
30
|
-
description: 'Apply consistent code formatting',
|
|
31
|
-
improver: this.improveFormatting.bind(this),
|
|
32
|
-
priority: 'medium',
|
|
33
|
-
automatic: true
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
// Linting fixes
|
|
37
|
-
this.improvementPatterns.set('linting', {
|
|
38
|
-
name: 'Linting Fixes',
|
|
39
|
-
description: 'Fix linting errors and warnings',
|
|
40
|
-
improver: this.improveLinting.bind(this),
|
|
41
|
-
priority: 'high',
|
|
42
|
-
automatic: true
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
// Modern syntax upgrades
|
|
46
|
-
this.improvementPatterns.set('modern_syntax', {
|
|
47
|
-
name: 'Modern Syntax',
|
|
48
|
-
description: 'Upgrade to modern JavaScript syntax',
|
|
49
|
-
improver: this.upgradeToModernSyntax.bind(this),
|
|
50
|
-
priority: 'medium',
|
|
51
|
-
automatic: true
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
// Import optimization
|
|
55
|
-
this.improvementPatterns.set('optimize_imports', {
|
|
56
|
-
name: 'Optimize Imports',
|
|
57
|
-
description: 'Organize and optimize import statements',
|
|
58
|
-
improver: this.optimizeImports.bind(this),
|
|
59
|
-
priority: 'low',
|
|
60
|
-
automatic: true
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
// Dead code elimination
|
|
64
|
-
this.improvementPatterns.set('remove_unused', {
|
|
65
|
-
name: 'Remove Unused Code',
|
|
66
|
-
description: 'Remove unused variables and functions',
|
|
67
|
-
improver: this.removeUnusedCode.bind(this),
|
|
68
|
-
priority: 'high',
|
|
69
|
-
automatic: false
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
// Consistent naming
|
|
73
|
-
this.improvementPatterns.set('naming_conventions', {
|
|
74
|
-
name: 'Naming Conventions',
|
|
75
|
-
description: 'Apply consistent naming conventions',
|
|
76
|
-
improver: this.improveNaming.bind(this),
|
|
77
|
-
priority: 'medium',
|
|
78
|
-
automatic: false
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
// Error handling improvements
|
|
82
|
-
this.improvementPatterns.set('error_handling', {
|
|
83
|
-
name: 'Error Handling',
|
|
84
|
-
description: 'Improve error handling patterns',
|
|
85
|
-
improver: this.improveErrorHandling.bind(this),
|
|
86
|
-
priority: 'high',
|
|
87
|
-
automatic: false
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
// Async/await conversion
|
|
91
|
-
this.improvementPatterns.set('async_await', {
|
|
92
|
-
name: 'Async/Await Conversion',
|
|
93
|
-
description: 'Convert promises to async/await',
|
|
94
|
-
improver: this.convertToAsyncAwait.bind(this),
|
|
95
|
-
priority: 'medium',
|
|
96
|
-
automatic: true
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
// Type safety improvements
|
|
100
|
-
this.improvementPatterns.set('type_safety', {
|
|
101
|
-
name: 'Type Safety',
|
|
102
|
-
description: 'Add type checks and validations',
|
|
103
|
-
improver: this.improveTypeSafety.bind(this),
|
|
104
|
-
priority: 'high',
|
|
105
|
-
automatic: false
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
// Documentation generation
|
|
109
|
-
this.improvementPatterns.set('documentation', {
|
|
110
|
-
name: 'Documentation',
|
|
111
|
-
description: 'Generate missing JSDoc comments',
|
|
112
|
-
improver: this.generateDocumentation.bind(this),
|
|
113
|
-
priority: 'medium',
|
|
114
|
-
automatic: false
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Initialize tools
|
|
120
|
-
*/
|
|
121
|
-
async initialize() {
|
|
122
|
-
// Initialize ESLint
|
|
123
|
-
this.eslint = new ESLint({
|
|
124
|
-
fix: true,
|
|
125
|
-
baseConfig: await this.loadESLintConfig(),
|
|
126
|
-
useEslintrc: true
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
// Load Prettier config
|
|
130
|
-
this.prettierConfig = await this.loadPrettierConfig();
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Analyze and improve code quality
|
|
135
|
-
*/
|
|
136
|
-
async improveCode(_filePath, options = {}) {
|
|
137
|
-
console.log(chalk.blue(`🎯 Improving: ${_filePath}`));
|
|
138
|
-
|
|
139
|
-
try {
|
|
140
|
-
const content = await fs.readFile(_filePath, 'utf-8');
|
|
141
|
-
const fileType = path.extname(_filePath);
|
|
142
|
-
|
|
143
|
-
if (!['.js', '.jsx', '.ts', '.tsx'].includes(fileType)) {
|
|
144
|
-
return {
|
|
145
|
-
_filePath,
|
|
146
|
-
improvements: [],
|
|
147
|
-
error: 'Unsupported file type'
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Calculate initial metrics
|
|
152
|
-
const initialMetrics = await this.calculateMetrics(content, filePath);
|
|
153
|
-
this.metrics.set(_filePath, { before: initialMetrics });
|
|
154
|
-
|
|
155
|
-
// Clear previous improvements
|
|
156
|
-
this.improvements = [];
|
|
157
|
-
|
|
158
|
-
let improvedContent = content;
|
|
159
|
-
const appliedImprovements = [];
|
|
160
|
-
|
|
161
|
-
// Apply improvement patterns
|
|
162
|
-
for (const [patternId, pattern] of this.improvementPatterns) {
|
|
163
|
-
if (options.patterns && !options.patterns.includes(patternId)) {
|
|
164
|
-
continue;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
if (!options.manual && !pattern.automatic) {
|
|
168
|
-
continue; // Skip manual improvements in automatic mode
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
try {
|
|
172
|
-
const result = await pattern.improver(improvedContent, _filePath, options);
|
|
173
|
-
|
|
174
|
-
if (result.improved) {
|
|
175
|
-
improvedContent = result.content;
|
|
176
|
-
appliedImprovements.push({
|
|
177
|
-
patternId,
|
|
178
|
-
pattern: pattern.name,
|
|
179
|
-
priority: pattern.priority,
|
|
180
|
-
changes: result.changes,
|
|
181
|
-
impact: result.impact || 'medium'
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
this.improvements.push(...result.improvements || []);
|
|
185
|
-
}
|
|
186
|
-
} catch (_error) {
|
|
187
|
-
console.warn(chalk.yellow(`Failed to apply ${pattern.name}: ${error.message}`));
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// Calculate final metrics
|
|
192
|
-
const finalMetrics = await this.calculateMetrics(improvedContent, filePath);
|
|
193
|
-
this.metrics.get(_filePath).after = finalMetrics;
|
|
194
|
-
|
|
195
|
-
// Calculate improvement score
|
|
196
|
-
const improvementScore = this.calculateImprovementScore(initialMetrics, finalMetrics);
|
|
197
|
-
|
|
198
|
-
return {
|
|
199
|
-
_filePath,
|
|
200
|
-
originalContent: content,
|
|
201
|
-
improvedContent,
|
|
202
|
-
improvements: appliedImprovements,
|
|
203
|
-
metrics: {
|
|
204
|
-
before: initialMetrics,
|
|
205
|
-
after: finalMetrics,
|
|
206
|
-
improvementScore
|
|
207
|
-
},
|
|
208
|
-
changed: content !== improvedContent
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
} catch (_error) {
|
|
212
|
-
return {
|
|
213
|
-
_filePath,
|
|
214
|
-
improvements: [],
|
|
215
|
-
error: error.message
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Calculate code metrics
|
|
222
|
-
*/
|
|
223
|
-
async calculateMetrics(content, filePath) {
|
|
224
|
-
const metrics = {
|
|
225
|
-
lines: content.split('\n').length,
|
|
226
|
-
complexity: 0,
|
|
227
|
-
maintainability: 0,
|
|
228
|
-
issues: 0,
|
|
229
|
-
coverage: 0
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
try {
|
|
233
|
-
// Linting issues
|
|
234
|
-
const lintResults = await this.eslint.lintText(content, { _filePath });
|
|
235
|
-
metrics.issues = lintResults[0]?.errorCount + lintResults[0]?.warningCount || 0;
|
|
236
|
-
|
|
237
|
-
// Cyclomatic complexity (simplified)
|
|
238
|
-
metrics.complexity = this.calculateCyclomaticComplexity(content);
|
|
239
|
-
|
|
240
|
-
// Maintainability index (simplified)
|
|
241
|
-
metrics.maintainability = this.calculateMaintainabilityIndex(content);
|
|
242
|
-
|
|
243
|
-
// Documentation coverage
|
|
244
|
-
metrics.coverage = this.calculateDocumentationCoverage(content);
|
|
245
|
-
|
|
246
|
-
} catch (_error) {
|
|
247
|
-
// Metrics calculation failed, use defaults
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
return metrics;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// Improvement functions
|
|
254
|
-
|
|
255
|
-
async improveFormatting(content, _filePath, options) {
|
|
256
|
-
try {
|
|
257
|
-
const formatted = await prettier.format(content, {
|
|
258
|
-
...this.prettierConfig,
|
|
259
|
-
filepath: filePath
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
return {
|
|
263
|
-
improved: formatted !== content,
|
|
264
|
-
content: formatted,
|
|
265
|
-
changes: formatted !== content ? ['Applied consistent formatting'] : [],
|
|
266
|
-
improvements: [{
|
|
267
|
-
type: 'formatting',
|
|
268
|
-
description: 'Applied Prettier formatting',
|
|
269
|
-
line: 0
|
|
270
|
-
}]
|
|
271
|
-
};
|
|
272
|
-
} catch (_error) {
|
|
273
|
-
return { improved: false, content, error: error.message };
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
async improveLinting(content, _filePath, options) {
|
|
278
|
-
try {
|
|
279
|
-
const results = await this.eslint.lintText(content, { _filePath });
|
|
280
|
-
|
|
281
|
-
if (results[0]?.output) {
|
|
282
|
-
return {
|
|
283
|
-
improved: true,
|
|
284
|
-
content: results[0].output,
|
|
285
|
-
changes: this.summarizeLintFixes(results[0]),
|
|
286
|
-
improvements: results[0].messages.map(msg => ({
|
|
287
|
-
type: 'linting',
|
|
288
|
-
description: msg.message,
|
|
289
|
-
line: msg.line,
|
|
290
|
-
severity: msg.severity === 2 ? 'error' : 'warning'
|
|
291
|
-
}))
|
|
292
|
-
};
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
return { improved: false, content };
|
|
296
|
-
} catch (_error) {
|
|
297
|
-
return { improved: false, content, error: error.message };
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
async upgradeToModernSyntax(content, _filePath, options) {
|
|
302
|
-
const j = jscodeshift;
|
|
303
|
-
let improved = false;
|
|
304
|
-
const changes = [];
|
|
305
|
-
|
|
306
|
-
try {
|
|
307
|
-
// Convert var to let/const
|
|
308
|
-
let ast = j(content);
|
|
309
|
-
const _varToLetConst = ast
|
|
310
|
-
.find(j.VariableDeclaration, { kind: 'var' })
|
|
311
|
-
.forEach(path => {
|
|
312
|
-
const isReassigned = this.isVariableReassigned(j, path);
|
|
313
|
-
path.node.kind = isReassigned ? 'let' : 'const';
|
|
314
|
-
improved = true;
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
if (improved) {
|
|
318
|
-
changes.push('Converted var to let/const');
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// Convert function expressions to arrow functions (where appropriate)
|
|
322
|
-
ast.find(j.FunctionExpression)
|
|
323
|
-
.filter(path => {
|
|
324
|
-
// Don't convert if it uses 'this' or 'arguments'
|
|
325
|
-
const usesThis = j(_path).find(j.ThisExpression).length > 0;
|
|
326
|
-
const usesArguments = j(_path).find(j.Identifier, { name: 'arguments' }).length > 0;
|
|
327
|
-
return !usesThis && !usesArguments && !path.node.id;
|
|
328
|
-
})
|
|
329
|
-
.forEach(path => {
|
|
330
|
-
const arrowFunction = j.arrowFunctionExpression(
|
|
331
|
-
path.node.params,
|
|
332
|
-
path.node.body,
|
|
333
|
-
path.node.body.type !== 'BlockStatement'
|
|
334
|
-
);
|
|
335
|
-
j(_path).replaceWith(arrowFunction);
|
|
336
|
-
improved = true;
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
if (changes.length > 0) {
|
|
340
|
-
changes.push('Converted function expressions to arrow functions');
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// Template literals
|
|
344
|
-
ast.find(j.BinaryExpression, { operator: '+' })
|
|
345
|
-
.filter(path => {
|
|
346
|
-
// Check if it's string concatenation
|
|
347
|
-
return path.node.left.type === 'Literal' || path.node.right.type === 'Literal';
|
|
348
|
-
})
|
|
349
|
-
.forEach(path => {
|
|
350
|
-
// Convert to template literal (simplified)
|
|
351
|
-
improved = true;
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
const result = ast.toSource();
|
|
355
|
-
|
|
356
|
-
return {
|
|
357
|
-
improved,
|
|
358
|
-
content: improved ? result : content,
|
|
359
|
-
changes,
|
|
360
|
-
improvements: changes.map(change => ({
|
|
361
|
-
type: 'modern_syntax',
|
|
362
|
-
description: change,
|
|
363
|
-
line: 0
|
|
364
|
-
}))
|
|
365
|
-
};
|
|
366
|
-
|
|
367
|
-
} catch (_error) {
|
|
368
|
-
return { improved: false, content, error: error.message };
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
async optimizeImports(content, _filePath, options) {
|
|
373
|
-
const j = jscodeshift;
|
|
374
|
-
let improved = false;
|
|
375
|
-
const changes = [];
|
|
376
|
-
|
|
377
|
-
try {
|
|
378
|
-
const ast = j(content);
|
|
379
|
-
|
|
380
|
-
// Group imports by source
|
|
381
|
-
const imports = new Map();
|
|
382
|
-
|
|
383
|
-
ast.find(j.ImportDeclaration)
|
|
384
|
-
.forEach(path => {
|
|
385
|
-
const source = path.node.source.value;
|
|
386
|
-
if (!imports.has(source)) {
|
|
387
|
-
imports.set(source, []);
|
|
388
|
-
}
|
|
389
|
-
imports.get(source).push(_path);
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
// Merge imports from same source
|
|
393
|
-
for (const [source, importPaths] of imports) {
|
|
394
|
-
if (importPaths.length > 1) {
|
|
395
|
-
// Merge specifiers
|
|
396
|
-
const allSpecifiers = [];
|
|
397
|
-
importPaths.forEach(path => {
|
|
398
|
-
allSpecifiers.push(...path.node.specifiers);
|
|
399
|
-
});
|
|
400
|
-
|
|
401
|
-
// Keep first import, remove others
|
|
402
|
-
importPaths[0].node.specifiers = allSpecifiers;
|
|
403
|
-
for (let i = 1; i < importPaths.length; i++) {
|
|
404
|
-
j(importPaths[i]).remove();
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
improved = true;
|
|
408
|
-
changes.push(`Merged imports from ${source}`);
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
// Sort imports
|
|
413
|
-
const importNodes = [];
|
|
414
|
-
ast.find(j.ImportDeclaration)
|
|
415
|
-
.forEach(path => {
|
|
416
|
-
importNodes.push(path.node);
|
|
417
|
-
j(_path).remove();
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
if (importNodes.length > 0) {
|
|
421
|
-
// Sort by: external packages, internal absolute, internal relative
|
|
422
|
-
importNodes.sort((a, b) => {
|
|
423
|
-
const aSource = a.source.value;
|
|
424
|
-
const bSource = b.source.value;
|
|
425
|
-
|
|
426
|
-
const aExternal = !aSource.startsWith('.') && !aSource.startsWith('/');
|
|
427
|
-
const bExternal = !bSource.startsWith('.') && !bSource.startsWith('/');
|
|
428
|
-
|
|
429
|
-
if (aExternal && !bExternal) return -1;
|
|
430
|
-
if (!aExternal && bExternal) return 1;
|
|
431
|
-
|
|
432
|
-
return aSource.localeCompare(bSource);
|
|
433
|
-
});
|
|
434
|
-
|
|
435
|
-
// Re-insert sorted imports at the beginning
|
|
436
|
-
const program = ast.find(j.Program);
|
|
437
|
-
importNodes.reverse().forEach(node => {
|
|
438
|
-
program.get('body').unshift(node);
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
improved = true;
|
|
442
|
-
changes.push('Sorted imports');
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
const result = ast.toSource();
|
|
446
|
-
|
|
447
|
-
return {
|
|
448
|
-
improved,
|
|
449
|
-
content: improved ? result : content,
|
|
450
|
-
changes,
|
|
451
|
-
improvements: changes.map(change => ({
|
|
452
|
-
type: 'optimize_imports',
|
|
453
|
-
description: change,
|
|
454
|
-
line: 0
|
|
455
|
-
}))
|
|
456
|
-
};
|
|
457
|
-
|
|
458
|
-
} catch (_error) {
|
|
459
|
-
return { improved: false, content, error: error.message };
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
async removeUnusedCode(content, _filePath, options) {
|
|
464
|
-
const j = jscodeshift;
|
|
465
|
-
let improved = false;
|
|
466
|
-
const changes = [];
|
|
467
|
-
const improvements = [];
|
|
468
|
-
|
|
469
|
-
try {
|
|
470
|
-
const ast = j(content);
|
|
471
|
-
|
|
472
|
-
// Find all variable declarations and their usage
|
|
473
|
-
const declaredVars = new Map();
|
|
474
|
-
const usedVars = new Set();
|
|
475
|
-
|
|
476
|
-
// Collect declarations
|
|
477
|
-
ast.find(j.VariableDeclarator)
|
|
478
|
-
.forEach(path => {
|
|
479
|
-
if (path.node.id.type === 'Identifier') {
|
|
480
|
-
declaredVars.set(path.node.id.name, path);
|
|
481
|
-
}
|
|
482
|
-
});
|
|
483
|
-
|
|
484
|
-
// Collect usage
|
|
485
|
-
ast.find(j.Identifier)
|
|
486
|
-
.filter(path => {
|
|
487
|
-
// Only count as used if it's not the declaration itself
|
|
488
|
-
const parent = path.parent.node;
|
|
489
|
-
return !(parent.type === 'VariableDeclarator' && parent.id === path.node);
|
|
490
|
-
})
|
|
491
|
-
.forEach(path => {
|
|
492
|
-
usedVars.add(path.node.name);
|
|
493
|
-
});
|
|
494
|
-
|
|
495
|
-
// Remove unused variables
|
|
496
|
-
for (const [varName, declaratorPath] of declaredVars) {
|
|
497
|
-
if (!usedVars.has(varName) && !this.isExported(declaratorPath)) {
|
|
498
|
-
const declaration = declaratorPath.parent;
|
|
499
|
-
|
|
500
|
-
if (declaration.node.declarations.length === 1) {
|
|
501
|
-
// Remove entire declaration
|
|
502
|
-
j(declaration).remove();
|
|
503
|
-
} else {
|
|
504
|
-
// Remove just this declarator
|
|
505
|
-
j(declaratorPath).remove();
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
improved = true;
|
|
509
|
-
changes.push(`Removed unused variable: ${varName}`);
|
|
510
|
-
improvements.push({
|
|
511
|
-
type: 'remove_unused',
|
|
512
|
-
description: `Removed unused variable: ${varName}`,
|
|
513
|
-
line: declaratorPath.node.loc?.start.line || 0
|
|
514
|
-
});
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
// Find unused functions
|
|
519
|
-
const declaredFunctions = new Map();
|
|
520
|
-
const calledFunctions = new Set();
|
|
521
|
-
|
|
522
|
-
ast.find(j.FunctionDeclaration)
|
|
523
|
-
.forEach(path => {
|
|
524
|
-
if (path.node.id) {
|
|
525
|
-
declaredFunctions.set(path.node.id.name, path);
|
|
526
|
-
}
|
|
527
|
-
});
|
|
528
|
-
|
|
529
|
-
ast.find(j.CallExpression)
|
|
530
|
-
.forEach(path => {
|
|
531
|
-
if (path.node.callee.type === 'Identifier') {
|
|
532
|
-
calledFunctions.add(path.node.callee.name);
|
|
533
|
-
}
|
|
534
|
-
});
|
|
535
|
-
|
|
536
|
-
// Remove unused functions
|
|
537
|
-
for (const [funcName, funcPath] of declaredFunctions) {
|
|
538
|
-
if (!calledFunctions.has(funcName) && !this.isExported(funcPath)) {
|
|
539
|
-
j(funcPath).remove();
|
|
540
|
-
improved = true;
|
|
541
|
-
changes.push(`Removed unused function: ${funcName}`);
|
|
542
|
-
improvements.push({
|
|
543
|
-
type: 'remove_unused',
|
|
544
|
-
description: `Removed unused function: ${funcName}`,
|
|
545
|
-
line: funcPath.node.loc?.start.line || 0
|
|
546
|
-
});
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
const result = ast.toSource();
|
|
551
|
-
|
|
552
|
-
return {
|
|
553
|
-
improved,
|
|
554
|
-
content: improved ? result : content,
|
|
555
|
-
changes,
|
|
556
|
-
improvements
|
|
557
|
-
};
|
|
558
|
-
|
|
559
|
-
} catch (_error) {
|
|
560
|
-
return { improved: false, content, error: error.message };
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
async improveNaming(content, _filePath, options) {
|
|
565
|
-
const j = jscodeshift;
|
|
566
|
-
let improved = false;
|
|
567
|
-
const changes = [];
|
|
568
|
-
const improvements = [];
|
|
569
|
-
|
|
570
|
-
try {
|
|
571
|
-
const ast = j(content);
|
|
572
|
-
|
|
573
|
-
// Convert snake_case to camelCase for variables and functions
|
|
574
|
-
ast.find(j.Identifier)
|
|
575
|
-
.filter(path => {
|
|
576
|
-
const name = path.node.name;
|
|
577
|
-
return name.includes('_') &&
|
|
578
|
-
(path.parent.node.type === 'VariableDeclarator' ||
|
|
579
|
-
path.parent.node.type === 'FunctionDeclaration');
|
|
580
|
-
})
|
|
581
|
-
.forEach(path => {
|
|
582
|
-
const oldName = path.node.name;
|
|
583
|
-
const newName = this.snakeToCamel(oldName);
|
|
584
|
-
|
|
585
|
-
// Rename all occurrences
|
|
586
|
-
ast.find(j.Identifier, { name: oldName })
|
|
587
|
-
.forEach(p => {
|
|
588
|
-
p.node.name = newName;
|
|
589
|
-
});
|
|
590
|
-
|
|
591
|
-
improved = true;
|
|
592
|
-
changes.push(`Renamed ${oldName} to ${newName}`);
|
|
593
|
-
improvements.push({
|
|
594
|
-
type: 'naming_conventions',
|
|
595
|
-
description: `Renamed ${oldName} to ${newName}`,
|
|
596
|
-
line: path.node.loc?.start.line || 0
|
|
597
|
-
});
|
|
598
|
-
});
|
|
599
|
-
|
|
600
|
-
// Ensure classes start with uppercase
|
|
601
|
-
ast.find(j.ClassDeclaration)
|
|
602
|
-
.filter(path => {
|
|
603
|
-
const name = path.node.id?.name;
|
|
604
|
-
return name && name[0] !== name[0].toUpperCase();
|
|
605
|
-
})
|
|
606
|
-
.forEach(path => {
|
|
607
|
-
const oldName = path.node.id.name;
|
|
608
|
-
const newName = oldName[0].toUpperCase() + oldName.slice(1);
|
|
609
|
-
|
|
610
|
-
// Rename all occurrences
|
|
611
|
-
ast.find(j.Identifier, { name: oldName })
|
|
612
|
-
.forEach(p => {
|
|
613
|
-
p.node.name = newName;
|
|
614
|
-
});
|
|
615
|
-
|
|
616
|
-
improved = true;
|
|
617
|
-
changes.push(`Renamed class ${oldName} to ${newName}`);
|
|
618
|
-
improvements.push({
|
|
619
|
-
type: 'naming_conventions',
|
|
620
|
-
description: `Renamed class ${oldName} to ${newName}`,
|
|
621
|
-
line: path.node.loc?.start.line || 0
|
|
622
|
-
});
|
|
623
|
-
});
|
|
624
|
-
|
|
625
|
-
// Ensure constants are UPPER_SNAKE_CASE
|
|
626
|
-
ast.find(j.VariableDeclaration, { kind: 'const' })
|
|
627
|
-
.forEach(path => {
|
|
628
|
-
path.node.declarations.forEach(declarator => {
|
|
629
|
-
if (declarator.id.type === 'Identifier') {
|
|
630
|
-
const name = declarator.id.name;
|
|
631
|
-
// Check if it looks like a constant (all caps or should be)
|
|
632
|
-
if (this.shouldBeConstantCase(declarator) && !this.isConstantCase(name)) {
|
|
633
|
-
const oldName = name;
|
|
634
|
-
const newName = this.toConstantCase(name);
|
|
635
|
-
|
|
636
|
-
// Rename all occurrences
|
|
637
|
-
ast.find(j.Identifier, { name: oldName })
|
|
638
|
-
.forEach(p => {
|
|
639
|
-
p.node.name = newName;
|
|
640
|
-
});
|
|
641
|
-
|
|
642
|
-
improved = true;
|
|
643
|
-
changes.push(`Renamed constant ${oldName} to ${newName}`);
|
|
644
|
-
improvements.push({
|
|
645
|
-
type: 'naming_conventions',
|
|
646
|
-
description: `Renamed constant ${oldName} to ${newName}`,
|
|
647
|
-
line: declarator.loc?.start.line || 0
|
|
648
|
-
});
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
});
|
|
652
|
-
});
|
|
653
|
-
|
|
654
|
-
const result = ast.toSource();
|
|
655
|
-
|
|
656
|
-
return {
|
|
657
|
-
improved,
|
|
658
|
-
content: improved ? result : content,
|
|
659
|
-
changes,
|
|
660
|
-
improvements
|
|
661
|
-
};
|
|
662
|
-
|
|
663
|
-
} catch (_error) {
|
|
664
|
-
return { improved: false, content, error: error.message };
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
async improveErrorHandling(content, _filePath, options) {
|
|
669
|
-
const j = jscodeshift;
|
|
670
|
-
let improved = false;
|
|
671
|
-
const changes = [];
|
|
672
|
-
const improvements = [];
|
|
673
|
-
|
|
674
|
-
try {
|
|
675
|
-
const ast = j(content);
|
|
676
|
-
|
|
677
|
-
// Add try-catch to async functions without error handling
|
|
678
|
-
ast.find(j.FunctionDeclaration)
|
|
679
|
-
.filter(path => path.node.async)
|
|
680
|
-
.forEach(path => {
|
|
681
|
-
const hasErrorHandling = j(_path).find(j.TryStatement).length > 0;
|
|
682
|
-
|
|
683
|
-
if (!hasErrorHandling && path.node.body.body.length > 0) {
|
|
684
|
-
// Wrap body in try-catch
|
|
685
|
-
const originalBody = path.node.body.body;
|
|
686
|
-
const tryStatement = j.tryStatement(
|
|
687
|
-
j.blockStatement(originalBody),
|
|
688
|
-
j.catchClause(
|
|
689
|
-
j.identifier('error'),
|
|
690
|
-
j.blockStatement([
|
|
691
|
-
j.expressionStatement(
|
|
692
|
-
j.callExpression(
|
|
693
|
-
j.memberExpression(
|
|
694
|
-
j.identifier('console'),
|
|
695
|
-
j.identifier('error')
|
|
696
|
-
),
|
|
697
|
-
[j.identifier('error')]
|
|
698
|
-
)
|
|
699
|
-
),
|
|
700
|
-
j.throwStatement(j.identifier('error'))
|
|
701
|
-
])
|
|
702
|
-
)
|
|
703
|
-
);
|
|
704
|
-
|
|
705
|
-
path.node.body.body = [tryStatement];
|
|
706
|
-
improved = true;
|
|
707
|
-
|
|
708
|
-
const funcName = path.node.id?.name || 'anonymous';
|
|
709
|
-
changes.push(`Added error handling to ${funcName}`);
|
|
710
|
-
improvements.push({
|
|
711
|
-
type: 'error_handling',
|
|
712
|
-
description: `Added try-catch to async function ${funcName}`,
|
|
713
|
-
line: path.node.loc?.start.line || 0
|
|
714
|
-
});
|
|
715
|
-
}
|
|
716
|
-
});
|
|
717
|
-
|
|
718
|
-
// Improve catch blocks that swallow errors
|
|
719
|
-
ast.find(j.CatchClause)
|
|
720
|
-
.filter(path => {
|
|
721
|
-
// Check if catch block is empty or only logs
|
|
722
|
-
const body = path.node.body.body;
|
|
723
|
-
return body.length === 0 ||
|
|
724
|
-
(body.length === 1 && this.isOnlyConsoleLog(body[0]));
|
|
725
|
-
})
|
|
726
|
-
.forEach(path => {
|
|
727
|
-
// Add proper error handling
|
|
728
|
-
const errorParam = path.node.param || j.identifier('error');
|
|
729
|
-
|
|
730
|
-
path.node.body.body = [
|
|
731
|
-
j.expressionStatement(
|
|
732
|
-
j.callExpression(
|
|
733
|
-
j.memberExpression(
|
|
734
|
-
j.identifier('console'),
|
|
735
|
-
j.identifier('error')
|
|
736
|
-
),
|
|
737
|
-
[j.literal('Error caught:'), errorParam]
|
|
738
|
-
)
|
|
739
|
-
),
|
|
740
|
-
j.throwStatement(errorParam)
|
|
741
|
-
];
|
|
742
|
-
|
|
743
|
-
improved = true;
|
|
744
|
-
changes.push('Improved catch block to properly handle errors');
|
|
745
|
-
improvements.push({
|
|
746
|
-
type: 'error_handling',
|
|
747
|
-
description: 'Added proper error re-throwing in catch block',
|
|
748
|
-
line: path.node.loc?.start.line || 0
|
|
749
|
-
});
|
|
750
|
-
});
|
|
751
|
-
|
|
752
|
-
const result = ast.toSource();
|
|
753
|
-
|
|
754
|
-
return {
|
|
755
|
-
improved,
|
|
756
|
-
content: improved ? result : content,
|
|
757
|
-
changes,
|
|
758
|
-
improvements
|
|
759
|
-
};
|
|
760
|
-
|
|
761
|
-
} catch (_error) {
|
|
762
|
-
return { improved: false, content, error: error.message };
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
async convertToAsyncAwait(content, _filePath, options) {
|
|
767
|
-
const j = jscodeshift;
|
|
768
|
-
let improved = false;
|
|
769
|
-
const changes = [];
|
|
770
|
-
const improvements = [];
|
|
771
|
-
|
|
772
|
-
try {
|
|
773
|
-
const ast = j(content);
|
|
774
|
-
|
|
775
|
-
// Convert .then().catch() chains to async/await
|
|
776
|
-
ast.find(j.CallExpression)
|
|
777
|
-
.filter(path => {
|
|
778
|
-
return path.node.callee.type === 'MemberExpression' &&
|
|
779
|
-
path.node.callee.property.name === 'then';
|
|
780
|
-
})
|
|
781
|
-
.forEach(path => {
|
|
782
|
-
// Find the containing function
|
|
783
|
-
const containingFunction = j(_path).closest(j.Function);
|
|
784
|
-
|
|
785
|
-
if (containingFunction.length > 0) {
|
|
786
|
-
const func = containingFunction.get();
|
|
787
|
-
|
|
788
|
-
// Make function async if not already
|
|
789
|
-
if (!func.node.async) {
|
|
790
|
-
func.node.async = true;
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
// Convert promise chain to await
|
|
794
|
-
// This is simplified - real implementation would be more complex
|
|
795
|
-
improved = true;
|
|
796
|
-
changes.push('Converted promise chain to async/await');
|
|
797
|
-
improvements.push({
|
|
798
|
-
type: 'async_await',
|
|
799
|
-
description: 'Converted .then() chain to async/await',
|
|
800
|
-
line: path.node.loc?.start.line || 0
|
|
801
|
-
});
|
|
802
|
-
}
|
|
803
|
-
});
|
|
804
|
-
|
|
805
|
-
// Convert Promise callbacks to async functions
|
|
806
|
-
ast.find(j.NewExpression)
|
|
807
|
-
.filter(path => {
|
|
808
|
-
return path.node.callee.name === 'Promise' &&
|
|
809
|
-
path.node.arguments.length > 0 &&
|
|
810
|
-
path.node.arguments[0].type === 'FunctionExpression';
|
|
811
|
-
})
|
|
812
|
-
.forEach(path => {
|
|
813
|
-
const promiseCallback = path.node.arguments[0];
|
|
814
|
-
|
|
815
|
-
// Convert to async function
|
|
816
|
-
const asyncFunction = j.functionExpression(
|
|
817
|
-
promiseCallback.id,
|
|
818
|
-
promiseCallback.params,
|
|
819
|
-
promiseCallback.body,
|
|
820
|
-
promiseCallback.generator,
|
|
821
|
-
true // async
|
|
822
|
-
);
|
|
823
|
-
|
|
824
|
-
path.node.arguments[0] = asyncFunction;
|
|
825
|
-
improved = true;
|
|
826
|
-
changes.push('Converted Promise constructor to async function');
|
|
827
|
-
improvements.push({
|
|
828
|
-
type: 'async_await',
|
|
829
|
-
description: 'Made Promise callback async',
|
|
830
|
-
line: path.node.loc?.start.line || 0
|
|
831
|
-
});
|
|
832
|
-
});
|
|
833
|
-
|
|
834
|
-
const result = ast.toSource();
|
|
835
|
-
|
|
836
|
-
return {
|
|
837
|
-
improved,
|
|
838
|
-
content: improved ? result : content,
|
|
839
|
-
changes,
|
|
840
|
-
improvements
|
|
841
|
-
};
|
|
842
|
-
|
|
843
|
-
} catch (_error) {
|
|
844
|
-
return { improved: false, content, error: error.message };
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
async improveTypeSafety(content, _filePath, options) {
|
|
849
|
-
const j = jscodeshift;
|
|
850
|
-
let improved = false;
|
|
851
|
-
const changes = [];
|
|
852
|
-
const improvements = [];
|
|
853
|
-
|
|
854
|
-
try {
|
|
855
|
-
const ast = j(content);
|
|
856
|
-
|
|
857
|
-
// Add parameter validation to functions
|
|
858
|
-
ast.find(j.FunctionDeclaration)
|
|
859
|
-
.forEach(path => {
|
|
860
|
-
const params = path.node.params;
|
|
861
|
-
if (params.length > 0) {
|
|
862
|
-
const validationStatements = [];
|
|
863
|
-
|
|
864
|
-
params.forEach(param => {
|
|
865
|
-
if (param.type === 'Identifier') {
|
|
866
|
-
// Add basic validation
|
|
867
|
-
validationStatements.push(
|
|
868
|
-
j.ifStatement(
|
|
869
|
-
j.binaryExpression(
|
|
870
|
-
'==',
|
|
871
|
-
param,
|
|
872
|
-
j.identifier('undefined')
|
|
873
|
-
),
|
|
874
|
-
j.throwStatement(
|
|
875
|
-
j.newExpression(
|
|
876
|
-
j.identifier('Error'),
|
|
877
|
-
[j.literal(`Parameter '${param.name}' is required`)]
|
|
878
|
-
)
|
|
879
|
-
)
|
|
880
|
-
)
|
|
881
|
-
);
|
|
882
|
-
}
|
|
883
|
-
});
|
|
884
|
-
|
|
885
|
-
if (validationStatements.length > 0) {
|
|
886
|
-
// Insert at beginning of function body
|
|
887
|
-
path.node.body.body.unshift(...validationStatements);
|
|
888
|
-
improved = true;
|
|
889
|
-
|
|
890
|
-
const funcName = path.node.id?.name || 'anonymous';
|
|
891
|
-
changes.push(`Added parameter validation to ${funcName}`);
|
|
892
|
-
improvements.push({
|
|
893
|
-
type: 'type_safety',
|
|
894
|
-
description: `Added parameter validation to function ${funcName}`,
|
|
895
|
-
line: path.node.loc?.start.line || 0
|
|
896
|
-
});
|
|
897
|
-
}
|
|
898
|
-
}
|
|
899
|
-
});
|
|
900
|
-
|
|
901
|
-
// Add null checks before property access
|
|
902
|
-
ast.find(j.MemberExpression)
|
|
903
|
-
.filter(path => {
|
|
904
|
-
// Check if it's a chain that could throw
|
|
905
|
-
return path.node.object.type === 'MemberExpression' ||
|
|
906
|
-
(path.node.object.type === 'Identifier' &&
|
|
907
|
-
!this.isKnownSafeObject(path.node.object.name));
|
|
908
|
-
})
|
|
909
|
-
.forEach(path => {
|
|
910
|
-
// Convert to optional chaining if not already
|
|
911
|
-
if (!path.node.optional) {
|
|
912
|
-
path.node.optional = true;
|
|
913
|
-
improved = true;
|
|
914
|
-
improvements.push({
|
|
915
|
-
type: 'type_safety',
|
|
916
|
-
description: 'Added optional chaining for safer property access',
|
|
917
|
-
line: path.node.loc?.start.line || 0
|
|
918
|
-
});
|
|
919
|
-
}
|
|
920
|
-
});
|
|
921
|
-
|
|
922
|
-
if (improvements.length > 0) {
|
|
923
|
-
changes.push('Added type safety improvements');
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
const result = ast.toSource();
|
|
927
|
-
|
|
928
|
-
return {
|
|
929
|
-
improved,
|
|
930
|
-
content: improved ? result : content,
|
|
931
|
-
changes,
|
|
932
|
-
improvements
|
|
933
|
-
};
|
|
934
|
-
|
|
935
|
-
} catch (_error) {
|
|
936
|
-
return { improved: false, content, error: error.message };
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
async generateDocumentation(content, _filePath, options) {
|
|
941
|
-
const j = jscodeshift;
|
|
942
|
-
let improved = false;
|
|
943
|
-
const changes = [];
|
|
944
|
-
const improvements = [];
|
|
945
|
-
|
|
946
|
-
try {
|
|
947
|
-
const ast = j(content);
|
|
948
|
-
|
|
949
|
-
// Add JSDoc to functions without documentation
|
|
950
|
-
ast.find(j.FunctionDeclaration)
|
|
951
|
-
.filter(path => {
|
|
952
|
-
// Check if function already has JSDoc
|
|
953
|
-
const comments = path.node.leadingComments || [];
|
|
954
|
-
return !comments.some(c => c.type === 'CommentBlock' && c.value.includes('*'));
|
|
955
|
-
})
|
|
956
|
-
.forEach(path => {
|
|
957
|
-
const funcName = path.node.id?.name || 'anonymous';
|
|
958
|
-
const params = path.node.params;
|
|
959
|
-
const isAsync = path.node.async;
|
|
960
|
-
|
|
961
|
-
// Generate JSDoc
|
|
962
|
-
let jsdoc = '/**\n';
|
|
963
|
-
jsdoc += ` * ${this.generateFunctionDescription(funcName)}\n`;
|
|
964
|
-
|
|
965
|
-
params.forEach(param => {
|
|
966
|
-
const paramName = param.type === 'Identifier' ? param.name : 'param';
|
|
967
|
-
jsdoc += ` * @param {*} ${paramName} - ${this.generateParamDescription(paramName)}\n`;
|
|
968
|
-
});
|
|
969
|
-
|
|
970
|
-
jsdoc += ` * @returns {${isAsync ? 'Promise<*>' : '*'}} ${this.generateReturnDescription(funcName)}\n`;
|
|
971
|
-
jsdoc += ' */';
|
|
972
|
-
|
|
973
|
-
// Add JSDoc comment
|
|
974
|
-
path.node.leadingComments = [
|
|
975
|
-
j.commentBlock(jsdoc.replace('/**', '*').replace('*/', ''), true)
|
|
976
|
-
];
|
|
977
|
-
|
|
978
|
-
improved = true;
|
|
979
|
-
changes.push(`Added JSDoc to ${funcName}`);
|
|
980
|
-
improvements.push({
|
|
981
|
-
type: 'documentation',
|
|
982
|
-
description: `Generated JSDoc for function ${funcName}`,
|
|
983
|
-
line: path.node.loc?.start.line || 0
|
|
984
|
-
});
|
|
985
|
-
});
|
|
986
|
-
|
|
987
|
-
// Add JSDoc to classes
|
|
988
|
-
ast.find(j.ClassDeclaration)
|
|
989
|
-
.filter(path => {
|
|
990
|
-
const comments = path.node.leadingComments || [];
|
|
991
|
-
return !comments.some(c => c.type === 'CommentBlock' && c.value.includes('*'));
|
|
992
|
-
})
|
|
993
|
-
.forEach(path => {
|
|
994
|
-
const className = path.node.id?.name || 'Class';
|
|
995
|
-
|
|
996
|
-
let jsdoc = '/**\n';
|
|
997
|
-
jsdoc += ` * ${this.generateClassDescription(className)}\n`;
|
|
998
|
-
jsdoc += ' */';
|
|
999
|
-
|
|
1000
|
-
path.node.leadingComments = [
|
|
1001
|
-
j.commentBlock(jsdoc.replace('/**', '*').replace('*/', ''), true)
|
|
1002
|
-
];
|
|
1003
|
-
|
|
1004
|
-
improved = true;
|
|
1005
|
-
changes.push(`Added JSDoc to class ${className}`);
|
|
1006
|
-
improvements.push({
|
|
1007
|
-
type: 'documentation',
|
|
1008
|
-
description: `Generated JSDoc for class ${className}`,
|
|
1009
|
-
line: path.node.loc?.start.line || 0
|
|
1010
|
-
});
|
|
1011
|
-
});
|
|
1012
|
-
|
|
1013
|
-
const result = ast.toSource();
|
|
1014
|
-
|
|
1015
|
-
return {
|
|
1016
|
-
improved,
|
|
1017
|
-
content: improved ? result : content,
|
|
1018
|
-
changes,
|
|
1019
|
-
improvements
|
|
1020
|
-
};
|
|
1021
|
-
|
|
1022
|
-
} catch (_error) {
|
|
1023
|
-
return { improved: false, content, error: error.message };
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
|
-
// Helper methods
|
|
1028
|
-
|
|
1029
|
-
async loadESLintConfig() {
|
|
1030
|
-
try {
|
|
1031
|
-
const configPath = path.join(this.rootPath, '.eslintrc.json');
|
|
1032
|
-
const content = await fs.readFile(configPath, 'utf-8');
|
|
1033
|
-
return JSON.parse(content);
|
|
1034
|
-
} catch (_error) {
|
|
1035
|
-
// Return default config
|
|
1036
|
-
return {
|
|
1037
|
-
env: {
|
|
1038
|
-
es2021: true,
|
|
1039
|
-
node: true
|
|
1040
|
-
},
|
|
1041
|
-
extends: ['eslint:recommended'],
|
|
1042
|
-
parserOptions: {
|
|
1043
|
-
ecmaVersion: 12,
|
|
1044
|
-
sourceType: 'module'
|
|
1045
|
-
},
|
|
1046
|
-
rules: {
|
|
1047
|
-
'no-unused-vars': 'error',
|
|
1048
|
-
'no-console': 'warn',
|
|
1049
|
-
'semi': ['error', 'always'],
|
|
1050
|
-
'quotes': ['error', 'single']
|
|
1051
|
-
}
|
|
1052
|
-
};
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
|
-
async loadPrettierConfig() {
|
|
1057
|
-
try {
|
|
1058
|
-
const configPath = path.join(this.rootPath, '.prettierrc');
|
|
1059
|
-
const content = await fs.readFile(configPath, 'utf-8');
|
|
1060
|
-
return JSON.parse(content);
|
|
1061
|
-
} catch (_error) {
|
|
1062
|
-
// Return default config
|
|
1063
|
-
return {
|
|
1064
|
-
semi: true,
|
|
1065
|
-
singleQuote: true,
|
|
1066
|
-
tabWidth: 2,
|
|
1067
|
-
trailingComma: 'es5',
|
|
1068
|
-
printWidth: 80
|
|
1069
|
-
};
|
|
1070
|
-
}
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
calculateCyclomaticComplexity(content) {
|
|
1074
|
-
// Simplified complexity calculation
|
|
1075
|
-
let complexity = 1;
|
|
1076
|
-
|
|
1077
|
-
const complexityPatterns = [
|
|
1078
|
-
/\bif\s*\(/g,
|
|
1079
|
-
/\belse\s+if\s*\(/g,
|
|
1080
|
-
/\bwhile\s*\(/g,
|
|
1081
|
-
/\bfor\s*\(/g,
|
|
1082
|
-
/\bcase\s+/g,
|
|
1083
|
-
/\bcatch\s*\(/g,
|
|
1084
|
-
/\?\s*[^:]+\s*:/g, // ternary
|
|
1085
|
-
/\|\|/g,
|
|
1086
|
-
/&&/g
|
|
1087
|
-
];
|
|
1088
|
-
|
|
1089
|
-
complexityPatterns.forEach(pattern => {
|
|
1090
|
-
const matches = content.match(pattern);
|
|
1091
|
-
if (matches) {
|
|
1092
|
-
complexity += matches.length;
|
|
1093
|
-
}
|
|
1094
|
-
});
|
|
1095
|
-
|
|
1096
|
-
return complexity;
|
|
1097
|
-
}
|
|
1098
|
-
|
|
1099
|
-
calculateMaintainabilityIndex(content) {
|
|
1100
|
-
// Simplified maintainability index (0-100)
|
|
1101
|
-
const lines = content.split('\n').length;
|
|
1102
|
-
const complexity = this.calculateCyclomaticComplexity(content);
|
|
1103
|
-
const comments = (content.match(/\/\//g) || []).length +
|
|
1104
|
-
(content.match(/\/\*/g) || []).length;
|
|
1105
|
-
|
|
1106
|
-
// Simple formula
|
|
1107
|
-
const commentRatio = comments / lines;
|
|
1108
|
-
const complexityRatio = complexity / lines;
|
|
1109
|
-
|
|
1110
|
-
const maintainability = Math.min(100, Math.max(0,
|
|
1111
|
-
100 - (complexityRatio * 50) + (commentRatio * 20)
|
|
1112
|
-
));
|
|
1113
|
-
|
|
1114
|
-
return Math.round(maintainability);
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
|
-
calculateDocumentationCoverage(content) {
|
|
1118
|
-
// Calculate percentage of documented functions/classes
|
|
1119
|
-
const functionMatches = content.match(/function\s+\w+|class\s+\w+/g) || [];
|
|
1120
|
-
const jsdocMatches = content.match(/\/\*\*[\s\S]*?\*\//g) || [];
|
|
1121
|
-
|
|
1122
|
-
if (functionMatches.length === 0) return 100;
|
|
1123
|
-
|
|
1124
|
-
const coverage = (jsdocMatches.length / functionMatches.length) * 100;
|
|
1125
|
-
return Math.min(100, Math.round(coverage));
|
|
1126
|
-
}
|
|
1127
|
-
|
|
1128
|
-
calculateImprovementScore(before, after) {
|
|
1129
|
-
const improvements = {
|
|
1130
|
-
issues: Math.max(0, before.issues - after.issues),
|
|
1131
|
-
complexity: Math.max(0, before.complexity - after.complexity),
|
|
1132
|
-
maintainability: Math.max(0, after.maintainability - before.maintainability),
|
|
1133
|
-
coverage: Math.max(0, after.coverage - before.coverage)
|
|
1134
|
-
};
|
|
1135
|
-
|
|
1136
|
-
// Calculate weighted score
|
|
1137
|
-
const score = (
|
|
1138
|
-
improvements.issues * 3 +
|
|
1139
|
-
improvements.complexity * 2 +
|
|
1140
|
-
improvements.maintainability +
|
|
1141
|
-
improvements.coverage * 0.5
|
|
1142
|
-
) / 6.5;
|
|
1143
|
-
|
|
1144
|
-
return Math.round(score * 10) / 10;
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1147
|
-
summarizeLintFixes(lintResult) {
|
|
1148
|
-
const fixedRules = new Map();
|
|
1149
|
-
|
|
1150
|
-
lintResult.messages.forEach(message => {
|
|
1151
|
-
if (message.fix) {
|
|
1152
|
-
const count = fixedRules.get(message.ruleId) || 0;
|
|
1153
|
-
fixedRules.set(message.ruleId, count + 1);
|
|
1154
|
-
}
|
|
1155
|
-
});
|
|
1156
|
-
|
|
1157
|
-
return Array.from(fixedRules.entries()).map(([rule, count]) =>
|
|
1158
|
-
`Fixed ${count} ${rule} issue${count > 1 ? 's' : ''}`
|
|
1159
|
-
);
|
|
1160
|
-
}
|
|
1161
|
-
|
|
1162
|
-
isVariableReassigned(j, variableDeclarator) {
|
|
1163
|
-
const varName = variableDeclarator.node.id.name;
|
|
1164
|
-
const scope = variableDeclarator.scope;
|
|
1165
|
-
|
|
1166
|
-
let reassigned = false;
|
|
1167
|
-
|
|
1168
|
-
j(scope.path).find(j.AssignmentExpression)
|
|
1169
|
-
.filter(path => {
|
|
1170
|
-
return path.node.left.type === 'Identifier' &&
|
|
1171
|
-
path.node.left.name === varName;
|
|
1172
|
-
})
|
|
1173
|
-
.forEach(() => {
|
|
1174
|
-
reassigned = true;
|
|
1175
|
-
});
|
|
1176
|
-
|
|
1177
|
-
return reassigned;
|
|
1178
|
-
}
|
|
1179
|
-
|
|
1180
|
-
isExported(_path) {
|
|
1181
|
-
// Check if the declaration is exported
|
|
1182
|
-
const parent = path.parent;
|
|
1183
|
-
|
|
1184
|
-
return parent.node.type === 'ExportNamedDeclaration' ||
|
|
1185
|
-
parent.node.type === 'ExportDefaultDeclaration' ||
|
|
1186
|
-
(parent.node.type === 'AssignmentExpression' &&
|
|
1187
|
-
parent.node.left.type === 'MemberExpression' &&
|
|
1188
|
-
parent.node.left.object.name === 'module' &&
|
|
1189
|
-
parent.node.left.property.name === 'exports');
|
|
1190
|
-
}
|
|
1191
|
-
|
|
1192
|
-
isOnlyConsoleLog(statement) {
|
|
1193
|
-
return statement.type === 'ExpressionStatement' &&
|
|
1194
|
-
statement.expression.type === 'CallExpression' &&
|
|
1195
|
-
statement.expression.callee.type === 'MemberExpression' &&
|
|
1196
|
-
statement.expression.callee.object.name === 'console';
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
|
-
isKnownSafeObject(name) {
|
|
1200
|
-
const safeObjects = ['console', 'Math', 'JSON', 'Object', 'Array', 'String', 'Number'];
|
|
1201
|
-
return safeObjects.includes(name);
|
|
1202
|
-
}
|
|
1203
|
-
|
|
1204
|
-
snakeToCamel(str) {
|
|
1205
|
-
return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
1206
|
-
}
|
|
1207
|
-
|
|
1208
|
-
shouldBeConstantCase(declarator) {
|
|
1209
|
-
// Check if the value is a literal or simple value
|
|
1210
|
-
const init = declarator.init;
|
|
1211
|
-
if (!init) return false;
|
|
1212
|
-
|
|
1213
|
-
return init.type === 'Literal' ||
|
|
1214
|
-
init.type === 'UnaryExpression' ||
|
|
1215
|
-
(init.type === 'Identifier' && init.name === init.name.toUpperCase());
|
|
1216
|
-
}
|
|
1217
|
-
|
|
1218
|
-
isConstantCase(name) {
|
|
1219
|
-
return /^[A-Z_]+$/.test(name);
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1222
|
-
toConstantCase(name) {
|
|
1223
|
-
// Convert camelCase or snake_case to CONSTANT_CASE
|
|
1224
|
-
return name
|
|
1225
|
-
.replace(/([a-z])([A-Z])/g, '$1_$2')
|
|
1226
|
-
.replace(/[_-]+/g, '_')
|
|
1227
|
-
.toUpperCase();
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
|
-
generateFunctionDescription(funcName) {
|
|
1231
|
-
// Generate meaningful description based on function name
|
|
1232
|
-
const words = funcName.split(/(?=[A-Z])/);
|
|
1233
|
-
return words.map(w => w.toLowerCase()).join(' ').replace(/^\w/, c => c.toUpperCase());
|
|
1234
|
-
}
|
|
1235
|
-
|
|
1236
|
-
generateParamDescription(paramName) {
|
|
1237
|
-
// Generate parameter description
|
|
1238
|
-
return `The ${paramName} parameter`;
|
|
1239
|
-
}
|
|
1240
|
-
|
|
1241
|
-
generateReturnDescription(funcName) {
|
|
1242
|
-
// Generate return description
|
|
1243
|
-
if (funcName.startsWith('get')) return 'The requested value';
|
|
1244
|
-
if (funcName.startsWith('is')) return 'True if condition is met, false otherwise';
|
|
1245
|
-
if (funcName.startsWith('has')) return 'True if exists, false otherwise';
|
|
1246
|
-
return 'The result of the operation';
|
|
1247
|
-
}
|
|
1248
|
-
|
|
1249
|
-
generateClassDescription(className) {
|
|
1250
|
-
// Generate class description
|
|
1251
|
-
const words = className.split(/(?=[A-Z])/);
|
|
1252
|
-
return `${words.join(' ')} class`;
|
|
1253
|
-
}
|
|
1254
|
-
|
|
1255
|
-
/**
|
|
1256
|
-
* Apply improvements to file
|
|
1257
|
-
*/
|
|
1258
|
-
async applyImprovements(_filePath, improvedContent, backup = true) {
|
|
1259
|
-
try {
|
|
1260
|
-
if (backup) {
|
|
1261
|
-
// Create backup
|
|
1262
|
-
const backupPath = `${_filePath}.backup.${Date.now()}`;
|
|
1263
|
-
const originalContent = await fs.readFile(_filePath, 'utf-8');
|
|
1264
|
-
await fs.writeFile(backupPath, originalContent);
|
|
1265
|
-
console.log(chalk.gray(`Backup created: ${backupPath}`));
|
|
1266
|
-
}
|
|
1267
|
-
|
|
1268
|
-
// Write improved content
|
|
1269
|
-
await fs.writeFile(_filePath, improvedContent);
|
|
1270
|
-
console.log(chalk.green(`✅ Improvements applied to: ${_filePath}`));
|
|
1271
|
-
|
|
1272
|
-
return { success: true };
|
|
1273
|
-
} catch (_error) {
|
|
1274
|
-
console.error(chalk.red(`Failed to apply improvements: ${error.message}`));
|
|
1275
|
-
return { success: false, error: error.message };
|
|
1276
|
-
}
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
/**
|
|
1280
|
-
* Get improvement statistics
|
|
1281
|
-
*/
|
|
1282
|
-
getStatistics() {
|
|
1283
|
-
const stats = {
|
|
1284
|
-
filesAnalyzed: this.metrics.size,
|
|
1285
|
-
totalImprovements: 0,
|
|
1286
|
-
byType: {},
|
|
1287
|
-
averageScore: 0
|
|
1288
|
-
};
|
|
1289
|
-
|
|
1290
|
-
let totalScore = 0;
|
|
1291
|
-
|
|
1292
|
-
for (const [file, metrics] of this.metrics) {
|
|
1293
|
-
if (metrics.after) {
|
|
1294
|
-
const score = this.calculateImprovementScore(metrics.before, metrics.after);
|
|
1295
|
-
totalScore += score;
|
|
1296
|
-
}
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
|
-
stats.averageScore = this.metrics.size > 0 ?
|
|
1300
|
-
(totalScore / this.metrics.size).toFixed(2) : 0;
|
|
1301
|
-
|
|
1302
|
-
// Count improvements by type
|
|
1303
|
-
for (const improvement of this.improvements) {
|
|
1304
|
-
stats.byType[improvement.type] = (stats.byType[improvement.type] || 0) + 1;
|
|
1305
|
-
stats.totalImprovements++;
|
|
1306
|
-
}
|
|
1307
|
-
|
|
1308
|
-
return stats;
|
|
1309
|
-
}
|
|
1310
|
-
}
|
|
1311
|
-
|
|
1
|
+
const fs = require('fs').promises;
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const { ESLint } = require('eslint');
|
|
5
|
+
const prettier = require('prettier');
|
|
6
|
+
const jscodeshift = require('jscodeshift');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Automated code quality improvement system
|
|
10
|
+
* Applies automatic fixes and improvements to enhance code quality
|
|
11
|
+
*/
|
|
12
|
+
class CodeQualityImprover {
|
|
13
|
+
constructor(options = {}) {
|
|
14
|
+
this.rootPath = options.rootPath || process.cwd();
|
|
15
|
+
this.improvements = [];
|
|
16
|
+
this.metrics = new Map();
|
|
17
|
+
this.eslint = null;
|
|
18
|
+
this.prettierConfig = null;
|
|
19
|
+
this.improvementPatterns = new Map();
|
|
20
|
+
this.initializePatterns();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Initialize improvement patterns
|
|
25
|
+
*/
|
|
26
|
+
initializePatterns() {
|
|
27
|
+
// Code formatting improvements
|
|
28
|
+
this.improvementPatterns.set('formatting', {
|
|
29
|
+
name: 'Code Formatting',
|
|
30
|
+
description: 'Apply consistent code formatting',
|
|
31
|
+
improver: this.improveFormatting.bind(this),
|
|
32
|
+
priority: 'medium',
|
|
33
|
+
automatic: true
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Linting fixes
|
|
37
|
+
this.improvementPatterns.set('linting', {
|
|
38
|
+
name: 'Linting Fixes',
|
|
39
|
+
description: 'Fix linting errors and warnings',
|
|
40
|
+
improver: this.improveLinting.bind(this),
|
|
41
|
+
priority: 'high',
|
|
42
|
+
automatic: true
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Modern syntax upgrades
|
|
46
|
+
this.improvementPatterns.set('modern_syntax', {
|
|
47
|
+
name: 'Modern Syntax',
|
|
48
|
+
description: 'Upgrade to modern JavaScript syntax',
|
|
49
|
+
improver: this.upgradeToModernSyntax.bind(this),
|
|
50
|
+
priority: 'medium',
|
|
51
|
+
automatic: true
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Import optimization
|
|
55
|
+
this.improvementPatterns.set('optimize_imports', {
|
|
56
|
+
name: 'Optimize Imports',
|
|
57
|
+
description: 'Organize and optimize import statements',
|
|
58
|
+
improver: this.optimizeImports.bind(this),
|
|
59
|
+
priority: 'low',
|
|
60
|
+
automatic: true
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Dead code elimination
|
|
64
|
+
this.improvementPatterns.set('remove_unused', {
|
|
65
|
+
name: 'Remove Unused Code',
|
|
66
|
+
description: 'Remove unused variables and functions',
|
|
67
|
+
improver: this.removeUnusedCode.bind(this),
|
|
68
|
+
priority: 'high',
|
|
69
|
+
automatic: false
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Consistent naming
|
|
73
|
+
this.improvementPatterns.set('naming_conventions', {
|
|
74
|
+
name: 'Naming Conventions',
|
|
75
|
+
description: 'Apply consistent naming conventions',
|
|
76
|
+
improver: this.improveNaming.bind(this),
|
|
77
|
+
priority: 'medium',
|
|
78
|
+
automatic: false
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Error handling improvements
|
|
82
|
+
this.improvementPatterns.set('error_handling', {
|
|
83
|
+
name: 'Error Handling',
|
|
84
|
+
description: 'Improve error handling patterns',
|
|
85
|
+
improver: this.improveErrorHandling.bind(this),
|
|
86
|
+
priority: 'high',
|
|
87
|
+
automatic: false
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Async/await conversion
|
|
91
|
+
this.improvementPatterns.set('async_await', {
|
|
92
|
+
name: 'Async/Await Conversion',
|
|
93
|
+
description: 'Convert promises to async/await',
|
|
94
|
+
improver: this.convertToAsyncAwait.bind(this),
|
|
95
|
+
priority: 'medium',
|
|
96
|
+
automatic: true
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Type safety improvements
|
|
100
|
+
this.improvementPatterns.set('type_safety', {
|
|
101
|
+
name: 'Type Safety',
|
|
102
|
+
description: 'Add type checks and validations',
|
|
103
|
+
improver: this.improveTypeSafety.bind(this),
|
|
104
|
+
priority: 'high',
|
|
105
|
+
automatic: false
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Documentation generation
|
|
109
|
+
this.improvementPatterns.set('documentation', {
|
|
110
|
+
name: 'Documentation',
|
|
111
|
+
description: 'Generate missing JSDoc comments',
|
|
112
|
+
improver: this.generateDocumentation.bind(this),
|
|
113
|
+
priority: 'medium',
|
|
114
|
+
automatic: false
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Initialize tools
|
|
120
|
+
*/
|
|
121
|
+
async initialize() {
|
|
122
|
+
// Initialize ESLint
|
|
123
|
+
this.eslint = new ESLint({
|
|
124
|
+
fix: true,
|
|
125
|
+
baseConfig: await this.loadESLintConfig(),
|
|
126
|
+
useEslintrc: true
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Load Prettier config
|
|
130
|
+
this.prettierConfig = await this.loadPrettierConfig();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Analyze and improve code quality
|
|
135
|
+
*/
|
|
136
|
+
async improveCode(_filePath, options = {}) {
|
|
137
|
+
console.log(chalk.blue(`🎯 Improving: ${_filePath}`));
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
const content = await fs.readFile(_filePath, 'utf-8');
|
|
141
|
+
const fileType = path.extname(_filePath);
|
|
142
|
+
|
|
143
|
+
if (!['.js', '.jsx', '.ts', '.tsx'].includes(fileType)) {
|
|
144
|
+
return {
|
|
145
|
+
_filePath,
|
|
146
|
+
improvements: [],
|
|
147
|
+
error: 'Unsupported file type'
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Calculate initial metrics
|
|
152
|
+
const initialMetrics = await this.calculateMetrics(content, filePath);
|
|
153
|
+
this.metrics.set(_filePath, { before: initialMetrics });
|
|
154
|
+
|
|
155
|
+
// Clear previous improvements
|
|
156
|
+
this.improvements = [];
|
|
157
|
+
|
|
158
|
+
let improvedContent = content;
|
|
159
|
+
const appliedImprovements = [];
|
|
160
|
+
|
|
161
|
+
// Apply improvement patterns
|
|
162
|
+
for (const [patternId, pattern] of this.improvementPatterns) {
|
|
163
|
+
if (options.patterns && !options.patterns.includes(patternId)) {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (!options.manual && !pattern.automatic) {
|
|
168
|
+
continue; // Skip manual improvements in automatic mode
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
const result = await pattern.improver(improvedContent, _filePath, options);
|
|
173
|
+
|
|
174
|
+
if (result.improved) {
|
|
175
|
+
improvedContent = result.content;
|
|
176
|
+
appliedImprovements.push({
|
|
177
|
+
patternId,
|
|
178
|
+
pattern: pattern.name,
|
|
179
|
+
priority: pattern.priority,
|
|
180
|
+
changes: result.changes,
|
|
181
|
+
impact: result.impact || 'medium'
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
this.improvements.push(...result.improvements || []);
|
|
185
|
+
}
|
|
186
|
+
} catch (_error) {
|
|
187
|
+
console.warn(chalk.yellow(`Failed to apply ${pattern.name}: ${error.message}`));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Calculate final metrics
|
|
192
|
+
const finalMetrics = await this.calculateMetrics(improvedContent, filePath);
|
|
193
|
+
this.metrics.get(_filePath).after = finalMetrics;
|
|
194
|
+
|
|
195
|
+
// Calculate improvement score
|
|
196
|
+
const improvementScore = this.calculateImprovementScore(initialMetrics, finalMetrics);
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
_filePath,
|
|
200
|
+
originalContent: content,
|
|
201
|
+
improvedContent,
|
|
202
|
+
improvements: appliedImprovements,
|
|
203
|
+
metrics: {
|
|
204
|
+
before: initialMetrics,
|
|
205
|
+
after: finalMetrics,
|
|
206
|
+
improvementScore
|
|
207
|
+
},
|
|
208
|
+
changed: content !== improvedContent
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
} catch (_error) {
|
|
212
|
+
return {
|
|
213
|
+
_filePath,
|
|
214
|
+
improvements: [],
|
|
215
|
+
error: error.message
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Calculate code metrics
|
|
222
|
+
*/
|
|
223
|
+
async calculateMetrics(content, filePath) {
|
|
224
|
+
const metrics = {
|
|
225
|
+
lines: content.split('\n').length,
|
|
226
|
+
complexity: 0,
|
|
227
|
+
maintainability: 0,
|
|
228
|
+
issues: 0,
|
|
229
|
+
coverage: 0
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
// Linting issues
|
|
234
|
+
const lintResults = await this.eslint.lintText(content, { _filePath });
|
|
235
|
+
metrics.issues = lintResults[0]?.errorCount + lintResults[0]?.warningCount || 0;
|
|
236
|
+
|
|
237
|
+
// Cyclomatic complexity (simplified)
|
|
238
|
+
metrics.complexity = this.calculateCyclomaticComplexity(content);
|
|
239
|
+
|
|
240
|
+
// Maintainability index (simplified)
|
|
241
|
+
metrics.maintainability = this.calculateMaintainabilityIndex(content);
|
|
242
|
+
|
|
243
|
+
// Documentation coverage
|
|
244
|
+
metrics.coverage = this.calculateDocumentationCoverage(content);
|
|
245
|
+
|
|
246
|
+
} catch (_error) {
|
|
247
|
+
// Metrics calculation failed, use defaults
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return metrics;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Improvement functions
|
|
254
|
+
|
|
255
|
+
async improveFormatting(content, _filePath, options) {
|
|
256
|
+
try {
|
|
257
|
+
const formatted = await prettier.format(content, {
|
|
258
|
+
...this.prettierConfig,
|
|
259
|
+
filepath: filePath
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
improved: formatted !== content,
|
|
264
|
+
content: formatted,
|
|
265
|
+
changes: formatted !== content ? ['Applied consistent formatting'] : [],
|
|
266
|
+
improvements: [{
|
|
267
|
+
type: 'formatting',
|
|
268
|
+
description: 'Applied Prettier formatting',
|
|
269
|
+
line: 0
|
|
270
|
+
}]
|
|
271
|
+
};
|
|
272
|
+
} catch (_error) {
|
|
273
|
+
return { improved: false, content, error: error.message };
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async improveLinting(content, _filePath, options) {
|
|
278
|
+
try {
|
|
279
|
+
const results = await this.eslint.lintText(content, { _filePath });
|
|
280
|
+
|
|
281
|
+
if (results[0]?.output) {
|
|
282
|
+
return {
|
|
283
|
+
improved: true,
|
|
284
|
+
content: results[0].output,
|
|
285
|
+
changes: this.summarizeLintFixes(results[0]),
|
|
286
|
+
improvements: results[0].messages.map(msg => ({
|
|
287
|
+
type: 'linting',
|
|
288
|
+
description: msg.message,
|
|
289
|
+
line: msg.line,
|
|
290
|
+
severity: msg.severity === 2 ? 'error' : 'warning'
|
|
291
|
+
}))
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return { improved: false, content };
|
|
296
|
+
} catch (_error) {
|
|
297
|
+
return { improved: false, content, error: error.message };
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async upgradeToModernSyntax(content, _filePath, options) {
|
|
302
|
+
const j = jscodeshift;
|
|
303
|
+
let improved = false;
|
|
304
|
+
const changes = [];
|
|
305
|
+
|
|
306
|
+
try {
|
|
307
|
+
// Convert var to let/const
|
|
308
|
+
let ast = j(content);
|
|
309
|
+
const _varToLetConst = ast
|
|
310
|
+
.find(j.VariableDeclaration, { kind: 'var' })
|
|
311
|
+
.forEach(path => {
|
|
312
|
+
const isReassigned = this.isVariableReassigned(j, path);
|
|
313
|
+
path.node.kind = isReassigned ? 'let' : 'const';
|
|
314
|
+
improved = true;
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
if (improved) {
|
|
318
|
+
changes.push('Converted var to let/const');
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Convert function expressions to arrow functions (where appropriate)
|
|
322
|
+
ast.find(j.FunctionExpression)
|
|
323
|
+
.filter(path => {
|
|
324
|
+
// Don't convert if it uses 'this' or 'arguments'
|
|
325
|
+
const usesThis = j(_path).find(j.ThisExpression).length > 0;
|
|
326
|
+
const usesArguments = j(_path).find(j.Identifier, { name: 'arguments' }).length > 0;
|
|
327
|
+
return !usesThis && !usesArguments && !path.node.id;
|
|
328
|
+
})
|
|
329
|
+
.forEach(path => {
|
|
330
|
+
const arrowFunction = j.arrowFunctionExpression(
|
|
331
|
+
path.node.params,
|
|
332
|
+
path.node.body,
|
|
333
|
+
path.node.body.type !== 'BlockStatement'
|
|
334
|
+
);
|
|
335
|
+
j(_path).replaceWith(arrowFunction);
|
|
336
|
+
improved = true;
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
if (changes.length > 0) {
|
|
340
|
+
changes.push('Converted function expressions to arrow functions');
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Template literals
|
|
344
|
+
ast.find(j.BinaryExpression, { operator: '+' })
|
|
345
|
+
.filter(path => {
|
|
346
|
+
// Check if it's string concatenation
|
|
347
|
+
return path.node.left.type === 'Literal' || path.node.right.type === 'Literal';
|
|
348
|
+
})
|
|
349
|
+
.forEach(path => {
|
|
350
|
+
// Convert to template literal (simplified)
|
|
351
|
+
improved = true;
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
const result = ast.toSource();
|
|
355
|
+
|
|
356
|
+
return {
|
|
357
|
+
improved,
|
|
358
|
+
content: improved ? result : content,
|
|
359
|
+
changes,
|
|
360
|
+
improvements: changes.map(change => ({
|
|
361
|
+
type: 'modern_syntax',
|
|
362
|
+
description: change,
|
|
363
|
+
line: 0
|
|
364
|
+
}))
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
} catch (_error) {
|
|
368
|
+
return { improved: false, content, error: error.message };
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
async optimizeImports(content, _filePath, options) {
|
|
373
|
+
const j = jscodeshift;
|
|
374
|
+
let improved = false;
|
|
375
|
+
const changes = [];
|
|
376
|
+
|
|
377
|
+
try {
|
|
378
|
+
const ast = j(content);
|
|
379
|
+
|
|
380
|
+
// Group imports by source
|
|
381
|
+
const imports = new Map();
|
|
382
|
+
|
|
383
|
+
ast.find(j.ImportDeclaration)
|
|
384
|
+
.forEach(path => {
|
|
385
|
+
const source = path.node.source.value;
|
|
386
|
+
if (!imports.has(source)) {
|
|
387
|
+
imports.set(source, []);
|
|
388
|
+
}
|
|
389
|
+
imports.get(source).push(_path);
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// Merge imports from same source
|
|
393
|
+
for (const [source, importPaths] of imports) {
|
|
394
|
+
if (importPaths.length > 1) {
|
|
395
|
+
// Merge specifiers
|
|
396
|
+
const allSpecifiers = [];
|
|
397
|
+
importPaths.forEach(path => {
|
|
398
|
+
allSpecifiers.push(...path.node.specifiers);
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
// Keep first import, remove others
|
|
402
|
+
importPaths[0].node.specifiers = allSpecifiers;
|
|
403
|
+
for (let i = 1; i < importPaths.length; i++) {
|
|
404
|
+
j(importPaths[i]).remove();
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
improved = true;
|
|
408
|
+
changes.push(`Merged imports from ${source}`);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Sort imports
|
|
413
|
+
const importNodes = [];
|
|
414
|
+
ast.find(j.ImportDeclaration)
|
|
415
|
+
.forEach(path => {
|
|
416
|
+
importNodes.push(path.node);
|
|
417
|
+
j(_path).remove();
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
if (importNodes.length > 0) {
|
|
421
|
+
// Sort by: external packages, internal absolute, internal relative
|
|
422
|
+
importNodes.sort((a, b) => {
|
|
423
|
+
const aSource = a.source.value;
|
|
424
|
+
const bSource = b.source.value;
|
|
425
|
+
|
|
426
|
+
const aExternal = !aSource.startsWith('.') && !aSource.startsWith('/');
|
|
427
|
+
const bExternal = !bSource.startsWith('.') && !bSource.startsWith('/');
|
|
428
|
+
|
|
429
|
+
if (aExternal && !bExternal) return -1;
|
|
430
|
+
if (!aExternal && bExternal) return 1;
|
|
431
|
+
|
|
432
|
+
return aSource.localeCompare(bSource);
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
// Re-insert sorted imports at the beginning
|
|
436
|
+
const program = ast.find(j.Program);
|
|
437
|
+
importNodes.reverse().forEach(node => {
|
|
438
|
+
program.get('body').unshift(node);
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
improved = true;
|
|
442
|
+
changes.push('Sorted imports');
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const result = ast.toSource();
|
|
446
|
+
|
|
447
|
+
return {
|
|
448
|
+
improved,
|
|
449
|
+
content: improved ? result : content,
|
|
450
|
+
changes,
|
|
451
|
+
improvements: changes.map(change => ({
|
|
452
|
+
type: 'optimize_imports',
|
|
453
|
+
description: change,
|
|
454
|
+
line: 0
|
|
455
|
+
}))
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
} catch (_error) {
|
|
459
|
+
return { improved: false, content, error: error.message };
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
async removeUnusedCode(content, _filePath, options) {
|
|
464
|
+
const j = jscodeshift;
|
|
465
|
+
let improved = false;
|
|
466
|
+
const changes = [];
|
|
467
|
+
const improvements = [];
|
|
468
|
+
|
|
469
|
+
try {
|
|
470
|
+
const ast = j(content);
|
|
471
|
+
|
|
472
|
+
// Find all variable declarations and their usage
|
|
473
|
+
const declaredVars = new Map();
|
|
474
|
+
const usedVars = new Set();
|
|
475
|
+
|
|
476
|
+
// Collect declarations
|
|
477
|
+
ast.find(j.VariableDeclarator)
|
|
478
|
+
.forEach(path => {
|
|
479
|
+
if (path.node.id.type === 'Identifier') {
|
|
480
|
+
declaredVars.set(path.node.id.name, path);
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
// Collect usage
|
|
485
|
+
ast.find(j.Identifier)
|
|
486
|
+
.filter(path => {
|
|
487
|
+
// Only count as used if it's not the declaration itself
|
|
488
|
+
const parent = path.parent.node;
|
|
489
|
+
return !(parent.type === 'VariableDeclarator' && parent.id === path.node);
|
|
490
|
+
})
|
|
491
|
+
.forEach(path => {
|
|
492
|
+
usedVars.add(path.node.name);
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
// Remove unused variables
|
|
496
|
+
for (const [varName, declaratorPath] of declaredVars) {
|
|
497
|
+
if (!usedVars.has(varName) && !this.isExported(declaratorPath)) {
|
|
498
|
+
const declaration = declaratorPath.parent;
|
|
499
|
+
|
|
500
|
+
if (declaration.node.declarations.length === 1) {
|
|
501
|
+
// Remove entire declaration
|
|
502
|
+
j(declaration).remove();
|
|
503
|
+
} else {
|
|
504
|
+
// Remove just this declarator
|
|
505
|
+
j(declaratorPath).remove();
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
improved = true;
|
|
509
|
+
changes.push(`Removed unused variable: ${varName}`);
|
|
510
|
+
improvements.push({
|
|
511
|
+
type: 'remove_unused',
|
|
512
|
+
description: `Removed unused variable: ${varName}`,
|
|
513
|
+
line: declaratorPath.node.loc?.start.line || 0
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Find unused functions
|
|
519
|
+
const declaredFunctions = new Map();
|
|
520
|
+
const calledFunctions = new Set();
|
|
521
|
+
|
|
522
|
+
ast.find(j.FunctionDeclaration)
|
|
523
|
+
.forEach(path => {
|
|
524
|
+
if (path.node.id) {
|
|
525
|
+
declaredFunctions.set(path.node.id.name, path);
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
ast.find(j.CallExpression)
|
|
530
|
+
.forEach(path => {
|
|
531
|
+
if (path.node.callee.type === 'Identifier') {
|
|
532
|
+
calledFunctions.add(path.node.callee.name);
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
// Remove unused functions
|
|
537
|
+
for (const [funcName, funcPath] of declaredFunctions) {
|
|
538
|
+
if (!calledFunctions.has(funcName) && !this.isExported(funcPath)) {
|
|
539
|
+
j(funcPath).remove();
|
|
540
|
+
improved = true;
|
|
541
|
+
changes.push(`Removed unused function: ${funcName}`);
|
|
542
|
+
improvements.push({
|
|
543
|
+
type: 'remove_unused',
|
|
544
|
+
description: `Removed unused function: ${funcName}`,
|
|
545
|
+
line: funcPath.node.loc?.start.line || 0
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
const result = ast.toSource();
|
|
551
|
+
|
|
552
|
+
return {
|
|
553
|
+
improved,
|
|
554
|
+
content: improved ? result : content,
|
|
555
|
+
changes,
|
|
556
|
+
improvements
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
} catch (_error) {
|
|
560
|
+
return { improved: false, content, error: error.message };
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
async improveNaming(content, _filePath, options) {
|
|
565
|
+
const j = jscodeshift;
|
|
566
|
+
let improved = false;
|
|
567
|
+
const changes = [];
|
|
568
|
+
const improvements = [];
|
|
569
|
+
|
|
570
|
+
try {
|
|
571
|
+
const ast = j(content);
|
|
572
|
+
|
|
573
|
+
// Convert snake_case to camelCase for variables and functions
|
|
574
|
+
ast.find(j.Identifier)
|
|
575
|
+
.filter(path => {
|
|
576
|
+
const name = path.node.name;
|
|
577
|
+
return name.includes('_') &&
|
|
578
|
+
(path.parent.node.type === 'VariableDeclarator' ||
|
|
579
|
+
path.parent.node.type === 'FunctionDeclaration');
|
|
580
|
+
})
|
|
581
|
+
.forEach(path => {
|
|
582
|
+
const oldName = path.node.name;
|
|
583
|
+
const newName = this.snakeToCamel(oldName);
|
|
584
|
+
|
|
585
|
+
// Rename all occurrences
|
|
586
|
+
ast.find(j.Identifier, { name: oldName })
|
|
587
|
+
.forEach(p => {
|
|
588
|
+
p.node.name = newName;
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
improved = true;
|
|
592
|
+
changes.push(`Renamed ${oldName} to ${newName}`);
|
|
593
|
+
improvements.push({
|
|
594
|
+
type: 'naming_conventions',
|
|
595
|
+
description: `Renamed ${oldName} to ${newName}`,
|
|
596
|
+
line: path.node.loc?.start.line || 0
|
|
597
|
+
});
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
// Ensure classes start with uppercase
|
|
601
|
+
ast.find(j.ClassDeclaration)
|
|
602
|
+
.filter(path => {
|
|
603
|
+
const name = path.node.id?.name;
|
|
604
|
+
return name && name[0] !== name[0].toUpperCase();
|
|
605
|
+
})
|
|
606
|
+
.forEach(path => {
|
|
607
|
+
const oldName = path.node.id.name;
|
|
608
|
+
const newName = oldName[0].toUpperCase() + oldName.slice(1);
|
|
609
|
+
|
|
610
|
+
// Rename all occurrences
|
|
611
|
+
ast.find(j.Identifier, { name: oldName })
|
|
612
|
+
.forEach(p => {
|
|
613
|
+
p.node.name = newName;
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
improved = true;
|
|
617
|
+
changes.push(`Renamed class ${oldName} to ${newName}`);
|
|
618
|
+
improvements.push({
|
|
619
|
+
type: 'naming_conventions',
|
|
620
|
+
description: `Renamed class ${oldName} to ${newName}`,
|
|
621
|
+
line: path.node.loc?.start.line || 0
|
|
622
|
+
});
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
// Ensure constants are UPPER_SNAKE_CASE
|
|
626
|
+
ast.find(j.VariableDeclaration, { kind: 'const' })
|
|
627
|
+
.forEach(path => {
|
|
628
|
+
path.node.declarations.forEach(declarator => {
|
|
629
|
+
if (declarator.id.type === 'Identifier') {
|
|
630
|
+
const name = declarator.id.name;
|
|
631
|
+
// Check if it looks like a constant (all caps or should be)
|
|
632
|
+
if (this.shouldBeConstantCase(declarator) && !this.isConstantCase(name)) {
|
|
633
|
+
const oldName = name;
|
|
634
|
+
const newName = this.toConstantCase(name);
|
|
635
|
+
|
|
636
|
+
// Rename all occurrences
|
|
637
|
+
ast.find(j.Identifier, { name: oldName })
|
|
638
|
+
.forEach(p => {
|
|
639
|
+
p.node.name = newName;
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
improved = true;
|
|
643
|
+
changes.push(`Renamed constant ${oldName} to ${newName}`);
|
|
644
|
+
improvements.push({
|
|
645
|
+
type: 'naming_conventions',
|
|
646
|
+
description: `Renamed constant ${oldName} to ${newName}`,
|
|
647
|
+
line: declarator.loc?.start.line || 0
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
});
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
const result = ast.toSource();
|
|
655
|
+
|
|
656
|
+
return {
|
|
657
|
+
improved,
|
|
658
|
+
content: improved ? result : content,
|
|
659
|
+
changes,
|
|
660
|
+
improvements
|
|
661
|
+
};
|
|
662
|
+
|
|
663
|
+
} catch (_error) {
|
|
664
|
+
return { improved: false, content, error: error.message };
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
async improveErrorHandling(content, _filePath, options) {
|
|
669
|
+
const j = jscodeshift;
|
|
670
|
+
let improved = false;
|
|
671
|
+
const changes = [];
|
|
672
|
+
const improvements = [];
|
|
673
|
+
|
|
674
|
+
try {
|
|
675
|
+
const ast = j(content);
|
|
676
|
+
|
|
677
|
+
// Add try-catch to async functions without error handling
|
|
678
|
+
ast.find(j.FunctionDeclaration)
|
|
679
|
+
.filter(path => path.node.async)
|
|
680
|
+
.forEach(path => {
|
|
681
|
+
const hasErrorHandling = j(_path).find(j.TryStatement).length > 0;
|
|
682
|
+
|
|
683
|
+
if (!hasErrorHandling && path.node.body.body.length > 0) {
|
|
684
|
+
// Wrap body in try-catch
|
|
685
|
+
const originalBody = path.node.body.body;
|
|
686
|
+
const tryStatement = j.tryStatement(
|
|
687
|
+
j.blockStatement(originalBody),
|
|
688
|
+
j.catchClause(
|
|
689
|
+
j.identifier('error'),
|
|
690
|
+
j.blockStatement([
|
|
691
|
+
j.expressionStatement(
|
|
692
|
+
j.callExpression(
|
|
693
|
+
j.memberExpression(
|
|
694
|
+
j.identifier('console'),
|
|
695
|
+
j.identifier('error')
|
|
696
|
+
),
|
|
697
|
+
[j.identifier('error')]
|
|
698
|
+
)
|
|
699
|
+
),
|
|
700
|
+
j.throwStatement(j.identifier('error'))
|
|
701
|
+
])
|
|
702
|
+
)
|
|
703
|
+
);
|
|
704
|
+
|
|
705
|
+
path.node.body.body = [tryStatement];
|
|
706
|
+
improved = true;
|
|
707
|
+
|
|
708
|
+
const funcName = path.node.id?.name || 'anonymous';
|
|
709
|
+
changes.push(`Added error handling to ${funcName}`);
|
|
710
|
+
improvements.push({
|
|
711
|
+
type: 'error_handling',
|
|
712
|
+
description: `Added try-catch to async function ${funcName}`,
|
|
713
|
+
line: path.node.loc?.start.line || 0
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
// Improve catch blocks that swallow errors
|
|
719
|
+
ast.find(j.CatchClause)
|
|
720
|
+
.filter(path => {
|
|
721
|
+
// Check if catch block is empty or only logs
|
|
722
|
+
const body = path.node.body.body;
|
|
723
|
+
return body.length === 0 ||
|
|
724
|
+
(body.length === 1 && this.isOnlyConsoleLog(body[0]));
|
|
725
|
+
})
|
|
726
|
+
.forEach(path => {
|
|
727
|
+
// Add proper error handling
|
|
728
|
+
const errorParam = path.node.param || j.identifier('error');
|
|
729
|
+
|
|
730
|
+
path.node.body.body = [
|
|
731
|
+
j.expressionStatement(
|
|
732
|
+
j.callExpression(
|
|
733
|
+
j.memberExpression(
|
|
734
|
+
j.identifier('console'),
|
|
735
|
+
j.identifier('error')
|
|
736
|
+
),
|
|
737
|
+
[j.literal('Error caught:'), errorParam]
|
|
738
|
+
)
|
|
739
|
+
),
|
|
740
|
+
j.throwStatement(errorParam)
|
|
741
|
+
];
|
|
742
|
+
|
|
743
|
+
improved = true;
|
|
744
|
+
changes.push('Improved catch block to properly handle errors');
|
|
745
|
+
improvements.push({
|
|
746
|
+
type: 'error_handling',
|
|
747
|
+
description: 'Added proper error re-throwing in catch block',
|
|
748
|
+
line: path.node.loc?.start.line || 0
|
|
749
|
+
});
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
const result = ast.toSource();
|
|
753
|
+
|
|
754
|
+
return {
|
|
755
|
+
improved,
|
|
756
|
+
content: improved ? result : content,
|
|
757
|
+
changes,
|
|
758
|
+
improvements
|
|
759
|
+
};
|
|
760
|
+
|
|
761
|
+
} catch (_error) {
|
|
762
|
+
return { improved: false, content, error: error.message };
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
async convertToAsyncAwait(content, _filePath, options) {
|
|
767
|
+
const j = jscodeshift;
|
|
768
|
+
let improved = false;
|
|
769
|
+
const changes = [];
|
|
770
|
+
const improvements = [];
|
|
771
|
+
|
|
772
|
+
try {
|
|
773
|
+
const ast = j(content);
|
|
774
|
+
|
|
775
|
+
// Convert .then().catch() chains to async/await
|
|
776
|
+
ast.find(j.CallExpression)
|
|
777
|
+
.filter(path => {
|
|
778
|
+
return path.node.callee.type === 'MemberExpression' &&
|
|
779
|
+
path.node.callee.property.name === 'then';
|
|
780
|
+
})
|
|
781
|
+
.forEach(path => {
|
|
782
|
+
// Find the containing function
|
|
783
|
+
const containingFunction = j(_path).closest(j.Function);
|
|
784
|
+
|
|
785
|
+
if (containingFunction.length > 0) {
|
|
786
|
+
const func = containingFunction.get();
|
|
787
|
+
|
|
788
|
+
// Make function async if not already
|
|
789
|
+
if (!func.node.async) {
|
|
790
|
+
func.node.async = true;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// Convert promise chain to await
|
|
794
|
+
// This is simplified - real implementation would be more complex
|
|
795
|
+
improved = true;
|
|
796
|
+
changes.push('Converted promise chain to async/await');
|
|
797
|
+
improvements.push({
|
|
798
|
+
type: 'async_await',
|
|
799
|
+
description: 'Converted .then() chain to async/await',
|
|
800
|
+
line: path.node.loc?.start.line || 0
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
// Convert Promise callbacks to async functions
|
|
806
|
+
ast.find(j.NewExpression)
|
|
807
|
+
.filter(path => {
|
|
808
|
+
return path.node.callee.name === 'Promise' &&
|
|
809
|
+
path.node.arguments.length > 0 &&
|
|
810
|
+
path.node.arguments[0].type === 'FunctionExpression';
|
|
811
|
+
})
|
|
812
|
+
.forEach(path => {
|
|
813
|
+
const promiseCallback = path.node.arguments[0];
|
|
814
|
+
|
|
815
|
+
// Convert to async function
|
|
816
|
+
const asyncFunction = j.functionExpression(
|
|
817
|
+
promiseCallback.id,
|
|
818
|
+
promiseCallback.params,
|
|
819
|
+
promiseCallback.body,
|
|
820
|
+
promiseCallback.generator,
|
|
821
|
+
true // async
|
|
822
|
+
);
|
|
823
|
+
|
|
824
|
+
path.node.arguments[0] = asyncFunction;
|
|
825
|
+
improved = true;
|
|
826
|
+
changes.push('Converted Promise constructor to async function');
|
|
827
|
+
improvements.push({
|
|
828
|
+
type: 'async_await',
|
|
829
|
+
description: 'Made Promise callback async',
|
|
830
|
+
line: path.node.loc?.start.line || 0
|
|
831
|
+
});
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
const result = ast.toSource();
|
|
835
|
+
|
|
836
|
+
return {
|
|
837
|
+
improved,
|
|
838
|
+
content: improved ? result : content,
|
|
839
|
+
changes,
|
|
840
|
+
improvements
|
|
841
|
+
};
|
|
842
|
+
|
|
843
|
+
} catch (_error) {
|
|
844
|
+
return { improved: false, content, error: error.message };
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
async improveTypeSafety(content, _filePath, options) {
|
|
849
|
+
const j = jscodeshift;
|
|
850
|
+
let improved = false;
|
|
851
|
+
const changes = [];
|
|
852
|
+
const improvements = [];
|
|
853
|
+
|
|
854
|
+
try {
|
|
855
|
+
const ast = j(content);
|
|
856
|
+
|
|
857
|
+
// Add parameter validation to functions
|
|
858
|
+
ast.find(j.FunctionDeclaration)
|
|
859
|
+
.forEach(path => {
|
|
860
|
+
const params = path.node.params;
|
|
861
|
+
if (params.length > 0) {
|
|
862
|
+
const validationStatements = [];
|
|
863
|
+
|
|
864
|
+
params.forEach(param => {
|
|
865
|
+
if (param.type === 'Identifier') {
|
|
866
|
+
// Add basic validation
|
|
867
|
+
validationStatements.push(
|
|
868
|
+
j.ifStatement(
|
|
869
|
+
j.binaryExpression(
|
|
870
|
+
'==',
|
|
871
|
+
param,
|
|
872
|
+
j.identifier('undefined')
|
|
873
|
+
),
|
|
874
|
+
j.throwStatement(
|
|
875
|
+
j.newExpression(
|
|
876
|
+
j.identifier('Error'),
|
|
877
|
+
[j.literal(`Parameter '${param.name}' is required`)]
|
|
878
|
+
)
|
|
879
|
+
)
|
|
880
|
+
)
|
|
881
|
+
);
|
|
882
|
+
}
|
|
883
|
+
});
|
|
884
|
+
|
|
885
|
+
if (validationStatements.length > 0) {
|
|
886
|
+
// Insert at beginning of function body
|
|
887
|
+
path.node.body.body.unshift(...validationStatements);
|
|
888
|
+
improved = true;
|
|
889
|
+
|
|
890
|
+
const funcName = path.node.id?.name || 'anonymous';
|
|
891
|
+
changes.push(`Added parameter validation to ${funcName}`);
|
|
892
|
+
improvements.push({
|
|
893
|
+
type: 'type_safety',
|
|
894
|
+
description: `Added parameter validation to function ${funcName}`,
|
|
895
|
+
line: path.node.loc?.start.line || 0
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
});
|
|
900
|
+
|
|
901
|
+
// Add null checks before property access
|
|
902
|
+
ast.find(j.MemberExpression)
|
|
903
|
+
.filter(path => {
|
|
904
|
+
// Check if it's a chain that could throw
|
|
905
|
+
return path.node.object.type === 'MemberExpression' ||
|
|
906
|
+
(path.node.object.type === 'Identifier' &&
|
|
907
|
+
!this.isKnownSafeObject(path.node.object.name));
|
|
908
|
+
})
|
|
909
|
+
.forEach(path => {
|
|
910
|
+
// Convert to optional chaining if not already
|
|
911
|
+
if (!path.node.optional) {
|
|
912
|
+
path.node.optional = true;
|
|
913
|
+
improved = true;
|
|
914
|
+
improvements.push({
|
|
915
|
+
type: 'type_safety',
|
|
916
|
+
description: 'Added optional chaining for safer property access',
|
|
917
|
+
line: path.node.loc?.start.line || 0
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
});
|
|
921
|
+
|
|
922
|
+
if (improvements.length > 0) {
|
|
923
|
+
changes.push('Added type safety improvements');
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
const result = ast.toSource();
|
|
927
|
+
|
|
928
|
+
return {
|
|
929
|
+
improved,
|
|
930
|
+
content: improved ? result : content,
|
|
931
|
+
changes,
|
|
932
|
+
improvements
|
|
933
|
+
};
|
|
934
|
+
|
|
935
|
+
} catch (_error) {
|
|
936
|
+
return { improved: false, content, error: error.message };
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
async generateDocumentation(content, _filePath, options) {
|
|
941
|
+
const j = jscodeshift;
|
|
942
|
+
let improved = false;
|
|
943
|
+
const changes = [];
|
|
944
|
+
const improvements = [];
|
|
945
|
+
|
|
946
|
+
try {
|
|
947
|
+
const ast = j(content);
|
|
948
|
+
|
|
949
|
+
// Add JSDoc to functions without documentation
|
|
950
|
+
ast.find(j.FunctionDeclaration)
|
|
951
|
+
.filter(path => {
|
|
952
|
+
// Check if function already has JSDoc
|
|
953
|
+
const comments = path.node.leadingComments || [];
|
|
954
|
+
return !comments.some(c => c.type === 'CommentBlock' && c.value.includes('*'));
|
|
955
|
+
})
|
|
956
|
+
.forEach(path => {
|
|
957
|
+
const funcName = path.node.id?.name || 'anonymous';
|
|
958
|
+
const params = path.node.params;
|
|
959
|
+
const isAsync = path.node.async;
|
|
960
|
+
|
|
961
|
+
// Generate JSDoc
|
|
962
|
+
let jsdoc = '/**\n';
|
|
963
|
+
jsdoc += ` * ${this.generateFunctionDescription(funcName)}\n`;
|
|
964
|
+
|
|
965
|
+
params.forEach(param => {
|
|
966
|
+
const paramName = param.type === 'Identifier' ? param.name : 'param';
|
|
967
|
+
jsdoc += ` * @param {*} ${paramName} - ${this.generateParamDescription(paramName)}\n`;
|
|
968
|
+
});
|
|
969
|
+
|
|
970
|
+
jsdoc += ` * @returns {${isAsync ? 'Promise<*>' : '*'}} ${this.generateReturnDescription(funcName)}\n`;
|
|
971
|
+
jsdoc += ' */';
|
|
972
|
+
|
|
973
|
+
// Add JSDoc comment
|
|
974
|
+
path.node.leadingComments = [
|
|
975
|
+
j.commentBlock(jsdoc.replace('/**', '*').replace('*/', ''), true)
|
|
976
|
+
];
|
|
977
|
+
|
|
978
|
+
improved = true;
|
|
979
|
+
changes.push(`Added JSDoc to ${funcName}`);
|
|
980
|
+
improvements.push({
|
|
981
|
+
type: 'documentation',
|
|
982
|
+
description: `Generated JSDoc for function ${funcName}`,
|
|
983
|
+
line: path.node.loc?.start.line || 0
|
|
984
|
+
});
|
|
985
|
+
});
|
|
986
|
+
|
|
987
|
+
// Add JSDoc to classes
|
|
988
|
+
ast.find(j.ClassDeclaration)
|
|
989
|
+
.filter(path => {
|
|
990
|
+
const comments = path.node.leadingComments || [];
|
|
991
|
+
return !comments.some(c => c.type === 'CommentBlock' && c.value.includes('*'));
|
|
992
|
+
})
|
|
993
|
+
.forEach(path => {
|
|
994
|
+
const className = path.node.id?.name || 'Class';
|
|
995
|
+
|
|
996
|
+
let jsdoc = '/**\n';
|
|
997
|
+
jsdoc += ` * ${this.generateClassDescription(className)}\n`;
|
|
998
|
+
jsdoc += ' */';
|
|
999
|
+
|
|
1000
|
+
path.node.leadingComments = [
|
|
1001
|
+
j.commentBlock(jsdoc.replace('/**', '*').replace('*/', ''), true)
|
|
1002
|
+
];
|
|
1003
|
+
|
|
1004
|
+
improved = true;
|
|
1005
|
+
changes.push(`Added JSDoc to class ${className}`);
|
|
1006
|
+
improvements.push({
|
|
1007
|
+
type: 'documentation',
|
|
1008
|
+
description: `Generated JSDoc for class ${className}`,
|
|
1009
|
+
line: path.node.loc?.start.line || 0
|
|
1010
|
+
});
|
|
1011
|
+
});
|
|
1012
|
+
|
|
1013
|
+
const result = ast.toSource();
|
|
1014
|
+
|
|
1015
|
+
return {
|
|
1016
|
+
improved,
|
|
1017
|
+
content: improved ? result : content,
|
|
1018
|
+
changes,
|
|
1019
|
+
improvements
|
|
1020
|
+
};
|
|
1021
|
+
|
|
1022
|
+
} catch (_error) {
|
|
1023
|
+
return { improved: false, content, error: error.message };
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
// Helper methods
|
|
1028
|
+
|
|
1029
|
+
async loadESLintConfig() {
|
|
1030
|
+
try {
|
|
1031
|
+
const configPath = path.join(this.rootPath, '.eslintrc.json');
|
|
1032
|
+
const content = await fs.readFile(configPath, 'utf-8');
|
|
1033
|
+
return JSON.parse(content);
|
|
1034
|
+
} catch (_error) {
|
|
1035
|
+
// Return default config
|
|
1036
|
+
return {
|
|
1037
|
+
env: {
|
|
1038
|
+
es2021: true,
|
|
1039
|
+
node: true
|
|
1040
|
+
},
|
|
1041
|
+
extends: ['eslint:recommended'],
|
|
1042
|
+
parserOptions: {
|
|
1043
|
+
ecmaVersion: 12,
|
|
1044
|
+
sourceType: 'module'
|
|
1045
|
+
},
|
|
1046
|
+
rules: {
|
|
1047
|
+
'no-unused-vars': 'error',
|
|
1048
|
+
'no-console': 'warn',
|
|
1049
|
+
'semi': ['error', 'always'],
|
|
1050
|
+
'quotes': ['error', 'single']
|
|
1051
|
+
}
|
|
1052
|
+
};
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
async loadPrettierConfig() {
|
|
1057
|
+
try {
|
|
1058
|
+
const configPath = path.join(this.rootPath, '.prettierrc');
|
|
1059
|
+
const content = await fs.readFile(configPath, 'utf-8');
|
|
1060
|
+
return JSON.parse(content);
|
|
1061
|
+
} catch (_error) {
|
|
1062
|
+
// Return default config
|
|
1063
|
+
return {
|
|
1064
|
+
semi: true,
|
|
1065
|
+
singleQuote: true,
|
|
1066
|
+
tabWidth: 2,
|
|
1067
|
+
trailingComma: 'es5',
|
|
1068
|
+
printWidth: 80
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
calculateCyclomaticComplexity(content) {
|
|
1074
|
+
// Simplified complexity calculation
|
|
1075
|
+
let complexity = 1;
|
|
1076
|
+
|
|
1077
|
+
const complexityPatterns = [
|
|
1078
|
+
/\bif\s*\(/g,
|
|
1079
|
+
/\belse\s+if\s*\(/g,
|
|
1080
|
+
/\bwhile\s*\(/g,
|
|
1081
|
+
/\bfor\s*\(/g,
|
|
1082
|
+
/\bcase\s+/g,
|
|
1083
|
+
/\bcatch\s*\(/g,
|
|
1084
|
+
/\?\s*[^:]+\s*:/g, // ternary
|
|
1085
|
+
/\|\|/g,
|
|
1086
|
+
/&&/g
|
|
1087
|
+
];
|
|
1088
|
+
|
|
1089
|
+
complexityPatterns.forEach(pattern => {
|
|
1090
|
+
const matches = content.match(pattern);
|
|
1091
|
+
if (matches) {
|
|
1092
|
+
complexity += matches.length;
|
|
1093
|
+
}
|
|
1094
|
+
});
|
|
1095
|
+
|
|
1096
|
+
return complexity;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
calculateMaintainabilityIndex(content) {
|
|
1100
|
+
// Simplified maintainability index (0-100)
|
|
1101
|
+
const lines = content.split('\n').length;
|
|
1102
|
+
const complexity = this.calculateCyclomaticComplexity(content);
|
|
1103
|
+
const comments = (content.match(/\/\//g) || []).length +
|
|
1104
|
+
(content.match(/\/\*/g) || []).length;
|
|
1105
|
+
|
|
1106
|
+
// Simple formula
|
|
1107
|
+
const commentRatio = comments / lines;
|
|
1108
|
+
const complexityRatio = complexity / lines;
|
|
1109
|
+
|
|
1110
|
+
const maintainability = Math.min(100, Math.max(0,
|
|
1111
|
+
100 - (complexityRatio * 50) + (commentRatio * 20)
|
|
1112
|
+
));
|
|
1113
|
+
|
|
1114
|
+
return Math.round(maintainability);
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
calculateDocumentationCoverage(content) {
|
|
1118
|
+
// Calculate percentage of documented functions/classes
|
|
1119
|
+
const functionMatches = content.match(/function\s+\w+|class\s+\w+/g) || [];
|
|
1120
|
+
const jsdocMatches = content.match(/\/\*\*[\s\S]*?\*\//g) || [];
|
|
1121
|
+
|
|
1122
|
+
if (functionMatches.length === 0) return 100;
|
|
1123
|
+
|
|
1124
|
+
const coverage = (jsdocMatches.length / functionMatches.length) * 100;
|
|
1125
|
+
return Math.min(100, Math.round(coverage));
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
calculateImprovementScore(before, after) {
|
|
1129
|
+
const improvements = {
|
|
1130
|
+
issues: Math.max(0, before.issues - after.issues),
|
|
1131
|
+
complexity: Math.max(0, before.complexity - after.complexity),
|
|
1132
|
+
maintainability: Math.max(0, after.maintainability - before.maintainability),
|
|
1133
|
+
coverage: Math.max(0, after.coverage - before.coverage)
|
|
1134
|
+
};
|
|
1135
|
+
|
|
1136
|
+
// Calculate weighted score
|
|
1137
|
+
const score = (
|
|
1138
|
+
improvements.issues * 3 +
|
|
1139
|
+
improvements.complexity * 2 +
|
|
1140
|
+
improvements.maintainability +
|
|
1141
|
+
improvements.coverage * 0.5
|
|
1142
|
+
) / 6.5;
|
|
1143
|
+
|
|
1144
|
+
return Math.round(score * 10) / 10;
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
summarizeLintFixes(lintResult) {
|
|
1148
|
+
const fixedRules = new Map();
|
|
1149
|
+
|
|
1150
|
+
lintResult.messages.forEach(message => {
|
|
1151
|
+
if (message.fix) {
|
|
1152
|
+
const count = fixedRules.get(message.ruleId) || 0;
|
|
1153
|
+
fixedRules.set(message.ruleId, count + 1);
|
|
1154
|
+
}
|
|
1155
|
+
});
|
|
1156
|
+
|
|
1157
|
+
return Array.from(fixedRules.entries()).map(([rule, count]) =>
|
|
1158
|
+
`Fixed ${count} ${rule} issue${count > 1 ? 's' : ''}`
|
|
1159
|
+
);
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
isVariableReassigned(j, variableDeclarator) {
|
|
1163
|
+
const varName = variableDeclarator.node.id.name;
|
|
1164
|
+
const scope = variableDeclarator.scope;
|
|
1165
|
+
|
|
1166
|
+
let reassigned = false;
|
|
1167
|
+
|
|
1168
|
+
j(scope.path).find(j.AssignmentExpression)
|
|
1169
|
+
.filter(path => {
|
|
1170
|
+
return path.node.left.type === 'Identifier' &&
|
|
1171
|
+
path.node.left.name === varName;
|
|
1172
|
+
})
|
|
1173
|
+
.forEach(() => {
|
|
1174
|
+
reassigned = true;
|
|
1175
|
+
});
|
|
1176
|
+
|
|
1177
|
+
return reassigned;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
isExported(_path) {
|
|
1181
|
+
// Check if the declaration is exported
|
|
1182
|
+
const parent = path.parent;
|
|
1183
|
+
|
|
1184
|
+
return parent.node.type === 'ExportNamedDeclaration' ||
|
|
1185
|
+
parent.node.type === 'ExportDefaultDeclaration' ||
|
|
1186
|
+
(parent.node.type === 'AssignmentExpression' &&
|
|
1187
|
+
parent.node.left.type === 'MemberExpression' &&
|
|
1188
|
+
parent.node.left.object.name === 'module' &&
|
|
1189
|
+
parent.node.left.property.name === 'exports');
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
isOnlyConsoleLog(statement) {
|
|
1193
|
+
return statement.type === 'ExpressionStatement' &&
|
|
1194
|
+
statement.expression.type === 'CallExpression' &&
|
|
1195
|
+
statement.expression.callee.type === 'MemberExpression' &&
|
|
1196
|
+
statement.expression.callee.object.name === 'console';
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
isKnownSafeObject(name) {
|
|
1200
|
+
const safeObjects = ['console', 'Math', 'JSON', 'Object', 'Array', 'String', 'Number'];
|
|
1201
|
+
return safeObjects.includes(name);
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
snakeToCamel(str) {
|
|
1205
|
+
return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
shouldBeConstantCase(declarator) {
|
|
1209
|
+
// Check if the value is a literal or simple value
|
|
1210
|
+
const init = declarator.init;
|
|
1211
|
+
if (!init) return false;
|
|
1212
|
+
|
|
1213
|
+
return init.type === 'Literal' ||
|
|
1214
|
+
init.type === 'UnaryExpression' ||
|
|
1215
|
+
(init.type === 'Identifier' && init.name === init.name.toUpperCase());
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
isConstantCase(name) {
|
|
1219
|
+
return /^[A-Z_]+$/.test(name);
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
toConstantCase(name) {
|
|
1223
|
+
// Convert camelCase or snake_case to CONSTANT_CASE
|
|
1224
|
+
return name
|
|
1225
|
+
.replace(/([a-z])([A-Z])/g, '$1_$2')
|
|
1226
|
+
.replace(/[_-]+/g, '_')
|
|
1227
|
+
.toUpperCase();
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
generateFunctionDescription(funcName) {
|
|
1231
|
+
// Generate meaningful description based on function name
|
|
1232
|
+
const words = funcName.split(/(?=[A-Z])/);
|
|
1233
|
+
return words.map(w => w.toLowerCase()).join(' ').replace(/^\w/, c => c.toUpperCase());
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
generateParamDescription(paramName) {
|
|
1237
|
+
// Generate parameter description
|
|
1238
|
+
return `The ${paramName} parameter`;
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
generateReturnDescription(funcName) {
|
|
1242
|
+
// Generate return description
|
|
1243
|
+
if (funcName.startsWith('get')) return 'The requested value';
|
|
1244
|
+
if (funcName.startsWith('is')) return 'True if condition is met, false otherwise';
|
|
1245
|
+
if (funcName.startsWith('has')) return 'True if exists, false otherwise';
|
|
1246
|
+
return 'The result of the operation';
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
generateClassDescription(className) {
|
|
1250
|
+
// Generate class description
|
|
1251
|
+
const words = className.split(/(?=[A-Z])/);
|
|
1252
|
+
return `${words.join(' ')} class`;
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
/**
|
|
1256
|
+
* Apply improvements to file
|
|
1257
|
+
*/
|
|
1258
|
+
async applyImprovements(_filePath, improvedContent, backup = true) {
|
|
1259
|
+
try {
|
|
1260
|
+
if (backup) {
|
|
1261
|
+
// Create backup
|
|
1262
|
+
const backupPath = `${_filePath}.backup.${Date.now()}`;
|
|
1263
|
+
const originalContent = await fs.readFile(_filePath, 'utf-8');
|
|
1264
|
+
await fs.writeFile(backupPath, originalContent);
|
|
1265
|
+
console.log(chalk.gray(`Backup created: ${backupPath}`));
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
// Write improved content
|
|
1269
|
+
await fs.writeFile(_filePath, improvedContent);
|
|
1270
|
+
console.log(chalk.green(`✅ Improvements applied to: ${_filePath}`));
|
|
1271
|
+
|
|
1272
|
+
return { success: true };
|
|
1273
|
+
} catch (_error) {
|
|
1274
|
+
console.error(chalk.red(`Failed to apply improvements: ${error.message}`));
|
|
1275
|
+
return { success: false, error: error.message };
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
/**
|
|
1280
|
+
* Get improvement statistics
|
|
1281
|
+
*/
|
|
1282
|
+
getStatistics() {
|
|
1283
|
+
const stats = {
|
|
1284
|
+
filesAnalyzed: this.metrics.size,
|
|
1285
|
+
totalImprovements: 0,
|
|
1286
|
+
byType: {},
|
|
1287
|
+
averageScore: 0
|
|
1288
|
+
};
|
|
1289
|
+
|
|
1290
|
+
let totalScore = 0;
|
|
1291
|
+
|
|
1292
|
+
for (const [file, metrics] of this.metrics) {
|
|
1293
|
+
if (metrics.after) {
|
|
1294
|
+
const score = this.calculateImprovementScore(metrics.before, metrics.after);
|
|
1295
|
+
totalScore += score;
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
stats.averageScore = this.metrics.size > 0 ?
|
|
1300
|
+
(totalScore / this.metrics.size).toFixed(2) : 0;
|
|
1301
|
+
|
|
1302
|
+
// Count improvements by type
|
|
1303
|
+
for (const improvement of this.improvements) {
|
|
1304
|
+
stats.byType[improvement.type] = (stats.byType[improvement.type] || 0) + 1;
|
|
1305
|
+
stats.totalImprovements++;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
return stats;
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
1312
|
module.exports = CodeQualityImprover;
|