aios-core 4.2.13 → 4.2.15
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/pro/license/degradation.js +220 -220
- package/pro/license/errors.js +450 -450
- package/pro/license/feature-gate.js +354 -354
- package/pro/license/index.js +181 -181
- package/pro/license/license-cache.js +523 -523
- package/pro/license/license-crypto.js +303 -303
- 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/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,1139 +1,1139 @@
|
|
|
1
|
-
const fs = require('fs').promises;
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const chalk = require('chalk');
|
|
4
|
-
const { parse } = require('@babel/parser');
|
|
5
|
-
const traverse = require('@babel/traverse').default;
|
|
6
|
-
const generate = require('@babel/generator').default;
|
|
7
|
-
const _t = require('@babel/types');
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Automated refactoring suggestion system
|
|
11
|
-
* Analyzes code and suggests refactoring opportunities
|
|
12
|
-
*/
|
|
13
|
-
class RefactoringSuggester {
|
|
14
|
-
constructor(options = {}) {
|
|
15
|
-
this.rootPath = options.rootPath || process.cwd();
|
|
16
|
-
this.suggestions = [];
|
|
17
|
-
this.refactoringPatterns = new Map();
|
|
18
|
-
this.codeMetrics = new Map();
|
|
19
|
-
this.initializePatterns();
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Initialize refactoring patterns
|
|
24
|
-
*/
|
|
25
|
-
initializePatterns() {
|
|
26
|
-
// Method extraction pattern
|
|
27
|
-
this.refactoringPatterns.set('extract_method', {
|
|
28
|
-
name: 'Extract Method',
|
|
29
|
-
description: 'Extract long methods into smaller, focused methods',
|
|
30
|
-
detector: this.detectLongMethods.bind(this),
|
|
31
|
-
suggester: this.suggestMethodExtraction.bind(this),
|
|
32
|
-
priority: 'high'
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
// Variable extraction pattern
|
|
36
|
-
this.refactoringPatterns.set('extract_variable', {
|
|
37
|
-
name: 'Extract Variable',
|
|
38
|
-
description: 'Extract complex expressions into named variables',
|
|
39
|
-
detector: this.detectComplexExpressions.bind(this),
|
|
40
|
-
suggester: this.suggestVariableExtraction.bind(this),
|
|
41
|
-
priority: 'medium'
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
// Parameter object pattern
|
|
45
|
-
this.refactoringPatterns.set('introduce_parameter_object', {
|
|
46
|
-
name: 'Introduce Parameter Object',
|
|
47
|
-
description: 'Group related parameters into an object',
|
|
48
|
-
detector: this.detectLongParameterLists.bind(this),
|
|
49
|
-
suggester: this.suggestParameterObject.bind(this),
|
|
50
|
-
priority: 'medium'
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
// Replace conditional with polymorphism
|
|
54
|
-
this.refactoringPatterns.set('replace_conditional', {
|
|
55
|
-
name: 'Replace Conditional with Polymorphism',
|
|
56
|
-
description: 'Replace complex conditionals with polymorphic behavior',
|
|
57
|
-
detector: this.detectComplexConditionals.bind(this),
|
|
58
|
-
suggester: this.suggestPolymorphism.bind(this),
|
|
59
|
-
priority: 'high'
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
// Inline temp pattern
|
|
63
|
-
this.refactoringPatterns.set('inline_temp', {
|
|
64
|
-
name: 'Inline Temporary Variable',
|
|
65
|
-
description: 'Replace temporary variables used only once',
|
|
66
|
-
detector: this.detectSingleUseTempVariables.bind(this),
|
|
67
|
-
suggester: this.suggestInlineTemp.bind(this),
|
|
68
|
-
priority: 'low'
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
// Remove dead code
|
|
72
|
-
this.refactoringPatterns.set('remove_dead_code', {
|
|
73
|
-
name: 'Remove Dead Code',
|
|
74
|
-
description: 'Remove unreachable or unused code',
|
|
75
|
-
detector: this.detectDeadCode.bind(this),
|
|
76
|
-
suggester: this.suggestDeadCodeRemoval.bind(this),
|
|
77
|
-
priority: 'high'
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
// Consolidate duplicate code
|
|
81
|
-
this.refactoringPatterns.set('consolidate_duplicates', {
|
|
82
|
-
name: 'Consolidate Duplicate Code',
|
|
83
|
-
description: 'Extract duplicate code into shared functions',
|
|
84
|
-
detector: this.detectDuplicateCode.bind(this),
|
|
85
|
-
suggester: this.suggestCodeConsolidation.bind(this),
|
|
86
|
-
priority: 'high'
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
// Simplify nested conditionals
|
|
90
|
-
this.refactoringPatterns.set('simplify_conditionals', {
|
|
91
|
-
name: 'Simplify Nested Conditionals',
|
|
92
|
-
description: 'Flatten deeply nested if-else chains',
|
|
93
|
-
detector: this.detectNestedConditionals.bind(this),
|
|
94
|
-
suggester: this.suggestConditionalSimplification.bind(this),
|
|
95
|
-
priority: 'medium'
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
// Replace magic numbers
|
|
99
|
-
this.refactoringPatterns.set('replace_magic_numbers', {
|
|
100
|
-
name: 'Replace Magic Numbers',
|
|
101
|
-
description: 'Replace hard-coded numbers with named constants',
|
|
102
|
-
detector: this.detectMagicNumbers.bind(this),
|
|
103
|
-
suggester: this.suggestConstantExtraction.bind(this),
|
|
104
|
-
priority: 'low'
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
// Decompose complex class
|
|
108
|
-
this.refactoringPatterns.set('decompose_class', {
|
|
109
|
-
name: 'Decompose Complex Class',
|
|
110
|
-
description: 'Split large classes into smaller, focused classes',
|
|
111
|
-
detector: this.detectLargeClasses.bind(this),
|
|
112
|
-
suggester: this.suggestClassDecomposition.bind(this),
|
|
113
|
-
priority: 'high'
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Analyze code and suggest refactorings
|
|
119
|
-
*/
|
|
120
|
-
async analyzeCode(filePath, options = {}) {
|
|
121
|
-
console.log(chalk.blue(`🔍 Analyzing: ${filePath}`));
|
|
122
|
-
|
|
123
|
-
try {
|
|
124
|
-
const _content = await fs.readFile(filePath, 'utf-8');
|
|
125
|
-
const fileType = path.extname(filePath);
|
|
126
|
-
|
|
127
|
-
if (!['.js', '.jsx', '.ts', '.tsx'].includes(fileType)) {
|
|
128
|
-
return {
|
|
129
|
-
filePath,
|
|
130
|
-
suggestions: [],
|
|
131
|
-
error: 'Unsupported file type'
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Parse code
|
|
136
|
-
const _ast = this.parseCode(_content, filePath);
|
|
137
|
-
|
|
138
|
-
// Calculate code metrics
|
|
139
|
-
const _metrics = this.calculateCodeMetrics(_ast, content);
|
|
140
|
-
this.codeMetrics.set(filePath, metrics);
|
|
141
|
-
|
|
142
|
-
// Clear previous suggestions
|
|
143
|
-
this.suggestions = [];
|
|
144
|
-
|
|
145
|
-
// Run all refactoring detectors
|
|
146
|
-
for (const [patternId, pattern] of this.refactoringPatterns) {
|
|
147
|
-
if (options.patterns && !options.patterns.includes(patternId)) {
|
|
148
|
-
continue; // Skip if not in requested patterns
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
try {
|
|
152
|
-
const detected = await pattern.detector(_ast, _content, metrics);
|
|
153
|
-
if (detected && detected.length > 0) {
|
|
154
|
-
for (const _detection of detected) {
|
|
155
|
-
const suggestion = await pattern.suggester(_detection, _ast, content);
|
|
156
|
-
if (suggestion) {
|
|
157
|
-
this.suggestions.push({
|
|
158
|
-
...suggestion,
|
|
159
|
-
patternId,
|
|
160
|
-
pattern: pattern.name,
|
|
161
|
-
priority: pattern.priority,
|
|
162
|
-
filePath
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
} catch (error) {
|
|
168
|
-
console.warn(chalk.yellow(`Failed to run ${pattern.name}: ${error.message}`));
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// Sort suggestions by priority and impact
|
|
173
|
-
this.suggestions.sort((a, b) => {
|
|
174
|
-
const priorityOrder = { high: 3, medium: 2, low: 1 };
|
|
175
|
-
const priorityDiff = priorityOrder[b.priority] - priorityOrder[a.priority];
|
|
176
|
-
if (priorityDiff !== 0) return priorityDiff;
|
|
177
|
-
return (b.impact || 0) - (a.impact || 0);
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
return {
|
|
181
|
-
filePath,
|
|
182
|
-
_metrics,
|
|
183
|
-
suggestions: this.suggestions
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
} catch (error) {
|
|
187
|
-
return {
|
|
188
|
-
filePath,
|
|
189
|
-
suggestions: [],
|
|
190
|
-
error: error.message
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* Parse code into AST
|
|
197
|
-
*/
|
|
198
|
-
parseCode(_content, filePath) {
|
|
199
|
-
const parserOptions = {
|
|
200
|
-
sourceType: 'module',
|
|
201
|
-
plugins: [
|
|
202
|
-
'jsx',
|
|
203
|
-
'typescript',
|
|
204
|
-
'decorators-legacy',
|
|
205
|
-
'classProperties',
|
|
206
|
-
'asyncGenerators',
|
|
207
|
-
'dynamicImport',
|
|
208
|
-
'optionalChaining',
|
|
209
|
-
'nullishCoalescingOperator'
|
|
210
|
-
],
|
|
211
|
-
errorRecovery: true
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
try {
|
|
215
|
-
return parse(_content, parserOptions);
|
|
216
|
-
} catch (error) {
|
|
217
|
-
console.warn(chalk.yellow(`Parse error in ${filePath}: ${error.message}`));
|
|
218
|
-
// Try with more lenient options
|
|
219
|
-
return parse(_content, { ...parserOptions, errorRecovery: true });
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Calculate code metrics
|
|
225
|
-
*/
|
|
226
|
-
calculateCodeMetrics(_ast, content) {
|
|
227
|
-
const _metrics = {
|
|
228
|
-
lines: content.split('\n').length,
|
|
229
|
-
functions: 0,
|
|
230
|
-
classes: 0,
|
|
231
|
-
complexity: 0,
|
|
232
|
-
maxNesting: 0,
|
|
233
|
-
duplicateBlocks: 0,
|
|
234
|
-
comments: 0,
|
|
235
|
-
imports: 0
|
|
236
|
-
};
|
|
237
|
-
|
|
238
|
-
let currentNesting = 0;
|
|
239
|
-
|
|
240
|
-
traverse(_ast, {
|
|
241
|
-
FunctionDeclaration: () => metrics.functions++,
|
|
242
|
-
FunctionExpression: () => metrics.functions++,
|
|
243
|
-
ArrowFunctionExpression: () => metrics.functions++,
|
|
244
|
-
ClassDeclaration: () => metrics.classes++,
|
|
245
|
-
ImportDeclaration: () => metrics.imports++,
|
|
246
|
-
|
|
247
|
-
IfStatement: {
|
|
248
|
-
enter: () => {
|
|
249
|
-
metrics.complexity++;
|
|
250
|
-
currentNesting++;
|
|
251
|
-
metrics.maxNesting = Math.max(metrics.maxNesting, currentNesting);
|
|
252
|
-
},
|
|
253
|
-
exit: () => currentNesting--
|
|
254
|
-
},
|
|
255
|
-
|
|
256
|
-
SwitchStatement: () => metrics.complexity += 2,
|
|
257
|
-
ForStatement: () => metrics.complexity++,
|
|
258
|
-
WhileStatement: () => metrics.complexity++,
|
|
259
|
-
DoWhileStatement: () => metrics.complexity++,
|
|
260
|
-
ConditionalExpression: () => metrics.complexity++,
|
|
261
|
-
LogicalExpression: (path) => {
|
|
262
|
-
if (path.node.operator === '&&' || path.node.operator === '||') {
|
|
263
|
-
metrics.complexity++;
|
|
264
|
-
}
|
|
265
|
-
},
|
|
266
|
-
|
|
267
|
-
Comment: () => metrics.comments++
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
return metrics;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// Refactoring detectors
|
|
274
|
-
|
|
275
|
-
async detectLongMethods(_ast, _content, metrics) {
|
|
276
|
-
const longMethods = [];
|
|
277
|
-
const methodSizeThreshold = 30; // lines
|
|
278
|
-
|
|
279
|
-
traverse(_ast, {
|
|
280
|
-
'FunctionDeclaration|FunctionExpression|ArrowFunctionExpression': (path) => {
|
|
281
|
-
const start = path.node.loc.start.line;
|
|
282
|
-
const end = path.node.loc.end.line;
|
|
283
|
-
const methodLines = end - start + 1;
|
|
284
|
-
|
|
285
|
-
if (methodLines > methodSizeThreshold) {
|
|
286
|
-
const methodName = this.getMethodName(path);
|
|
287
|
-
longMethods.push({
|
|
288
|
-
type: 'long_method',
|
|
289
|
-
node: path.node,
|
|
290
|
-
path: path,
|
|
291
|
-
name: methodName,
|
|
292
|
-
lines: methodLines,
|
|
293
|
-
startLine: start,
|
|
294
|
-
endLine: end,
|
|
295
|
-
complexity: this.calculateMethodComplexity(path)
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
return longMethods;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
async detectComplexExpressions(_ast, _content, metrics) {
|
|
305
|
-
const complexExpressions = [];
|
|
306
|
-
const complexityThreshold = 3; // nesting/chaining depth
|
|
307
|
-
|
|
308
|
-
traverse(_ast, {
|
|
309
|
-
Expression: (path) => {
|
|
310
|
-
const complexity = this.calculateExpressionComplexity(path.node);
|
|
311
|
-
if (complexity > complexityThreshold) {
|
|
312
|
-
complexExpressions.push({
|
|
313
|
-
type: 'complex_expression',
|
|
314
|
-
node: path.node,
|
|
315
|
-
path: path,
|
|
316
|
-
complexity: complexity,
|
|
317
|
-
startLine: path.node.loc?.start.line,
|
|
318
|
-
endLine: path.node.loc?.end.line
|
|
319
|
-
});
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
return complexExpressions;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
async detectLongParameterLists(_ast, _content, metrics) {
|
|
328
|
-
const longParameterLists = [];
|
|
329
|
-
const parameterThreshold = 4;
|
|
330
|
-
|
|
331
|
-
traverse(_ast, {
|
|
332
|
-
'FunctionDeclaration|FunctionExpression|ArrowFunctionExpression': (path) => {
|
|
333
|
-
const params = path.node.params;
|
|
334
|
-
if (params.length > parameterThreshold) {
|
|
335
|
-
const methodName = this.getMethodName(path);
|
|
336
|
-
longParameterLists.push({
|
|
337
|
-
type: 'long_parameter_list',
|
|
338
|
-
node: path.node,
|
|
339
|
-
path: path,
|
|
340
|
-
name: methodName,
|
|
341
|
-
parameterCount: params.length,
|
|
342
|
-
parameters: params.map(p => p.name || 'unknown'),
|
|
343
|
-
startLine: path.node.loc?.start.line
|
|
344
|
-
});
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
return longParameterLists;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
async detectComplexConditionals(_ast, _content, metrics) {
|
|
353
|
-
const complexConditionals = [];
|
|
354
|
-
const branchThreshold = 4;
|
|
355
|
-
|
|
356
|
-
traverse(_ast, {
|
|
357
|
-
IfStatement: (path) => {
|
|
358
|
-
const branches = this.countConditionalBranches(path);
|
|
359
|
-
if (branches > branchThreshold) {
|
|
360
|
-
complexConditionals.push({
|
|
361
|
-
type: 'complex_conditional',
|
|
362
|
-
node: path.node,
|
|
363
|
-
path: path,
|
|
364
|
-
branches: branches,
|
|
365
|
-
startLine: path.node.loc?.start.line,
|
|
366
|
-
endLine: path.node.loc?.end.line
|
|
367
|
-
});
|
|
368
|
-
}
|
|
369
|
-
},
|
|
370
|
-
|
|
371
|
-
SwitchStatement: (path) => {
|
|
372
|
-
const cases = path.node.cases.length;
|
|
373
|
-
if (cases > branchThreshold) {
|
|
374
|
-
complexConditionals.push({
|
|
375
|
-
type: 'complex_switch',
|
|
376
|
-
node: path.node,
|
|
377
|
-
path: path,
|
|
378
|
-
cases: cases,
|
|
379
|
-
startLine: path.node.loc?.start.line,
|
|
380
|
-
endLine: path.node.loc?.end.line
|
|
381
|
-
});
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
});
|
|
385
|
-
|
|
386
|
-
return complexConditionals;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
async detectSingleUseTempVariables(_ast, _content, metrics) {
|
|
390
|
-
const singleUseVars = [];
|
|
391
|
-
const varUsage = new Map();
|
|
392
|
-
|
|
393
|
-
// First pass: collect all variable declarations and usages
|
|
394
|
-
traverse(_ast, {
|
|
395
|
-
VariableDeclarator: (path) => {
|
|
396
|
-
if (path.node.id.type === 'Identifier') {
|
|
397
|
-
const varName = path.node.id.name;
|
|
398
|
-
if (!varUsage.has(varName)) {
|
|
399
|
-
varUsage.set(varName, {
|
|
400
|
-
declaration: path,
|
|
401
|
-
uses: []
|
|
402
|
-
});
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
},
|
|
406
|
-
|
|
407
|
-
Identifier: (path) => {
|
|
408
|
-
if (path.isReferencedIdentifier()) {
|
|
409
|
-
const varName = path.node.name;
|
|
410
|
-
if (varUsage.has(varName)) {
|
|
411
|
-
varUsage.get(varName).uses.push(path);
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
});
|
|
416
|
-
|
|
417
|
-
// Second pass: find single-use variables
|
|
418
|
-
for (const [varName, usage] of varUsage) {
|
|
419
|
-
if (usage.uses.length === 1 && usage.declaration.node.init) {
|
|
420
|
-
singleUseVars.push({
|
|
421
|
-
type: 'single_use_temp',
|
|
422
|
-
name: varName,
|
|
423
|
-
declaration: usage.declaration,
|
|
424
|
-
use: usage.uses[0],
|
|
425
|
-
startLine: usage.declaration.node.loc?.start.line
|
|
426
|
-
});
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
return singleUseVars;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
async detectDeadCode(_ast, _content, metrics) {
|
|
434
|
-
const deadCode = [];
|
|
435
|
-
|
|
436
|
-
traverse(_ast, {
|
|
437
|
-
// Unreachable code after return/throw
|
|
438
|
-
'ReturnStatement|ThrowStatement': (path) => {
|
|
439
|
-
const parent = path.parent;
|
|
440
|
-
if (parent.type === 'BlockStatement') {
|
|
441
|
-
const siblings = parent.body;
|
|
442
|
-
const currentIndex = siblings.indexOf(path.node);
|
|
443
|
-
|
|
444
|
-
for (let i = currentIndex + 1; i < siblings.length; i++) {
|
|
445
|
-
deadCode.push({
|
|
446
|
-
type: 'unreachable_code',
|
|
447
|
-
node: siblings[i],
|
|
448
|
-
reason: 'after_return_throw',
|
|
449
|
-
startLine: siblings[i].loc?.start.line
|
|
450
|
-
});
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
},
|
|
454
|
-
|
|
455
|
-
// Unused functions
|
|
456
|
-
FunctionDeclaration: (path) => {
|
|
457
|
-
const functionName = path.node.id?.name;
|
|
458
|
-
if (functionName && !this.isFunctionUsed(functionName, ast)) {
|
|
459
|
-
deadCode.push({
|
|
460
|
-
type: 'unused_function',
|
|
461
|
-
node: path.node,
|
|
462
|
-
name: functionName,
|
|
463
|
-
startLine: path.node.loc?.start.line
|
|
464
|
-
});
|
|
465
|
-
}
|
|
466
|
-
},
|
|
467
|
-
|
|
468
|
-
// Always false conditions
|
|
469
|
-
IfStatement: (path) => {
|
|
470
|
-
if (path.node.test.type === 'BooleanLiteral' && !path.node.test.value) {
|
|
471
|
-
deadCode.push({
|
|
472
|
-
type: 'dead_branch',
|
|
473
|
-
node: path.node.consequent,
|
|
474
|
-
reason: 'always_false',
|
|
475
|
-
startLine: path.node.loc?.start.line
|
|
476
|
-
});
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
});
|
|
480
|
-
|
|
481
|
-
return deadCode;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
async detectDuplicateCode(_ast, _content, metrics) {
|
|
485
|
-
const duplicates = [];
|
|
486
|
-
const codeBlocks = new Map();
|
|
487
|
-
const minBlockSize = 5; // minimum lines for duplicate detection
|
|
488
|
-
|
|
489
|
-
traverse(_ast, {
|
|
490
|
-
BlockStatement: (path) => {
|
|
491
|
-
if (path.node.body.length >= minBlockSize) {
|
|
492
|
-
const blockHash = this.hashCodeBlock(path.node);
|
|
493
|
-
|
|
494
|
-
if (codeBlocks.has(blockHash)) {
|
|
495
|
-
const original = codeBlocks.get(blockHash);
|
|
496
|
-
duplicates.push({
|
|
497
|
-
type: 'duplicate_code',
|
|
498
|
-
original: original,
|
|
499
|
-
duplicate: path,
|
|
500
|
-
startLine: path.node.loc?.start.line,
|
|
501
|
-
endLine: path.node.loc?.end.line,
|
|
502
|
-
lines: path.node.loc?.end.line - path.node.loc?.start.line + 1
|
|
503
|
-
});
|
|
504
|
-
} else {
|
|
505
|
-
codeBlocks.set(blockHash, path);
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
});
|
|
510
|
-
|
|
511
|
-
return duplicates;
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
async detectNestedConditionals(_ast, _content, metrics) {
|
|
515
|
-
const nestedConditionals = [];
|
|
516
|
-
const nestingThreshold = 3;
|
|
517
|
-
|
|
518
|
-
const checkNesting = (path, depth = 0) => {
|
|
519
|
-
if (depth > nestingThreshold) {
|
|
520
|
-
nestedConditionals.push({
|
|
521
|
-
type: 'nested_conditional',
|
|
522
|
-
node: path.node,
|
|
523
|
-
path: path,
|
|
524
|
-
depth: depth,
|
|
525
|
-
startLine: path.node.loc?.start.line,
|
|
526
|
-
endLine: path.node.loc?.end.line
|
|
527
|
-
});
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
// Check nested ifs
|
|
531
|
-
traverse(path.node, {
|
|
532
|
-
IfStatement: (innerPath) => {
|
|
533
|
-
if (innerPath.node !== path.node) {
|
|
534
|
-
checkNesting(innerPath, depth + 1);
|
|
535
|
-
innerPath.skip();
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
}, path.scope, path);
|
|
539
|
-
};
|
|
540
|
-
|
|
541
|
-
traverse(_ast, {
|
|
542
|
-
IfStatement: (path) => checkNesting(path, 1)
|
|
543
|
-
});
|
|
544
|
-
|
|
545
|
-
return nestedConditionals;
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
async detectMagicNumbers(_ast, _content, metrics) {
|
|
549
|
-
const magicNumbers = [];
|
|
550
|
-
const ignoredNumbers = new Set([0, 1, -1, 2, 10, 100, 1000]);
|
|
551
|
-
|
|
552
|
-
traverse(_ast, {
|
|
553
|
-
NumericLiteral: (path) => {
|
|
554
|
-
const value = path.node.value;
|
|
555
|
-
|
|
556
|
-
// Skip common/obvious numbers
|
|
557
|
-
if (ignoredNumbers.has(value)) return;
|
|
558
|
-
|
|
559
|
-
// Skip array indices
|
|
560
|
-
if (path.parent.type === 'MemberExpression' && path.parent.computed) return;
|
|
561
|
-
|
|
562
|
-
// Skip in constant declarations
|
|
563
|
-
if (path.findParent(p => p.isVariableDeclarator() &&
|
|
564
|
-
p.parent.kind === 'const')) return;
|
|
565
|
-
|
|
566
|
-
magicNumbers.push({
|
|
567
|
-
type: 'magic_number',
|
|
568
|
-
node: path.node,
|
|
569
|
-
path: path,
|
|
570
|
-
value: value,
|
|
571
|
-
context: path.parent.type,
|
|
572
|
-
startLine: path.node.loc?.start.line
|
|
573
|
-
});
|
|
574
|
-
}
|
|
575
|
-
});
|
|
576
|
-
|
|
577
|
-
return magicNumbers;
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
async detectLargeClasses(_ast, _content, metrics) {
|
|
581
|
-
const largeClasses = [];
|
|
582
|
-
const methodThreshold = 10;
|
|
583
|
-
const propertyThreshold = 15;
|
|
584
|
-
|
|
585
|
-
traverse(_ast, {
|
|
586
|
-
ClassDeclaration: (path) => {
|
|
587
|
-
const methods = path.node.body.body.filter(m =>
|
|
588
|
-
m.type === 'ClassMethod' || m.type === 'ClassProperty'
|
|
589
|
-
);
|
|
590
|
-
|
|
591
|
-
const methodCount = methods.filter(m => m.type === 'ClassMethod').length;
|
|
592
|
-
const propertyCount = methods.filter(m => m.type === 'ClassProperty').length;
|
|
593
|
-
|
|
594
|
-
if (methodCount > methodThreshold || propertyCount > propertyThreshold) {
|
|
595
|
-
largeClasses.push({
|
|
596
|
-
type: 'large_class',
|
|
597
|
-
node: path.node,
|
|
598
|
-
path: path,
|
|
599
|
-
name: path.node.id?.name,
|
|
600
|
-
methodCount: methodCount,
|
|
601
|
-
propertyCount: propertyCount,
|
|
602
|
-
totalMembers: methods.length,
|
|
603
|
-
startLine: path.node.loc?.start.line,
|
|
604
|
-
endLine: path.node.loc?.end.line
|
|
605
|
-
});
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
});
|
|
609
|
-
|
|
610
|
-
return largeClasses;
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
// Refactoring suggesters
|
|
614
|
-
|
|
615
|
-
async suggestMethodExtraction(_detection, _ast, content) {
|
|
616
|
-
const suggestion = {
|
|
617
|
-
type: 'extract_method',
|
|
618
|
-
description: `Extract method '${detection.name}' (${detection.lines} lines)`,
|
|
619
|
-
location: {
|
|
620
|
-
start: detection.startLine,
|
|
621
|
-
end: detection.endLine
|
|
622
|
-
},
|
|
623
|
-
impact: Math.min(10, Math.floor(detection.lines / 10) + Math.floor(detection.complexity / 5)),
|
|
624
|
-
details: `Method has ${detection.lines} lines and complexity of ${detection.complexity}. Consider extracting logical sections into separate methods.`,
|
|
625
|
-
suggestedRefactoring: this.generateMethodExtractionSuggestion(_detection)
|
|
626
|
-
};
|
|
627
|
-
|
|
628
|
-
return suggestion;
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
async suggestVariableExtraction(_detection, _ast, content) {
|
|
632
|
-
const suggestion = {
|
|
633
|
-
type: 'extract_variable',
|
|
634
|
-
description: 'Extract complex expression into variable',
|
|
635
|
-
location: {
|
|
636
|
-
start: detection.startLine,
|
|
637
|
-
end: detection.endLine
|
|
638
|
-
},
|
|
639
|
-
impact: Math.min(5, detection.complexity - 2),
|
|
640
|
-
details: `Expression has complexity of ${detection.complexity}. Extract into a named variable for better readability.`,
|
|
641
|
-
suggestedRefactoring: this.generateVariableExtractionSuggestion(_detection)
|
|
642
|
-
};
|
|
643
|
-
|
|
644
|
-
return suggestion;
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
async suggestParameterObject(_detection, _ast, content) {
|
|
648
|
-
const suggestion = {
|
|
649
|
-
type: 'introduce_parameter_object',
|
|
650
|
-
description: `Group ${detection.parameterCount} parameters in '${detection.name}'`,
|
|
651
|
-
location: {
|
|
652
|
-
start: detection.startLine,
|
|
653
|
-
end: detection.startLine
|
|
654
|
-
},
|
|
655
|
-
impact: Math.min(7, detection.parameterCount - 3),
|
|
656
|
-
details: `Method has ${detection.parameterCount} parameters: ${detection.parameters.join(', ')}. Consider grouping related parameters into an object.`,
|
|
657
|
-
suggestedRefactoring: this.generateParameterObjectSuggestion(_detection)
|
|
658
|
-
};
|
|
659
|
-
|
|
660
|
-
return suggestion;
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
async suggestPolymorphism(_detection, _ast, content) {
|
|
664
|
-
const suggestion = {
|
|
665
|
-
type: 'replace_conditional',
|
|
666
|
-
description: `Replace ${detection.type === 'complex_switch' ? 'switch' : 'conditional'} with polymorphism`,
|
|
667
|
-
location: {
|
|
668
|
-
start: detection.startLine,
|
|
669
|
-
end: detection.endLine
|
|
670
|
-
},
|
|
671
|
-
impact: Math.min(8, detection.branches || detection.cases),
|
|
672
|
-
details: `Complex ${detection.type === 'complex_switch' ? 'switch' : 'conditional'} with ${detection.branches || detection.cases} branches. Consider using polymorphism or strategy pattern.`,
|
|
673
|
-
suggestedRefactoring: this.generatePolymorphismSuggestion(_detection)
|
|
674
|
-
};
|
|
675
|
-
|
|
676
|
-
return suggestion;
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
async suggestInlineTemp(_detection, _ast, content) {
|
|
680
|
-
const suggestion = {
|
|
681
|
-
type: 'inline_temp',
|
|
682
|
-
description: `Inline temporary variable '${detection.name}'`,
|
|
683
|
-
location: {
|
|
684
|
-
start: detection.startLine,
|
|
685
|
-
end: detection.startLine
|
|
686
|
-
},
|
|
687
|
-
impact: 2,
|
|
688
|
-
details: `Variable '${detection.name}' is used only once. Consider inlining it.`,
|
|
689
|
-
suggestedRefactoring: this.generateInlineTempSuggestion(_detection)
|
|
690
|
-
};
|
|
691
|
-
|
|
692
|
-
return suggestion;
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
async suggestDeadCodeRemoval(_detection, _ast, content) {
|
|
696
|
-
const suggestion = {
|
|
697
|
-
type: 'remove_dead_code',
|
|
698
|
-
description: `Remove ${detection.type.replace('_', ' ')}${detection.name ? `: ${detection.name}` : ''}`,
|
|
699
|
-
location: {
|
|
700
|
-
start: detection.startLine,
|
|
701
|
-
end: detection.node.loc?.end.line || detection.startLine
|
|
702
|
-
},
|
|
703
|
-
impact: 5,
|
|
704
|
-
details: `${detection.type === 'unreachable_code' ? 'Code is unreachable' : detection.type === 'unused_function' ? 'Function is never called' : 'Code is dead'}`,
|
|
705
|
-
suggestedRefactoring: {
|
|
706
|
-
action: 'delete',
|
|
707
|
-
lines: [detection.startLine, detection.node.loc?.end.line || detection.startLine]
|
|
708
|
-
}
|
|
709
|
-
};
|
|
710
|
-
|
|
711
|
-
return suggestion;
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
async suggestCodeConsolidation(_detection, _ast, content) {
|
|
715
|
-
const suggestion = {
|
|
716
|
-
type: 'consolidate_duplicates',
|
|
717
|
-
description: `Extract duplicate code block (${detection.lines} lines)`,
|
|
718
|
-
location: {
|
|
719
|
-
start: detection.startLine,
|
|
720
|
-
end: detection.endLine
|
|
721
|
-
},
|
|
722
|
-
impact: Math.min(9, detection.lines),
|
|
723
|
-
details: `Found duplicate code block. Extract into a shared function.`,
|
|
724
|
-
suggestedRefactoring: this.generateConsolidationSuggestion(_detection)
|
|
725
|
-
};
|
|
726
|
-
|
|
727
|
-
return suggestion;
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
async suggestConditionalSimplification(_detection, _ast, content) {
|
|
731
|
-
const suggestion = {
|
|
732
|
-
type: 'simplify_conditionals',
|
|
733
|
-
description: `Simplify nested conditionals (depth: ${detection.depth})`,
|
|
734
|
-
location: {
|
|
735
|
-
start: detection.startLine,
|
|
736
|
-
end: detection.endLine
|
|
737
|
-
},
|
|
738
|
-
impact: Math.min(7, detection.depth * 2),
|
|
739
|
-
details: `Deeply nested conditionals (${detection.depth} levels). Consider early returns or guard clauses.`,
|
|
740
|
-
suggestedRefactoring: this.generateConditionalSimplificationSuggestion(_detection)
|
|
741
|
-
};
|
|
742
|
-
|
|
743
|
-
return suggestion;
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
async suggestConstantExtraction(_detection, _ast, content) {
|
|
747
|
-
const suggestion = {
|
|
748
|
-
type: 'replace_magic_numbers',
|
|
749
|
-
description: `Replace magic number ${detection.value}`,
|
|
750
|
-
location: {
|
|
751
|
-
start: detection.startLine,
|
|
752
|
-
end: detection.startLine
|
|
753
|
-
},
|
|
754
|
-
impact: 3,
|
|
755
|
-
details: `Magic number ${detection.value} found in ${detection.context}. Extract to named constant.`,
|
|
756
|
-
suggestedRefactoring: this.generateConstantExtractionSuggestion(_detection)
|
|
757
|
-
};
|
|
758
|
-
|
|
759
|
-
return suggestion;
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
async suggestClassDecomposition(_detection, _ast, content) {
|
|
763
|
-
const suggestion = {
|
|
764
|
-
type: 'decompose_class',
|
|
765
|
-
description: `Decompose large class '${detection.name}' (${detection.totalMembers} members)`,
|
|
766
|
-
location: {
|
|
767
|
-
start: detection.startLine,
|
|
768
|
-
end: detection.endLine
|
|
769
|
-
},
|
|
770
|
-
impact: Math.min(10, Math.floor(detection.totalMembers / 5)),
|
|
771
|
-
details: `Class has ${detection.methodCount} methods and ${detection.propertyCount} properties. Consider splitting into smaller, focused classes.`,
|
|
772
|
-
suggestedRefactoring: this.generateClassDecompositionSuggestion(_detection)
|
|
773
|
-
};
|
|
774
|
-
|
|
775
|
-
return suggestion;
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
// Helper methods
|
|
779
|
-
|
|
780
|
-
getMethodName(path) {
|
|
781
|
-
if (path.node.id) {
|
|
782
|
-
return path.node.id.name;
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
// Check if it's a method in a class
|
|
786
|
-
if (path.parent.type === 'ClassMethod') {
|
|
787
|
-
return path.parent.key.name;
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
// Check if it's assigned to a variable
|
|
791
|
-
if (path.parent.type === 'VariableDeclarator') {
|
|
792
|
-
return path.parent.id.name;
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
// Check if it's a property
|
|
796
|
-
if (path.parent.type === 'ObjectProperty') {
|
|
797
|
-
return path.parent.key.name || path.parent.key.value;
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
return 'anonymous';
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
calculateMethodComplexity(path) {
|
|
804
|
-
let complexity = 1;
|
|
805
|
-
|
|
806
|
-
traverse(path.node, {
|
|
807
|
-
IfStatement: () => complexity++,
|
|
808
|
-
ConditionalExpression: () => complexity++,
|
|
809
|
-
SwitchCase: () => complexity++,
|
|
810
|
-
WhileStatement: () => complexity++,
|
|
811
|
-
ForStatement: () => complexity++,
|
|
812
|
-
DoWhileStatement: () => complexity++,
|
|
813
|
-
LogicalExpression: (innerPath) => {
|
|
814
|
-
if (innerPath.node.operator === '&&' || innerPath.node.operator === '||') {
|
|
815
|
-
complexity++;
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
}, path.scope, path);
|
|
819
|
-
|
|
820
|
-
return complexity;
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
calculateExpressionComplexity(node, depth = 0) {
|
|
824
|
-
if (!node) return depth;
|
|
825
|
-
|
|
826
|
-
let maxDepth = depth;
|
|
827
|
-
|
|
828
|
-
// Check different expression types
|
|
829
|
-
if (node.type === 'CallExpression') {
|
|
830
|
-
maxDepth = Math.max(maxDepth, this.calculateExpressionComplexity(node.callee, depth + 1));
|
|
831
|
-
for (const arg of node.arguments) {
|
|
832
|
-
maxDepth = Math.max(maxDepth, this.calculateExpressionComplexity(arg, depth + 1));
|
|
833
|
-
}
|
|
834
|
-
} else if (node.type === 'MemberExpression') {
|
|
835
|
-
maxDepth = Math.max(maxDepth, this.calculateExpressionComplexity(node.object, depth + 1));
|
|
836
|
-
} else if (node.type === 'ConditionalExpression') {
|
|
837
|
-
maxDepth = Math.max(maxDepth,
|
|
838
|
-
this.calculateExpressionComplexity(node.test, depth + 1),
|
|
839
|
-
this.calculateExpressionComplexity(node.consequent, depth + 1),
|
|
840
|
-
this.calculateExpressionComplexity(node.alternate, depth + 1)
|
|
841
|
-
);
|
|
842
|
-
} else if (node.type === 'BinaryExpression' || node.type === 'LogicalExpression') {
|
|
843
|
-
maxDepth = Math.max(maxDepth,
|
|
844
|
-
this.calculateExpressionComplexity(node.left, depth + 1),
|
|
845
|
-
this.calculateExpressionComplexity(node.right, depth + 1)
|
|
846
|
-
);
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
return maxDepth;
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
countConditionalBranches(path) {
|
|
853
|
-
let branches = 1; // Initial if branch
|
|
854
|
-
|
|
855
|
-
let current = path.node;
|
|
856
|
-
while (current.alternate) {
|
|
857
|
-
branches++;
|
|
858
|
-
if (current.alternate.type === 'IfStatement') {
|
|
859
|
-
current = current.alternate;
|
|
860
|
-
} else {
|
|
861
|
-
break;
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
return branches;
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
isFunctionUsed(functionName, ast) {
|
|
869
|
-
let used = false;
|
|
870
|
-
|
|
871
|
-
traverse(_ast, {
|
|
872
|
-
CallExpression: (path) => {
|
|
873
|
-
if (path.node.callee.type === 'Identifier' &&
|
|
874
|
-
path.node.callee.name === functionName) {
|
|
875
|
-
used = true;
|
|
876
|
-
path.stop();
|
|
877
|
-
}
|
|
878
|
-
},
|
|
879
|
-
Identifier: (path) => {
|
|
880
|
-
if (path.node.name === functionName &&
|
|
881
|
-
path.isReferencedIdentifier() &&
|
|
882
|
-
!path.isFunction()) {
|
|
883
|
-
used = true;
|
|
884
|
-
path.stop();
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
});
|
|
888
|
-
|
|
889
|
-
return used;
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
hashCodeBlock(node) {
|
|
893
|
-
// Simple hash based on code structure
|
|
894
|
-
const code = generate(node, { compact: true }).code;
|
|
895
|
-
return code.replace(/\s+/g, ' ').trim();
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
// Suggestion generators
|
|
899
|
-
|
|
900
|
-
generateMethodExtractionSuggestion(_detection) {
|
|
901
|
-
return {
|
|
902
|
-
action: 'extract_method',
|
|
903
|
-
extractedMethods: [
|
|
904
|
-
{
|
|
905
|
-
name: `extracted${detection.name.charAt(0).toUpperCase() + detection.name.slice(1)}Part1`,
|
|
906
|
-
description: 'Extract first logical section',
|
|
907
|
-
suggestedLines: [detection.startLine + 5, detection.startLine + 15]
|
|
908
|
-
},
|
|
909
|
-
{
|
|
910
|
-
name: `extracted${detection.name.charAt(0).toUpperCase() + detection.name.slice(1)}Part2`,
|
|
911
|
-
description: 'Extract second logical section',
|
|
912
|
-
suggestedLines: [detection.startLine + 16, detection.endLine - 5]
|
|
913
|
-
}
|
|
914
|
-
]
|
|
915
|
-
};
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
generateVariableExtractionSuggestion(_detection) {
|
|
919
|
-
return {
|
|
920
|
-
action: 'extract_variable',
|
|
921
|
-
variableName: 'extractedExpression',
|
|
922
|
-
insertBefore: detection.startLine
|
|
923
|
-
};
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
generateParameterObjectSuggestion(_detection) {
|
|
927
|
-
return {
|
|
928
|
-
action: 'introduce_parameter_object',
|
|
929
|
-
objectName: `${detection.name}Options`,
|
|
930
|
-
groupedParameters: detection.parameters.slice(2), // Keep first 2 params separate
|
|
931
|
-
keepParameters: detection.parameters.slice(0, 2)
|
|
932
|
-
};
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
generatePolymorphismSuggestion(_detection) {
|
|
936
|
-
return {
|
|
937
|
-
action: 'replace_with_polymorphism',
|
|
938
|
-
strategyPattern: true,
|
|
939
|
-
suggestedClasses: ['BaseHandler', 'TypeAHandler', 'TypeBHandler'],
|
|
940
|
-
interfaceMethod: 'handle'
|
|
941
|
-
};
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
generateInlineTempSuggestion(_detection) {
|
|
945
|
-
return {
|
|
946
|
-
action: 'inline_variable',
|
|
947
|
-
variableName: detection.name,
|
|
948
|
-
declarationLine: detection.declaration.node.loc?.start.line,
|
|
949
|
-
usageLine: detection.use.node.loc?.start.line
|
|
950
|
-
};
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
generateConsolidationSuggestion(_detection) {
|
|
954
|
-
return {
|
|
955
|
-
action: 'extract_shared_function',
|
|
956
|
-
functionName: 'extractedSharedFunction',
|
|
957
|
-
originalLocations: [
|
|
958
|
-
{
|
|
959
|
-
start: detection.original.node.loc?.start.line,
|
|
960
|
-
end: detection.original.node.loc?.end.line
|
|
961
|
-
},
|
|
962
|
-
{
|
|
963
|
-
start: detection.duplicate.node.loc?.start.line,
|
|
964
|
-
end: detection.duplicate.node.loc?.end.line
|
|
965
|
-
}
|
|
966
|
-
]
|
|
967
|
-
};
|
|
968
|
-
}
|
|
969
|
-
|
|
970
|
-
generateConditionalSimplificationSuggestion(_detection) {
|
|
971
|
-
return {
|
|
972
|
-
action: 'simplify_nested_conditionals',
|
|
973
|
-
techniques: ['early_return', 'guard_clauses', 'extract_condition'],
|
|
974
|
-
suggestedStructure: 'Use guard clauses for edge cases and early returns'
|
|
975
|
-
};
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
generateConstantExtractionSuggestion(_detection) {
|
|
979
|
-
const constantName = this.suggestConstantName(detection.value, detection.context);
|
|
980
|
-
return {
|
|
981
|
-
action: 'extract_constant',
|
|
982
|
-
constantName: constantName,
|
|
983
|
-
value: detection.value,
|
|
984
|
-
scope: 'module' // or 'class' depending on context
|
|
985
|
-
};
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
generateClassDecompositionSuggestion(_detection) {
|
|
989
|
-
return {
|
|
990
|
-
action: 'decompose_class',
|
|
991
|
-
suggestedClasses: [
|
|
992
|
-
{
|
|
993
|
-
name: `${detection.name}Core`,
|
|
994
|
-
description: 'Core functionality',
|
|
995
|
-
methods: 'Core business logic methods'
|
|
996
|
-
},
|
|
997
|
-
{
|
|
998
|
-
name: `${detection.name}Utils`,
|
|
999
|
-
description: 'Utility methods',
|
|
1000
|
-
methods: 'Helper and utility methods'
|
|
1001
|
-
},
|
|
1002
|
-
{
|
|
1003
|
-
name: `${detection.name}Config`,
|
|
1004
|
-
description: 'Configuration and setup',
|
|
1005
|
-
methods: 'Configuration-related methods'
|
|
1006
|
-
}
|
|
1007
|
-
]
|
|
1008
|
-
};
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
suggestConstantName(value, context) {
|
|
1012
|
-
// Generate meaningful constant names based on value and context
|
|
1013
|
-
const contextMap = {
|
|
1014
|
-
'BinaryExpression': 'THRESHOLD',
|
|
1015
|
-
'IfStatement': 'CONDITION',
|
|
1016
|
-
'ForStatement': 'LIMIT',
|
|
1017
|
-
'CallExpression': 'PARAMETER'
|
|
1018
|
-
};
|
|
1019
|
-
|
|
1020
|
-
const baseContext = contextMap[context] || 'VALUE';
|
|
1021
|
-
return `${baseContext}_${Math.abs(value).toString().replace('.', '_')}`;
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
/**
|
|
1025
|
-
* Apply refactoring suggestion
|
|
1026
|
-
*/
|
|
1027
|
-
async applySuggestion(suggestion, options = {}) {
|
|
1028
|
-
console.log(chalk.blue(`🔧 Applying ${suggestion.type} refactoring...`));
|
|
1029
|
-
|
|
1030
|
-
try {
|
|
1031
|
-
// This would integrate with the actual refactoring implementation
|
|
1032
|
-
// For now, it's a placeholder showing the structure
|
|
1033
|
-
|
|
1034
|
-
const result = {
|
|
1035
|
-
success: false,
|
|
1036
|
-
changes: [],
|
|
1037
|
-
error: null
|
|
1038
|
-
};
|
|
1039
|
-
|
|
1040
|
-
switch (suggestion.type) {
|
|
1041
|
-
case 'extract_method':
|
|
1042
|
-
result.changes = await this.applyMethodExtraction(suggestion);
|
|
1043
|
-
break;
|
|
1044
|
-
case 'extract_variable':
|
|
1045
|
-
result.changes = await this.applyVariableExtraction(suggestion);
|
|
1046
|
-
break;
|
|
1047
|
-
case 'inline_temp':
|
|
1048
|
-
result.changes = await this.applyInlineTemp(suggestion);
|
|
1049
|
-
break;
|
|
1050
|
-
case 'remove_dead_code':
|
|
1051
|
-
result.changes = await this.applyDeadCodeRemoval(suggestion);
|
|
1052
|
-
break;
|
|
1053
|
-
default:
|
|
1054
|
-
throw new Error(`Refactoring type ${suggestion.type} not implemented`);
|
|
1055
|
-
}
|
|
1056
|
-
|
|
1057
|
-
result.success = true;
|
|
1058
|
-
return result;
|
|
1059
|
-
|
|
1060
|
-
} catch (error) {
|
|
1061
|
-
console.error(chalk.red(`Failed to apply refactoring: ${error.message}`));
|
|
1062
|
-
return {
|
|
1063
|
-
success: false,
|
|
1064
|
-
changes: [],
|
|
1065
|
-
error: error.message
|
|
1066
|
-
};
|
|
1067
|
-
}
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
|
-
// Placeholder methods for applying refactorings
|
|
1071
|
-
async applyMethodExtraction(suggestion) {
|
|
1072
|
-
// Implementation would use AST transformation
|
|
1073
|
-
return [{
|
|
1074
|
-
type: 'extract_method',
|
|
1075
|
-
file: suggestion.filePath,
|
|
1076
|
-
description: `Extracted method from lines ${suggestion.location.start}-${suggestion.location.end}`
|
|
1077
|
-
}];
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
async applyVariableExtraction(suggestion) {
|
|
1081
|
-
return [{
|
|
1082
|
-
type: 'extract_variable',
|
|
1083
|
-
file: suggestion.filePath,
|
|
1084
|
-
description: `Extracted variable at line ${suggestion.location.start}`
|
|
1085
|
-
}];
|
|
1086
|
-
}
|
|
1087
|
-
|
|
1088
|
-
async applyInlineTemp(suggestion) {
|
|
1089
|
-
return [{
|
|
1090
|
-
type: 'inline_temp',
|
|
1091
|
-
file: suggestion.filePath,
|
|
1092
|
-
description: `Inlined variable at line ${suggestion.location.start}`
|
|
1093
|
-
}];
|
|
1094
|
-
}
|
|
1095
|
-
|
|
1096
|
-
async applyDeadCodeRemoval(suggestion) {
|
|
1097
|
-
return [{
|
|
1098
|
-
type: 'remove_dead_code',
|
|
1099
|
-
file: suggestion.filePath,
|
|
1100
|
-
description: `Removed dead code at lines ${suggestion.location.start}-${suggestion.location.end}`
|
|
1101
|
-
}];
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
|
-
/**
|
|
1105
|
-
* Get refactoring statistics
|
|
1106
|
-
*/
|
|
1107
|
-
getStatistics() {
|
|
1108
|
-
const stats = {
|
|
1109
|
-
totalSuggestions: this.suggestions.length,
|
|
1110
|
-
byType: {},
|
|
1111
|
-
byPriority: {
|
|
1112
|
-
high: 0,
|
|
1113
|
-
medium: 0,
|
|
1114
|
-
low: 0
|
|
1115
|
-
},
|
|
1116
|
-
averageImpact: 0
|
|
1117
|
-
};
|
|
1118
|
-
|
|
1119
|
-
let totalImpact = 0;
|
|
1120
|
-
|
|
1121
|
-
for (const suggestion of this.suggestions) {
|
|
1122
|
-
// By type
|
|
1123
|
-
stats.byType[suggestion.type] = (stats.byType[suggestion.type] || 0) + 1;
|
|
1124
|
-
|
|
1125
|
-
// By priority
|
|
1126
|
-
stats.byPriority[suggestion.priority]++;
|
|
1127
|
-
|
|
1128
|
-
// Impact
|
|
1129
|
-
totalImpact += suggestion.impact || 0;
|
|
1130
|
-
}
|
|
1131
|
-
|
|
1132
|
-
stats.averageImpact = stats.totalSuggestions > 0 ?
|
|
1133
|
-
(totalImpact / stats.totalSuggestions).toFixed(2) : 0;
|
|
1134
|
-
|
|
1135
|
-
return stats;
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
|
|
1
|
+
const fs = require('fs').promises;
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const { parse } = require('@babel/parser');
|
|
5
|
+
const traverse = require('@babel/traverse').default;
|
|
6
|
+
const generate = require('@babel/generator').default;
|
|
7
|
+
const _t = require('@babel/types');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Automated refactoring suggestion system
|
|
11
|
+
* Analyzes code and suggests refactoring opportunities
|
|
12
|
+
*/
|
|
13
|
+
class RefactoringSuggester {
|
|
14
|
+
constructor(options = {}) {
|
|
15
|
+
this.rootPath = options.rootPath || process.cwd();
|
|
16
|
+
this.suggestions = [];
|
|
17
|
+
this.refactoringPatterns = new Map();
|
|
18
|
+
this.codeMetrics = new Map();
|
|
19
|
+
this.initializePatterns();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Initialize refactoring patterns
|
|
24
|
+
*/
|
|
25
|
+
initializePatterns() {
|
|
26
|
+
// Method extraction pattern
|
|
27
|
+
this.refactoringPatterns.set('extract_method', {
|
|
28
|
+
name: 'Extract Method',
|
|
29
|
+
description: 'Extract long methods into smaller, focused methods',
|
|
30
|
+
detector: this.detectLongMethods.bind(this),
|
|
31
|
+
suggester: this.suggestMethodExtraction.bind(this),
|
|
32
|
+
priority: 'high'
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Variable extraction pattern
|
|
36
|
+
this.refactoringPatterns.set('extract_variable', {
|
|
37
|
+
name: 'Extract Variable',
|
|
38
|
+
description: 'Extract complex expressions into named variables',
|
|
39
|
+
detector: this.detectComplexExpressions.bind(this),
|
|
40
|
+
suggester: this.suggestVariableExtraction.bind(this),
|
|
41
|
+
priority: 'medium'
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Parameter object pattern
|
|
45
|
+
this.refactoringPatterns.set('introduce_parameter_object', {
|
|
46
|
+
name: 'Introduce Parameter Object',
|
|
47
|
+
description: 'Group related parameters into an object',
|
|
48
|
+
detector: this.detectLongParameterLists.bind(this),
|
|
49
|
+
suggester: this.suggestParameterObject.bind(this),
|
|
50
|
+
priority: 'medium'
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Replace conditional with polymorphism
|
|
54
|
+
this.refactoringPatterns.set('replace_conditional', {
|
|
55
|
+
name: 'Replace Conditional with Polymorphism',
|
|
56
|
+
description: 'Replace complex conditionals with polymorphic behavior',
|
|
57
|
+
detector: this.detectComplexConditionals.bind(this),
|
|
58
|
+
suggester: this.suggestPolymorphism.bind(this),
|
|
59
|
+
priority: 'high'
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Inline temp pattern
|
|
63
|
+
this.refactoringPatterns.set('inline_temp', {
|
|
64
|
+
name: 'Inline Temporary Variable',
|
|
65
|
+
description: 'Replace temporary variables used only once',
|
|
66
|
+
detector: this.detectSingleUseTempVariables.bind(this),
|
|
67
|
+
suggester: this.suggestInlineTemp.bind(this),
|
|
68
|
+
priority: 'low'
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Remove dead code
|
|
72
|
+
this.refactoringPatterns.set('remove_dead_code', {
|
|
73
|
+
name: 'Remove Dead Code',
|
|
74
|
+
description: 'Remove unreachable or unused code',
|
|
75
|
+
detector: this.detectDeadCode.bind(this),
|
|
76
|
+
suggester: this.suggestDeadCodeRemoval.bind(this),
|
|
77
|
+
priority: 'high'
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Consolidate duplicate code
|
|
81
|
+
this.refactoringPatterns.set('consolidate_duplicates', {
|
|
82
|
+
name: 'Consolidate Duplicate Code',
|
|
83
|
+
description: 'Extract duplicate code into shared functions',
|
|
84
|
+
detector: this.detectDuplicateCode.bind(this),
|
|
85
|
+
suggester: this.suggestCodeConsolidation.bind(this),
|
|
86
|
+
priority: 'high'
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Simplify nested conditionals
|
|
90
|
+
this.refactoringPatterns.set('simplify_conditionals', {
|
|
91
|
+
name: 'Simplify Nested Conditionals',
|
|
92
|
+
description: 'Flatten deeply nested if-else chains',
|
|
93
|
+
detector: this.detectNestedConditionals.bind(this),
|
|
94
|
+
suggester: this.suggestConditionalSimplification.bind(this),
|
|
95
|
+
priority: 'medium'
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Replace magic numbers
|
|
99
|
+
this.refactoringPatterns.set('replace_magic_numbers', {
|
|
100
|
+
name: 'Replace Magic Numbers',
|
|
101
|
+
description: 'Replace hard-coded numbers with named constants',
|
|
102
|
+
detector: this.detectMagicNumbers.bind(this),
|
|
103
|
+
suggester: this.suggestConstantExtraction.bind(this),
|
|
104
|
+
priority: 'low'
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Decompose complex class
|
|
108
|
+
this.refactoringPatterns.set('decompose_class', {
|
|
109
|
+
name: 'Decompose Complex Class',
|
|
110
|
+
description: 'Split large classes into smaller, focused classes',
|
|
111
|
+
detector: this.detectLargeClasses.bind(this),
|
|
112
|
+
suggester: this.suggestClassDecomposition.bind(this),
|
|
113
|
+
priority: 'high'
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Analyze code and suggest refactorings
|
|
119
|
+
*/
|
|
120
|
+
async analyzeCode(filePath, options = {}) {
|
|
121
|
+
console.log(chalk.blue(`🔍 Analyzing: ${filePath}`));
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
const _content = await fs.readFile(filePath, 'utf-8');
|
|
125
|
+
const fileType = path.extname(filePath);
|
|
126
|
+
|
|
127
|
+
if (!['.js', '.jsx', '.ts', '.tsx'].includes(fileType)) {
|
|
128
|
+
return {
|
|
129
|
+
filePath,
|
|
130
|
+
suggestions: [],
|
|
131
|
+
error: 'Unsupported file type'
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Parse code
|
|
136
|
+
const _ast = this.parseCode(_content, filePath);
|
|
137
|
+
|
|
138
|
+
// Calculate code metrics
|
|
139
|
+
const _metrics = this.calculateCodeMetrics(_ast, content);
|
|
140
|
+
this.codeMetrics.set(filePath, metrics);
|
|
141
|
+
|
|
142
|
+
// Clear previous suggestions
|
|
143
|
+
this.suggestions = [];
|
|
144
|
+
|
|
145
|
+
// Run all refactoring detectors
|
|
146
|
+
for (const [patternId, pattern] of this.refactoringPatterns) {
|
|
147
|
+
if (options.patterns && !options.patterns.includes(patternId)) {
|
|
148
|
+
continue; // Skip if not in requested patterns
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
const detected = await pattern.detector(_ast, _content, metrics);
|
|
153
|
+
if (detected && detected.length > 0) {
|
|
154
|
+
for (const _detection of detected) {
|
|
155
|
+
const suggestion = await pattern.suggester(_detection, _ast, content);
|
|
156
|
+
if (suggestion) {
|
|
157
|
+
this.suggestions.push({
|
|
158
|
+
...suggestion,
|
|
159
|
+
patternId,
|
|
160
|
+
pattern: pattern.name,
|
|
161
|
+
priority: pattern.priority,
|
|
162
|
+
filePath
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
} catch (error) {
|
|
168
|
+
console.warn(chalk.yellow(`Failed to run ${pattern.name}: ${error.message}`));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Sort suggestions by priority and impact
|
|
173
|
+
this.suggestions.sort((a, b) => {
|
|
174
|
+
const priorityOrder = { high: 3, medium: 2, low: 1 };
|
|
175
|
+
const priorityDiff = priorityOrder[b.priority] - priorityOrder[a.priority];
|
|
176
|
+
if (priorityDiff !== 0) return priorityDiff;
|
|
177
|
+
return (b.impact || 0) - (a.impact || 0);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
filePath,
|
|
182
|
+
_metrics,
|
|
183
|
+
suggestions: this.suggestions
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
} catch (error) {
|
|
187
|
+
return {
|
|
188
|
+
filePath,
|
|
189
|
+
suggestions: [],
|
|
190
|
+
error: error.message
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Parse code into AST
|
|
197
|
+
*/
|
|
198
|
+
parseCode(_content, filePath) {
|
|
199
|
+
const parserOptions = {
|
|
200
|
+
sourceType: 'module',
|
|
201
|
+
plugins: [
|
|
202
|
+
'jsx',
|
|
203
|
+
'typescript',
|
|
204
|
+
'decorators-legacy',
|
|
205
|
+
'classProperties',
|
|
206
|
+
'asyncGenerators',
|
|
207
|
+
'dynamicImport',
|
|
208
|
+
'optionalChaining',
|
|
209
|
+
'nullishCoalescingOperator'
|
|
210
|
+
],
|
|
211
|
+
errorRecovery: true
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
return parse(_content, parserOptions);
|
|
216
|
+
} catch (error) {
|
|
217
|
+
console.warn(chalk.yellow(`Parse error in ${filePath}: ${error.message}`));
|
|
218
|
+
// Try with more lenient options
|
|
219
|
+
return parse(_content, { ...parserOptions, errorRecovery: true });
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Calculate code metrics
|
|
225
|
+
*/
|
|
226
|
+
calculateCodeMetrics(_ast, content) {
|
|
227
|
+
const _metrics = {
|
|
228
|
+
lines: content.split('\n').length,
|
|
229
|
+
functions: 0,
|
|
230
|
+
classes: 0,
|
|
231
|
+
complexity: 0,
|
|
232
|
+
maxNesting: 0,
|
|
233
|
+
duplicateBlocks: 0,
|
|
234
|
+
comments: 0,
|
|
235
|
+
imports: 0
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
let currentNesting = 0;
|
|
239
|
+
|
|
240
|
+
traverse(_ast, {
|
|
241
|
+
FunctionDeclaration: () => metrics.functions++,
|
|
242
|
+
FunctionExpression: () => metrics.functions++,
|
|
243
|
+
ArrowFunctionExpression: () => metrics.functions++,
|
|
244
|
+
ClassDeclaration: () => metrics.classes++,
|
|
245
|
+
ImportDeclaration: () => metrics.imports++,
|
|
246
|
+
|
|
247
|
+
IfStatement: {
|
|
248
|
+
enter: () => {
|
|
249
|
+
metrics.complexity++;
|
|
250
|
+
currentNesting++;
|
|
251
|
+
metrics.maxNesting = Math.max(metrics.maxNesting, currentNesting);
|
|
252
|
+
},
|
|
253
|
+
exit: () => currentNesting--
|
|
254
|
+
},
|
|
255
|
+
|
|
256
|
+
SwitchStatement: () => metrics.complexity += 2,
|
|
257
|
+
ForStatement: () => metrics.complexity++,
|
|
258
|
+
WhileStatement: () => metrics.complexity++,
|
|
259
|
+
DoWhileStatement: () => metrics.complexity++,
|
|
260
|
+
ConditionalExpression: () => metrics.complexity++,
|
|
261
|
+
LogicalExpression: (path) => {
|
|
262
|
+
if (path.node.operator === '&&' || path.node.operator === '||') {
|
|
263
|
+
metrics.complexity++;
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
|
|
267
|
+
Comment: () => metrics.comments++
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
return metrics;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Refactoring detectors
|
|
274
|
+
|
|
275
|
+
async detectLongMethods(_ast, _content, metrics) {
|
|
276
|
+
const longMethods = [];
|
|
277
|
+
const methodSizeThreshold = 30; // lines
|
|
278
|
+
|
|
279
|
+
traverse(_ast, {
|
|
280
|
+
'FunctionDeclaration|FunctionExpression|ArrowFunctionExpression': (path) => {
|
|
281
|
+
const start = path.node.loc.start.line;
|
|
282
|
+
const end = path.node.loc.end.line;
|
|
283
|
+
const methodLines = end - start + 1;
|
|
284
|
+
|
|
285
|
+
if (methodLines > methodSizeThreshold) {
|
|
286
|
+
const methodName = this.getMethodName(path);
|
|
287
|
+
longMethods.push({
|
|
288
|
+
type: 'long_method',
|
|
289
|
+
node: path.node,
|
|
290
|
+
path: path,
|
|
291
|
+
name: methodName,
|
|
292
|
+
lines: methodLines,
|
|
293
|
+
startLine: start,
|
|
294
|
+
endLine: end,
|
|
295
|
+
complexity: this.calculateMethodComplexity(path)
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
return longMethods;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async detectComplexExpressions(_ast, _content, metrics) {
|
|
305
|
+
const complexExpressions = [];
|
|
306
|
+
const complexityThreshold = 3; // nesting/chaining depth
|
|
307
|
+
|
|
308
|
+
traverse(_ast, {
|
|
309
|
+
Expression: (path) => {
|
|
310
|
+
const complexity = this.calculateExpressionComplexity(path.node);
|
|
311
|
+
if (complexity > complexityThreshold) {
|
|
312
|
+
complexExpressions.push({
|
|
313
|
+
type: 'complex_expression',
|
|
314
|
+
node: path.node,
|
|
315
|
+
path: path,
|
|
316
|
+
complexity: complexity,
|
|
317
|
+
startLine: path.node.loc?.start.line,
|
|
318
|
+
endLine: path.node.loc?.end.line
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
return complexExpressions;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
async detectLongParameterLists(_ast, _content, metrics) {
|
|
328
|
+
const longParameterLists = [];
|
|
329
|
+
const parameterThreshold = 4;
|
|
330
|
+
|
|
331
|
+
traverse(_ast, {
|
|
332
|
+
'FunctionDeclaration|FunctionExpression|ArrowFunctionExpression': (path) => {
|
|
333
|
+
const params = path.node.params;
|
|
334
|
+
if (params.length > parameterThreshold) {
|
|
335
|
+
const methodName = this.getMethodName(path);
|
|
336
|
+
longParameterLists.push({
|
|
337
|
+
type: 'long_parameter_list',
|
|
338
|
+
node: path.node,
|
|
339
|
+
path: path,
|
|
340
|
+
name: methodName,
|
|
341
|
+
parameterCount: params.length,
|
|
342
|
+
parameters: params.map(p => p.name || 'unknown'),
|
|
343
|
+
startLine: path.node.loc?.start.line
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
return longParameterLists;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
async detectComplexConditionals(_ast, _content, metrics) {
|
|
353
|
+
const complexConditionals = [];
|
|
354
|
+
const branchThreshold = 4;
|
|
355
|
+
|
|
356
|
+
traverse(_ast, {
|
|
357
|
+
IfStatement: (path) => {
|
|
358
|
+
const branches = this.countConditionalBranches(path);
|
|
359
|
+
if (branches > branchThreshold) {
|
|
360
|
+
complexConditionals.push({
|
|
361
|
+
type: 'complex_conditional',
|
|
362
|
+
node: path.node,
|
|
363
|
+
path: path,
|
|
364
|
+
branches: branches,
|
|
365
|
+
startLine: path.node.loc?.start.line,
|
|
366
|
+
endLine: path.node.loc?.end.line
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
},
|
|
370
|
+
|
|
371
|
+
SwitchStatement: (path) => {
|
|
372
|
+
const cases = path.node.cases.length;
|
|
373
|
+
if (cases > branchThreshold) {
|
|
374
|
+
complexConditionals.push({
|
|
375
|
+
type: 'complex_switch',
|
|
376
|
+
node: path.node,
|
|
377
|
+
path: path,
|
|
378
|
+
cases: cases,
|
|
379
|
+
startLine: path.node.loc?.start.line,
|
|
380
|
+
endLine: path.node.loc?.end.line
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
return complexConditionals;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
async detectSingleUseTempVariables(_ast, _content, metrics) {
|
|
390
|
+
const singleUseVars = [];
|
|
391
|
+
const varUsage = new Map();
|
|
392
|
+
|
|
393
|
+
// First pass: collect all variable declarations and usages
|
|
394
|
+
traverse(_ast, {
|
|
395
|
+
VariableDeclarator: (path) => {
|
|
396
|
+
if (path.node.id.type === 'Identifier') {
|
|
397
|
+
const varName = path.node.id.name;
|
|
398
|
+
if (!varUsage.has(varName)) {
|
|
399
|
+
varUsage.set(varName, {
|
|
400
|
+
declaration: path,
|
|
401
|
+
uses: []
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
},
|
|
406
|
+
|
|
407
|
+
Identifier: (path) => {
|
|
408
|
+
if (path.isReferencedIdentifier()) {
|
|
409
|
+
const varName = path.node.name;
|
|
410
|
+
if (varUsage.has(varName)) {
|
|
411
|
+
varUsage.get(varName).uses.push(path);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
// Second pass: find single-use variables
|
|
418
|
+
for (const [varName, usage] of varUsage) {
|
|
419
|
+
if (usage.uses.length === 1 && usage.declaration.node.init) {
|
|
420
|
+
singleUseVars.push({
|
|
421
|
+
type: 'single_use_temp',
|
|
422
|
+
name: varName,
|
|
423
|
+
declaration: usage.declaration,
|
|
424
|
+
use: usage.uses[0],
|
|
425
|
+
startLine: usage.declaration.node.loc?.start.line
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return singleUseVars;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
async detectDeadCode(_ast, _content, metrics) {
|
|
434
|
+
const deadCode = [];
|
|
435
|
+
|
|
436
|
+
traverse(_ast, {
|
|
437
|
+
// Unreachable code after return/throw
|
|
438
|
+
'ReturnStatement|ThrowStatement': (path) => {
|
|
439
|
+
const parent = path.parent;
|
|
440
|
+
if (parent.type === 'BlockStatement') {
|
|
441
|
+
const siblings = parent.body;
|
|
442
|
+
const currentIndex = siblings.indexOf(path.node);
|
|
443
|
+
|
|
444
|
+
for (let i = currentIndex + 1; i < siblings.length; i++) {
|
|
445
|
+
deadCode.push({
|
|
446
|
+
type: 'unreachable_code',
|
|
447
|
+
node: siblings[i],
|
|
448
|
+
reason: 'after_return_throw',
|
|
449
|
+
startLine: siblings[i].loc?.start.line
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
},
|
|
454
|
+
|
|
455
|
+
// Unused functions
|
|
456
|
+
FunctionDeclaration: (path) => {
|
|
457
|
+
const functionName = path.node.id?.name;
|
|
458
|
+
if (functionName && !this.isFunctionUsed(functionName, ast)) {
|
|
459
|
+
deadCode.push({
|
|
460
|
+
type: 'unused_function',
|
|
461
|
+
node: path.node,
|
|
462
|
+
name: functionName,
|
|
463
|
+
startLine: path.node.loc?.start.line
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
},
|
|
467
|
+
|
|
468
|
+
// Always false conditions
|
|
469
|
+
IfStatement: (path) => {
|
|
470
|
+
if (path.node.test.type === 'BooleanLiteral' && !path.node.test.value) {
|
|
471
|
+
deadCode.push({
|
|
472
|
+
type: 'dead_branch',
|
|
473
|
+
node: path.node.consequent,
|
|
474
|
+
reason: 'always_false',
|
|
475
|
+
startLine: path.node.loc?.start.line
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
return deadCode;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
async detectDuplicateCode(_ast, _content, metrics) {
|
|
485
|
+
const duplicates = [];
|
|
486
|
+
const codeBlocks = new Map();
|
|
487
|
+
const minBlockSize = 5; // minimum lines for duplicate detection
|
|
488
|
+
|
|
489
|
+
traverse(_ast, {
|
|
490
|
+
BlockStatement: (path) => {
|
|
491
|
+
if (path.node.body.length >= minBlockSize) {
|
|
492
|
+
const blockHash = this.hashCodeBlock(path.node);
|
|
493
|
+
|
|
494
|
+
if (codeBlocks.has(blockHash)) {
|
|
495
|
+
const original = codeBlocks.get(blockHash);
|
|
496
|
+
duplicates.push({
|
|
497
|
+
type: 'duplicate_code',
|
|
498
|
+
original: original,
|
|
499
|
+
duplicate: path,
|
|
500
|
+
startLine: path.node.loc?.start.line,
|
|
501
|
+
endLine: path.node.loc?.end.line,
|
|
502
|
+
lines: path.node.loc?.end.line - path.node.loc?.start.line + 1
|
|
503
|
+
});
|
|
504
|
+
} else {
|
|
505
|
+
codeBlocks.set(blockHash, path);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
return duplicates;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
async detectNestedConditionals(_ast, _content, metrics) {
|
|
515
|
+
const nestedConditionals = [];
|
|
516
|
+
const nestingThreshold = 3;
|
|
517
|
+
|
|
518
|
+
const checkNesting = (path, depth = 0) => {
|
|
519
|
+
if (depth > nestingThreshold) {
|
|
520
|
+
nestedConditionals.push({
|
|
521
|
+
type: 'nested_conditional',
|
|
522
|
+
node: path.node,
|
|
523
|
+
path: path,
|
|
524
|
+
depth: depth,
|
|
525
|
+
startLine: path.node.loc?.start.line,
|
|
526
|
+
endLine: path.node.loc?.end.line
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Check nested ifs
|
|
531
|
+
traverse(path.node, {
|
|
532
|
+
IfStatement: (innerPath) => {
|
|
533
|
+
if (innerPath.node !== path.node) {
|
|
534
|
+
checkNesting(innerPath, depth + 1);
|
|
535
|
+
innerPath.skip();
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}, path.scope, path);
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
traverse(_ast, {
|
|
542
|
+
IfStatement: (path) => checkNesting(path, 1)
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
return nestedConditionals;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
async detectMagicNumbers(_ast, _content, metrics) {
|
|
549
|
+
const magicNumbers = [];
|
|
550
|
+
const ignoredNumbers = new Set([0, 1, -1, 2, 10, 100, 1000]);
|
|
551
|
+
|
|
552
|
+
traverse(_ast, {
|
|
553
|
+
NumericLiteral: (path) => {
|
|
554
|
+
const value = path.node.value;
|
|
555
|
+
|
|
556
|
+
// Skip common/obvious numbers
|
|
557
|
+
if (ignoredNumbers.has(value)) return;
|
|
558
|
+
|
|
559
|
+
// Skip array indices
|
|
560
|
+
if (path.parent.type === 'MemberExpression' && path.parent.computed) return;
|
|
561
|
+
|
|
562
|
+
// Skip in constant declarations
|
|
563
|
+
if (path.findParent(p => p.isVariableDeclarator() &&
|
|
564
|
+
p.parent.kind === 'const')) return;
|
|
565
|
+
|
|
566
|
+
magicNumbers.push({
|
|
567
|
+
type: 'magic_number',
|
|
568
|
+
node: path.node,
|
|
569
|
+
path: path,
|
|
570
|
+
value: value,
|
|
571
|
+
context: path.parent.type,
|
|
572
|
+
startLine: path.node.loc?.start.line
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
return magicNumbers;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
async detectLargeClasses(_ast, _content, metrics) {
|
|
581
|
+
const largeClasses = [];
|
|
582
|
+
const methodThreshold = 10;
|
|
583
|
+
const propertyThreshold = 15;
|
|
584
|
+
|
|
585
|
+
traverse(_ast, {
|
|
586
|
+
ClassDeclaration: (path) => {
|
|
587
|
+
const methods = path.node.body.body.filter(m =>
|
|
588
|
+
m.type === 'ClassMethod' || m.type === 'ClassProperty'
|
|
589
|
+
);
|
|
590
|
+
|
|
591
|
+
const methodCount = methods.filter(m => m.type === 'ClassMethod').length;
|
|
592
|
+
const propertyCount = methods.filter(m => m.type === 'ClassProperty').length;
|
|
593
|
+
|
|
594
|
+
if (methodCount > methodThreshold || propertyCount > propertyThreshold) {
|
|
595
|
+
largeClasses.push({
|
|
596
|
+
type: 'large_class',
|
|
597
|
+
node: path.node,
|
|
598
|
+
path: path,
|
|
599
|
+
name: path.node.id?.name,
|
|
600
|
+
methodCount: methodCount,
|
|
601
|
+
propertyCount: propertyCount,
|
|
602
|
+
totalMembers: methods.length,
|
|
603
|
+
startLine: path.node.loc?.start.line,
|
|
604
|
+
endLine: path.node.loc?.end.line
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
return largeClasses;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// Refactoring suggesters
|
|
614
|
+
|
|
615
|
+
async suggestMethodExtraction(_detection, _ast, content) {
|
|
616
|
+
const suggestion = {
|
|
617
|
+
type: 'extract_method',
|
|
618
|
+
description: `Extract method '${detection.name}' (${detection.lines} lines)`,
|
|
619
|
+
location: {
|
|
620
|
+
start: detection.startLine,
|
|
621
|
+
end: detection.endLine
|
|
622
|
+
},
|
|
623
|
+
impact: Math.min(10, Math.floor(detection.lines / 10) + Math.floor(detection.complexity / 5)),
|
|
624
|
+
details: `Method has ${detection.lines} lines and complexity of ${detection.complexity}. Consider extracting logical sections into separate methods.`,
|
|
625
|
+
suggestedRefactoring: this.generateMethodExtractionSuggestion(_detection)
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
return suggestion;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
async suggestVariableExtraction(_detection, _ast, content) {
|
|
632
|
+
const suggestion = {
|
|
633
|
+
type: 'extract_variable',
|
|
634
|
+
description: 'Extract complex expression into variable',
|
|
635
|
+
location: {
|
|
636
|
+
start: detection.startLine,
|
|
637
|
+
end: detection.endLine
|
|
638
|
+
},
|
|
639
|
+
impact: Math.min(5, detection.complexity - 2),
|
|
640
|
+
details: `Expression has complexity of ${detection.complexity}. Extract into a named variable for better readability.`,
|
|
641
|
+
suggestedRefactoring: this.generateVariableExtractionSuggestion(_detection)
|
|
642
|
+
};
|
|
643
|
+
|
|
644
|
+
return suggestion;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
async suggestParameterObject(_detection, _ast, content) {
|
|
648
|
+
const suggestion = {
|
|
649
|
+
type: 'introduce_parameter_object',
|
|
650
|
+
description: `Group ${detection.parameterCount} parameters in '${detection.name}'`,
|
|
651
|
+
location: {
|
|
652
|
+
start: detection.startLine,
|
|
653
|
+
end: detection.startLine
|
|
654
|
+
},
|
|
655
|
+
impact: Math.min(7, detection.parameterCount - 3),
|
|
656
|
+
details: `Method has ${detection.parameterCount} parameters: ${detection.parameters.join(', ')}. Consider grouping related parameters into an object.`,
|
|
657
|
+
suggestedRefactoring: this.generateParameterObjectSuggestion(_detection)
|
|
658
|
+
};
|
|
659
|
+
|
|
660
|
+
return suggestion;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
async suggestPolymorphism(_detection, _ast, content) {
|
|
664
|
+
const suggestion = {
|
|
665
|
+
type: 'replace_conditional',
|
|
666
|
+
description: `Replace ${detection.type === 'complex_switch' ? 'switch' : 'conditional'} with polymorphism`,
|
|
667
|
+
location: {
|
|
668
|
+
start: detection.startLine,
|
|
669
|
+
end: detection.endLine
|
|
670
|
+
},
|
|
671
|
+
impact: Math.min(8, detection.branches || detection.cases),
|
|
672
|
+
details: `Complex ${detection.type === 'complex_switch' ? 'switch' : 'conditional'} with ${detection.branches || detection.cases} branches. Consider using polymorphism or strategy pattern.`,
|
|
673
|
+
suggestedRefactoring: this.generatePolymorphismSuggestion(_detection)
|
|
674
|
+
};
|
|
675
|
+
|
|
676
|
+
return suggestion;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
async suggestInlineTemp(_detection, _ast, content) {
|
|
680
|
+
const suggestion = {
|
|
681
|
+
type: 'inline_temp',
|
|
682
|
+
description: `Inline temporary variable '${detection.name}'`,
|
|
683
|
+
location: {
|
|
684
|
+
start: detection.startLine,
|
|
685
|
+
end: detection.startLine
|
|
686
|
+
},
|
|
687
|
+
impact: 2,
|
|
688
|
+
details: `Variable '${detection.name}' is used only once. Consider inlining it.`,
|
|
689
|
+
suggestedRefactoring: this.generateInlineTempSuggestion(_detection)
|
|
690
|
+
};
|
|
691
|
+
|
|
692
|
+
return suggestion;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
async suggestDeadCodeRemoval(_detection, _ast, content) {
|
|
696
|
+
const suggestion = {
|
|
697
|
+
type: 'remove_dead_code',
|
|
698
|
+
description: `Remove ${detection.type.replace('_', ' ')}${detection.name ? `: ${detection.name}` : ''}`,
|
|
699
|
+
location: {
|
|
700
|
+
start: detection.startLine,
|
|
701
|
+
end: detection.node.loc?.end.line || detection.startLine
|
|
702
|
+
},
|
|
703
|
+
impact: 5,
|
|
704
|
+
details: `${detection.type === 'unreachable_code' ? 'Code is unreachable' : detection.type === 'unused_function' ? 'Function is never called' : 'Code is dead'}`,
|
|
705
|
+
suggestedRefactoring: {
|
|
706
|
+
action: 'delete',
|
|
707
|
+
lines: [detection.startLine, detection.node.loc?.end.line || detection.startLine]
|
|
708
|
+
}
|
|
709
|
+
};
|
|
710
|
+
|
|
711
|
+
return suggestion;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
async suggestCodeConsolidation(_detection, _ast, content) {
|
|
715
|
+
const suggestion = {
|
|
716
|
+
type: 'consolidate_duplicates',
|
|
717
|
+
description: `Extract duplicate code block (${detection.lines} lines)`,
|
|
718
|
+
location: {
|
|
719
|
+
start: detection.startLine,
|
|
720
|
+
end: detection.endLine
|
|
721
|
+
},
|
|
722
|
+
impact: Math.min(9, detection.lines),
|
|
723
|
+
details: `Found duplicate code block. Extract into a shared function.`,
|
|
724
|
+
suggestedRefactoring: this.generateConsolidationSuggestion(_detection)
|
|
725
|
+
};
|
|
726
|
+
|
|
727
|
+
return suggestion;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
async suggestConditionalSimplification(_detection, _ast, content) {
|
|
731
|
+
const suggestion = {
|
|
732
|
+
type: 'simplify_conditionals',
|
|
733
|
+
description: `Simplify nested conditionals (depth: ${detection.depth})`,
|
|
734
|
+
location: {
|
|
735
|
+
start: detection.startLine,
|
|
736
|
+
end: detection.endLine
|
|
737
|
+
},
|
|
738
|
+
impact: Math.min(7, detection.depth * 2),
|
|
739
|
+
details: `Deeply nested conditionals (${detection.depth} levels). Consider early returns or guard clauses.`,
|
|
740
|
+
suggestedRefactoring: this.generateConditionalSimplificationSuggestion(_detection)
|
|
741
|
+
};
|
|
742
|
+
|
|
743
|
+
return suggestion;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
async suggestConstantExtraction(_detection, _ast, content) {
|
|
747
|
+
const suggestion = {
|
|
748
|
+
type: 'replace_magic_numbers',
|
|
749
|
+
description: `Replace magic number ${detection.value}`,
|
|
750
|
+
location: {
|
|
751
|
+
start: detection.startLine,
|
|
752
|
+
end: detection.startLine
|
|
753
|
+
},
|
|
754
|
+
impact: 3,
|
|
755
|
+
details: `Magic number ${detection.value} found in ${detection.context}. Extract to named constant.`,
|
|
756
|
+
suggestedRefactoring: this.generateConstantExtractionSuggestion(_detection)
|
|
757
|
+
};
|
|
758
|
+
|
|
759
|
+
return suggestion;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
async suggestClassDecomposition(_detection, _ast, content) {
|
|
763
|
+
const suggestion = {
|
|
764
|
+
type: 'decompose_class',
|
|
765
|
+
description: `Decompose large class '${detection.name}' (${detection.totalMembers} members)`,
|
|
766
|
+
location: {
|
|
767
|
+
start: detection.startLine,
|
|
768
|
+
end: detection.endLine
|
|
769
|
+
},
|
|
770
|
+
impact: Math.min(10, Math.floor(detection.totalMembers / 5)),
|
|
771
|
+
details: `Class has ${detection.methodCount} methods and ${detection.propertyCount} properties. Consider splitting into smaller, focused classes.`,
|
|
772
|
+
suggestedRefactoring: this.generateClassDecompositionSuggestion(_detection)
|
|
773
|
+
};
|
|
774
|
+
|
|
775
|
+
return suggestion;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// Helper methods
|
|
779
|
+
|
|
780
|
+
getMethodName(path) {
|
|
781
|
+
if (path.node.id) {
|
|
782
|
+
return path.node.id.name;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// Check if it's a method in a class
|
|
786
|
+
if (path.parent.type === 'ClassMethod') {
|
|
787
|
+
return path.parent.key.name;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// Check if it's assigned to a variable
|
|
791
|
+
if (path.parent.type === 'VariableDeclarator') {
|
|
792
|
+
return path.parent.id.name;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// Check if it's a property
|
|
796
|
+
if (path.parent.type === 'ObjectProperty') {
|
|
797
|
+
return path.parent.key.name || path.parent.key.value;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
return 'anonymous';
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
calculateMethodComplexity(path) {
|
|
804
|
+
let complexity = 1;
|
|
805
|
+
|
|
806
|
+
traverse(path.node, {
|
|
807
|
+
IfStatement: () => complexity++,
|
|
808
|
+
ConditionalExpression: () => complexity++,
|
|
809
|
+
SwitchCase: () => complexity++,
|
|
810
|
+
WhileStatement: () => complexity++,
|
|
811
|
+
ForStatement: () => complexity++,
|
|
812
|
+
DoWhileStatement: () => complexity++,
|
|
813
|
+
LogicalExpression: (innerPath) => {
|
|
814
|
+
if (innerPath.node.operator === '&&' || innerPath.node.operator === '||') {
|
|
815
|
+
complexity++;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
}, path.scope, path);
|
|
819
|
+
|
|
820
|
+
return complexity;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
calculateExpressionComplexity(node, depth = 0) {
|
|
824
|
+
if (!node) return depth;
|
|
825
|
+
|
|
826
|
+
let maxDepth = depth;
|
|
827
|
+
|
|
828
|
+
// Check different expression types
|
|
829
|
+
if (node.type === 'CallExpression') {
|
|
830
|
+
maxDepth = Math.max(maxDepth, this.calculateExpressionComplexity(node.callee, depth + 1));
|
|
831
|
+
for (const arg of node.arguments) {
|
|
832
|
+
maxDepth = Math.max(maxDepth, this.calculateExpressionComplexity(arg, depth + 1));
|
|
833
|
+
}
|
|
834
|
+
} else if (node.type === 'MemberExpression') {
|
|
835
|
+
maxDepth = Math.max(maxDepth, this.calculateExpressionComplexity(node.object, depth + 1));
|
|
836
|
+
} else if (node.type === 'ConditionalExpression') {
|
|
837
|
+
maxDepth = Math.max(maxDepth,
|
|
838
|
+
this.calculateExpressionComplexity(node.test, depth + 1),
|
|
839
|
+
this.calculateExpressionComplexity(node.consequent, depth + 1),
|
|
840
|
+
this.calculateExpressionComplexity(node.alternate, depth + 1)
|
|
841
|
+
);
|
|
842
|
+
} else if (node.type === 'BinaryExpression' || node.type === 'LogicalExpression') {
|
|
843
|
+
maxDepth = Math.max(maxDepth,
|
|
844
|
+
this.calculateExpressionComplexity(node.left, depth + 1),
|
|
845
|
+
this.calculateExpressionComplexity(node.right, depth + 1)
|
|
846
|
+
);
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
return maxDepth;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
countConditionalBranches(path) {
|
|
853
|
+
let branches = 1; // Initial if branch
|
|
854
|
+
|
|
855
|
+
let current = path.node;
|
|
856
|
+
while (current.alternate) {
|
|
857
|
+
branches++;
|
|
858
|
+
if (current.alternate.type === 'IfStatement') {
|
|
859
|
+
current = current.alternate;
|
|
860
|
+
} else {
|
|
861
|
+
break;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
return branches;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
isFunctionUsed(functionName, ast) {
|
|
869
|
+
let used = false;
|
|
870
|
+
|
|
871
|
+
traverse(_ast, {
|
|
872
|
+
CallExpression: (path) => {
|
|
873
|
+
if (path.node.callee.type === 'Identifier' &&
|
|
874
|
+
path.node.callee.name === functionName) {
|
|
875
|
+
used = true;
|
|
876
|
+
path.stop();
|
|
877
|
+
}
|
|
878
|
+
},
|
|
879
|
+
Identifier: (path) => {
|
|
880
|
+
if (path.node.name === functionName &&
|
|
881
|
+
path.isReferencedIdentifier() &&
|
|
882
|
+
!path.isFunction()) {
|
|
883
|
+
used = true;
|
|
884
|
+
path.stop();
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
});
|
|
888
|
+
|
|
889
|
+
return used;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
hashCodeBlock(node) {
|
|
893
|
+
// Simple hash based on code structure
|
|
894
|
+
const code = generate(node, { compact: true }).code;
|
|
895
|
+
return code.replace(/\s+/g, ' ').trim();
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
// Suggestion generators
|
|
899
|
+
|
|
900
|
+
generateMethodExtractionSuggestion(_detection) {
|
|
901
|
+
return {
|
|
902
|
+
action: 'extract_method',
|
|
903
|
+
extractedMethods: [
|
|
904
|
+
{
|
|
905
|
+
name: `extracted${detection.name.charAt(0).toUpperCase() + detection.name.slice(1)}Part1`,
|
|
906
|
+
description: 'Extract first logical section',
|
|
907
|
+
suggestedLines: [detection.startLine + 5, detection.startLine + 15]
|
|
908
|
+
},
|
|
909
|
+
{
|
|
910
|
+
name: `extracted${detection.name.charAt(0).toUpperCase() + detection.name.slice(1)}Part2`,
|
|
911
|
+
description: 'Extract second logical section',
|
|
912
|
+
suggestedLines: [detection.startLine + 16, detection.endLine - 5]
|
|
913
|
+
}
|
|
914
|
+
]
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
generateVariableExtractionSuggestion(_detection) {
|
|
919
|
+
return {
|
|
920
|
+
action: 'extract_variable',
|
|
921
|
+
variableName: 'extractedExpression',
|
|
922
|
+
insertBefore: detection.startLine
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
generateParameterObjectSuggestion(_detection) {
|
|
927
|
+
return {
|
|
928
|
+
action: 'introduce_parameter_object',
|
|
929
|
+
objectName: `${detection.name}Options`,
|
|
930
|
+
groupedParameters: detection.parameters.slice(2), // Keep first 2 params separate
|
|
931
|
+
keepParameters: detection.parameters.slice(0, 2)
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
generatePolymorphismSuggestion(_detection) {
|
|
936
|
+
return {
|
|
937
|
+
action: 'replace_with_polymorphism',
|
|
938
|
+
strategyPattern: true,
|
|
939
|
+
suggestedClasses: ['BaseHandler', 'TypeAHandler', 'TypeBHandler'],
|
|
940
|
+
interfaceMethod: 'handle'
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
generateInlineTempSuggestion(_detection) {
|
|
945
|
+
return {
|
|
946
|
+
action: 'inline_variable',
|
|
947
|
+
variableName: detection.name,
|
|
948
|
+
declarationLine: detection.declaration.node.loc?.start.line,
|
|
949
|
+
usageLine: detection.use.node.loc?.start.line
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
generateConsolidationSuggestion(_detection) {
|
|
954
|
+
return {
|
|
955
|
+
action: 'extract_shared_function',
|
|
956
|
+
functionName: 'extractedSharedFunction',
|
|
957
|
+
originalLocations: [
|
|
958
|
+
{
|
|
959
|
+
start: detection.original.node.loc?.start.line,
|
|
960
|
+
end: detection.original.node.loc?.end.line
|
|
961
|
+
},
|
|
962
|
+
{
|
|
963
|
+
start: detection.duplicate.node.loc?.start.line,
|
|
964
|
+
end: detection.duplicate.node.loc?.end.line
|
|
965
|
+
}
|
|
966
|
+
]
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
generateConditionalSimplificationSuggestion(_detection) {
|
|
971
|
+
return {
|
|
972
|
+
action: 'simplify_nested_conditionals',
|
|
973
|
+
techniques: ['early_return', 'guard_clauses', 'extract_condition'],
|
|
974
|
+
suggestedStructure: 'Use guard clauses for edge cases and early returns'
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
generateConstantExtractionSuggestion(_detection) {
|
|
979
|
+
const constantName = this.suggestConstantName(detection.value, detection.context);
|
|
980
|
+
return {
|
|
981
|
+
action: 'extract_constant',
|
|
982
|
+
constantName: constantName,
|
|
983
|
+
value: detection.value,
|
|
984
|
+
scope: 'module' // or 'class' depending on context
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
generateClassDecompositionSuggestion(_detection) {
|
|
989
|
+
return {
|
|
990
|
+
action: 'decompose_class',
|
|
991
|
+
suggestedClasses: [
|
|
992
|
+
{
|
|
993
|
+
name: `${detection.name}Core`,
|
|
994
|
+
description: 'Core functionality',
|
|
995
|
+
methods: 'Core business logic methods'
|
|
996
|
+
},
|
|
997
|
+
{
|
|
998
|
+
name: `${detection.name}Utils`,
|
|
999
|
+
description: 'Utility methods',
|
|
1000
|
+
methods: 'Helper and utility methods'
|
|
1001
|
+
},
|
|
1002
|
+
{
|
|
1003
|
+
name: `${detection.name}Config`,
|
|
1004
|
+
description: 'Configuration and setup',
|
|
1005
|
+
methods: 'Configuration-related methods'
|
|
1006
|
+
}
|
|
1007
|
+
]
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
suggestConstantName(value, context) {
|
|
1012
|
+
// Generate meaningful constant names based on value and context
|
|
1013
|
+
const contextMap = {
|
|
1014
|
+
'BinaryExpression': 'THRESHOLD',
|
|
1015
|
+
'IfStatement': 'CONDITION',
|
|
1016
|
+
'ForStatement': 'LIMIT',
|
|
1017
|
+
'CallExpression': 'PARAMETER'
|
|
1018
|
+
};
|
|
1019
|
+
|
|
1020
|
+
const baseContext = contextMap[context] || 'VALUE';
|
|
1021
|
+
return `${baseContext}_${Math.abs(value).toString().replace('.', '_')}`;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
/**
|
|
1025
|
+
* Apply refactoring suggestion
|
|
1026
|
+
*/
|
|
1027
|
+
async applySuggestion(suggestion, options = {}) {
|
|
1028
|
+
console.log(chalk.blue(`🔧 Applying ${suggestion.type} refactoring...`));
|
|
1029
|
+
|
|
1030
|
+
try {
|
|
1031
|
+
// This would integrate with the actual refactoring implementation
|
|
1032
|
+
// For now, it's a placeholder showing the structure
|
|
1033
|
+
|
|
1034
|
+
const result = {
|
|
1035
|
+
success: false,
|
|
1036
|
+
changes: [],
|
|
1037
|
+
error: null
|
|
1038
|
+
};
|
|
1039
|
+
|
|
1040
|
+
switch (suggestion.type) {
|
|
1041
|
+
case 'extract_method':
|
|
1042
|
+
result.changes = await this.applyMethodExtraction(suggestion);
|
|
1043
|
+
break;
|
|
1044
|
+
case 'extract_variable':
|
|
1045
|
+
result.changes = await this.applyVariableExtraction(suggestion);
|
|
1046
|
+
break;
|
|
1047
|
+
case 'inline_temp':
|
|
1048
|
+
result.changes = await this.applyInlineTemp(suggestion);
|
|
1049
|
+
break;
|
|
1050
|
+
case 'remove_dead_code':
|
|
1051
|
+
result.changes = await this.applyDeadCodeRemoval(suggestion);
|
|
1052
|
+
break;
|
|
1053
|
+
default:
|
|
1054
|
+
throw new Error(`Refactoring type ${suggestion.type} not implemented`);
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
result.success = true;
|
|
1058
|
+
return result;
|
|
1059
|
+
|
|
1060
|
+
} catch (error) {
|
|
1061
|
+
console.error(chalk.red(`Failed to apply refactoring: ${error.message}`));
|
|
1062
|
+
return {
|
|
1063
|
+
success: false,
|
|
1064
|
+
changes: [],
|
|
1065
|
+
error: error.message
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
// Placeholder methods for applying refactorings
|
|
1071
|
+
async applyMethodExtraction(suggestion) {
|
|
1072
|
+
// Implementation would use AST transformation
|
|
1073
|
+
return [{
|
|
1074
|
+
type: 'extract_method',
|
|
1075
|
+
file: suggestion.filePath,
|
|
1076
|
+
description: `Extracted method from lines ${suggestion.location.start}-${suggestion.location.end}`
|
|
1077
|
+
}];
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
async applyVariableExtraction(suggestion) {
|
|
1081
|
+
return [{
|
|
1082
|
+
type: 'extract_variable',
|
|
1083
|
+
file: suggestion.filePath,
|
|
1084
|
+
description: `Extracted variable at line ${suggestion.location.start}`
|
|
1085
|
+
}];
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
async applyInlineTemp(suggestion) {
|
|
1089
|
+
return [{
|
|
1090
|
+
type: 'inline_temp',
|
|
1091
|
+
file: suggestion.filePath,
|
|
1092
|
+
description: `Inlined variable at line ${suggestion.location.start}`
|
|
1093
|
+
}];
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
async applyDeadCodeRemoval(suggestion) {
|
|
1097
|
+
return [{
|
|
1098
|
+
type: 'remove_dead_code',
|
|
1099
|
+
file: suggestion.filePath,
|
|
1100
|
+
description: `Removed dead code at lines ${suggestion.location.start}-${suggestion.location.end}`
|
|
1101
|
+
}];
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
/**
|
|
1105
|
+
* Get refactoring statistics
|
|
1106
|
+
*/
|
|
1107
|
+
getStatistics() {
|
|
1108
|
+
const stats = {
|
|
1109
|
+
totalSuggestions: this.suggestions.length,
|
|
1110
|
+
byType: {},
|
|
1111
|
+
byPriority: {
|
|
1112
|
+
high: 0,
|
|
1113
|
+
medium: 0,
|
|
1114
|
+
low: 0
|
|
1115
|
+
},
|
|
1116
|
+
averageImpact: 0
|
|
1117
|
+
};
|
|
1118
|
+
|
|
1119
|
+
let totalImpact = 0;
|
|
1120
|
+
|
|
1121
|
+
for (const suggestion of this.suggestions) {
|
|
1122
|
+
// By type
|
|
1123
|
+
stats.byType[suggestion.type] = (stats.byType[suggestion.type] || 0) + 1;
|
|
1124
|
+
|
|
1125
|
+
// By priority
|
|
1126
|
+
stats.byPriority[suggestion.priority]++;
|
|
1127
|
+
|
|
1128
|
+
// Impact
|
|
1129
|
+
totalImpact += suggestion.impact || 0;
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
stats.averageImpact = stats.totalSuggestions > 0 ?
|
|
1133
|
+
(totalImpact / stats.totalSuggestions).toFixed(2) : 0;
|
|
1134
|
+
|
|
1135
|
+
return stats;
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
1139
|
module.exports = RefactoringSuggester;
|