aios-core 4.1.0 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/.aios-core/.session/current-session.json +14 -0
  2. package/.aios-core/core/registry/registry-schema.json +166 -166
  3. package/.aios-core/core/registry/service-registry.json +6585 -6585
  4. package/.aios-core/data/entity-registry.yaml +208 -8
  5. package/.aios-core/data/registry-update-log.jsonl +165 -0
  6. package/.aios-core/development/scripts/approval-workflow.js +642 -642
  7. package/.aios-core/development/scripts/backup-manager.js +606 -606
  8. package/.aios-core/development/scripts/branch-manager.js +389 -389
  9. package/.aios-core/development/scripts/code-quality-improver.js +1311 -1311
  10. package/.aios-core/development/scripts/commit-message-generator.js +849 -849
  11. package/.aios-core/development/scripts/conflict-resolver.js +674 -674
  12. package/.aios-core/development/scripts/dependency-analyzer.js +637 -637
  13. package/.aios-core/development/scripts/diff-generator.js +351 -351
  14. package/.aios-core/development/scripts/elicitation-engine.js +384 -384
  15. package/.aios-core/development/scripts/elicitation-session-manager.js +299 -299
  16. package/.aios-core/development/scripts/git-wrapper.js +461 -461
  17. package/.aios-core/development/scripts/manifest-preview.js +244 -244
  18. package/.aios-core/development/scripts/metrics-tracker.js +775 -775
  19. package/.aios-core/development/scripts/modification-validator.js +554 -554
  20. package/.aios-core/development/scripts/pattern-learner.js +1224 -1224
  21. package/.aios-core/development/scripts/performance-analyzer.js +757 -757
  22. package/.aios-core/development/scripts/refactoring-suggester.js +1138 -1138
  23. package/.aios-core/development/scripts/rollback-handler.js +530 -530
  24. package/.aios-core/development/scripts/security-checker.js +358 -358
  25. package/.aios-core/development/scripts/template-engine.js +239 -239
  26. package/.aios-core/development/scripts/template-validator.js +278 -278
  27. package/.aios-core/development/scripts/test-generator.js +843 -843
  28. package/.aios-core/development/scripts/transaction-manager.js +589 -589
  29. package/.aios-core/development/scripts/usage-tracker.js +673 -673
  30. package/.aios-core/development/scripts/validate-filenames.js +226 -226
  31. package/.aios-core/development/scripts/version-tracker.js +526 -526
  32. package/.aios-core/development/scripts/yaml-validator.js +396 -396
  33. package/.aios-core/development/tasks/validate-next-story.md +99 -2
  34. package/.aios-core/development/templates/service-template/README.md.hbs +158 -158
  35. package/.aios-core/development/templates/service-template/__tests__/index.test.ts.hbs +237 -237
  36. package/.aios-core/development/templates/service-template/client.ts.hbs +403 -403
  37. package/.aios-core/development/templates/service-template/errors.ts.hbs +182 -182
  38. package/.aios-core/development/templates/service-template/index.ts.hbs +120 -120
  39. package/.aios-core/development/templates/service-template/package.json.hbs +87 -87
  40. package/.aios-core/development/templates/service-template/types.ts.hbs +145 -145
  41. package/.aios-core/development/templates/squad-template/LICENSE +21 -21
  42. package/.aios-core/docs/SHARD-TRANSLATION-GUIDE.md +335 -0
  43. package/.aios-core/docs/component-creation-guide.md +458 -0
  44. package/.aios-core/docs/session-update-pattern.md +307 -0
  45. package/.aios-core/docs/standards/AIOS-FRAMEWORK-MASTER.md +1963 -0
  46. package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1-SUMMARY.md +1190 -0
  47. package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1.md +439 -0
  48. package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO.md +5398 -0
  49. package/.aios-core/docs/standards/V3-ARCHITECTURAL-DECISIONS.md +523 -0
  50. package/.aios-core/docs/template-syntax.md +267 -0
  51. package/.aios-core/docs/troubleshooting-guide.md +625 -0
  52. package/.aios-core/infrastructure/templates/aios-sync.yaml.template +193 -193
  53. package/.aios-core/infrastructure/templates/coderabbit.yaml.template +279 -279
  54. package/.aios-core/infrastructure/templates/github-workflows/ci.yml.template +169 -169
  55. package/.aios-core/infrastructure/templates/github-workflows/pr-automation.yml.template +330 -330
  56. package/.aios-core/infrastructure/templates/github-workflows/release.yml.template +196 -196
  57. package/.aios-core/infrastructure/templates/gitignore/gitignore-aios-base.tmpl +63 -63
  58. package/.aios-core/infrastructure/templates/gitignore/gitignore-brownfield-merge.tmpl +18 -18
  59. package/.aios-core/infrastructure/templates/gitignore/gitignore-node.tmpl +85 -85
  60. package/.aios-core/infrastructure/templates/gitignore/gitignore-python.tmpl +145 -145
  61. package/.aios-core/infrastructure/tests/utilities-audit-results.json +501 -0
  62. package/.aios-core/install-manifest.yaml +101 -101
  63. package/.aios-core/local-config.yaml.template +70 -70
  64. package/.aios-core/manifests/agents.csv +29 -0
  65. package/.aios-core/manifests/schema/manifest-schema.json +190 -190
  66. package/.aios-core/manifests/tasks.csv +198 -0
  67. package/.aios-core/manifests/workers.csv +204 -0
  68. package/.aios-core/monitor/hooks/lib/__init__.py +1 -1
  69. package/.aios-core/monitor/hooks/lib/enrich.py +58 -58
  70. package/.aios-core/monitor/hooks/lib/send_event.py +47 -47
  71. package/.aios-core/monitor/hooks/notification.py +29 -29
  72. package/.aios-core/monitor/hooks/post_tool_use.py +45 -45
  73. package/.aios-core/monitor/hooks/pre_compact.py +29 -29
  74. package/.aios-core/monitor/hooks/pre_tool_use.py +40 -40
  75. package/.aios-core/monitor/hooks/stop.py +29 -29
  76. package/.aios-core/monitor/hooks/subagent_stop.py +29 -29
  77. package/.aios-core/monitor/hooks/user_prompt_submit.py +38 -38
  78. package/.aios-core/product/templates/adr.hbs +125 -125
  79. package/.aios-core/product/templates/component-react-tmpl.tsx +98 -98
  80. package/.aios-core/product/templates/dbdr.hbs +241 -241
  81. package/.aios-core/product/templates/engine/schemas/adr.schema.json +102 -102
  82. package/.aios-core/product/templates/engine/schemas/dbdr.schema.json +205 -205
  83. package/.aios-core/product/templates/engine/schemas/epic.schema.json +175 -175
  84. package/.aios-core/product/templates/engine/schemas/pmdr.schema.json +175 -175
  85. package/.aios-core/product/templates/engine/schemas/prd-v2.schema.json +300 -300
  86. package/.aios-core/product/templates/engine/schemas/prd.schema.json +152 -152
  87. package/.aios-core/product/templates/engine/schemas/story.schema.json +222 -222
  88. package/.aios-core/product/templates/engine/schemas/task.schema.json +154 -154
  89. package/.aios-core/product/templates/epic.hbs +212 -212
  90. package/.aios-core/product/templates/eslintrc-security.json +32 -32
  91. package/.aios-core/product/templates/github-actions-cd.yml +212 -212
  92. package/.aios-core/product/templates/github-actions-ci.yml +172 -172
  93. package/.aios-core/product/templates/pmdr.hbs +186 -186
  94. package/.aios-core/product/templates/prd-v2.0.hbs +216 -216
  95. package/.aios-core/product/templates/prd.hbs +201 -201
  96. package/.aios-core/product/templates/shock-report-tmpl.html +502 -502
  97. package/.aios-core/product/templates/story.hbs +263 -263
  98. package/.aios-core/product/templates/task.hbs +170 -170
  99. package/.aios-core/product/templates/tmpl-comment-on-examples.sql +158 -158
  100. package/.aios-core/product/templates/tmpl-migration-script.sql +91 -91
  101. package/.aios-core/product/templates/tmpl-rls-granular-policies.sql +104 -104
  102. package/.aios-core/product/templates/tmpl-rls-kiss-policy.sql +10 -10
  103. package/.aios-core/product/templates/tmpl-rls-roles.sql +135 -135
  104. package/.aios-core/product/templates/tmpl-rls-simple.sql +77 -77
  105. package/.aios-core/product/templates/tmpl-rls-tenant.sql +152 -152
  106. package/.aios-core/product/templates/tmpl-rollback-script.sql +77 -77
  107. package/.aios-core/product/templates/tmpl-seed-data.sql +140 -140
  108. package/.aios-core/product/templates/tmpl-smoke-test.sql +16 -16
  109. package/.aios-core/product/templates/tmpl-staging-copy-merge.sql +139 -139
  110. package/.aios-core/product/templates/tmpl-stored-proc.sql +140 -140
  111. package/.aios-core/product/templates/tmpl-trigger.sql +152 -152
  112. package/.aios-core/product/templates/tmpl-view-materialized.sql +133 -133
  113. package/.aios-core/product/templates/tmpl-view.sql +177 -177
  114. package/.aios-core/product/templates/token-exports-css-tmpl.css +240 -240
  115. package/.aios-core/quality/schemas/quality-metrics.schema.json +233 -233
  116. package/.aios-core/scripts/migrate-framework-docs.sh +300 -300
  117. package/.aios-core/scripts/pm.sh +0 -0
  118. package/.claude/hooks/enforce-architecture-first.py +196 -196
  119. package/.claude/hooks/mind-clone-governance.py +192 -192
  120. package/.claude/hooks/read-protection.py +151 -151
  121. package/.claude/hooks/slug-validation.py +176 -176
  122. package/.claude/hooks/sql-governance.py +182 -182
  123. package/.claude/hooks/write-path-validation.py +194 -194
  124. package/.claude/rules/agent-authority.md +105 -0
  125. package/.claude/rules/coderabbit-integration.md +93 -0
  126. package/.claude/rules/ids-principles.md +112 -0
  127. package/.claude/rules/story-lifecycle.md +139 -0
  128. package/.claude/rules/workflow-execution.md +150 -0
  129. package/LICENSE +48 -48
  130. package/bin/aios-minimal.js +0 -0
  131. package/bin/aios.js +0 -0
  132. package/package.json +1 -1
  133. package/packages/aios-install/bin/aios-install.js +0 -0
  134. package/packages/aios-install/bin/edmcp.js +0 -0
  135. package/packages/aios-pro-cli/bin/aios-pro.js +0 -0
  136. package/packages/installer/src/wizard/pro-setup.js +433 -49
  137. package/scripts/check-markdown-links.py +352 -352
  138. package/scripts/code-intel-health-check.js +343 -0
  139. package/scripts/dashboard-parallel-dev.sh +0 -0
  140. package/scripts/dashboard-parallel-phase3.sh +0 -0
  141. package/scripts/dashboard-parallel-phase4.sh +0 -0
  142. package/scripts/glue/README.md +355 -0
  143. package/scripts/glue/compose-agent-prompt.cjs +362 -0
  144. package/scripts/install-monitor-hooks.sh +0 -0
  145. package/.aios-core/lib/build.json +0 -1
@@ -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;