diff --git a/src/core/commands/extract-variable-command.ts b/src/core/commands/extract-variable-command.ts index 6f55cdd..9ea6e61 100644 --- a/src/core/commands/extract-variable-command.ts +++ b/src/core/commands/extract-variable-command.ts @@ -25,9 +25,13 @@ export class ExtractVariableCommand implements RefactoringCommand { this.validateOptions(options); const location = LocationRange.from(options.location as LocationRange); this.astService = ASTService.createForFile(file); - await this.performExtraction(this.astService.findNodeByLocation(location), options); + const count = await this.performExtraction(this.astService.findNodeByLocation(location), options); await this.astService.saveSourceFile(this.astService.loadSourceFile(file)); - return new RefactoringCommandResult(); + const variableName = options.name as string; + const message = options.all + ? `Successfully extracted variable '${variableName}' (${count} occurrence${count === 1 ? '' : 's'} replaced)` + : `Successfully extracted variable '${variableName}'`; + return new RefactoringCommandResult(message); } validateOptions(options: CommandOptions): void { @@ -43,11 +47,12 @@ export class ExtractVariableCommand implements RefactoringCommand { return '\nExamples:\n refakts extract-variable "[src/file.ts 8:15-8:29]" --name "result"'; } - private async performExtraction(targetNode: Node, options: CommandOptions): Promise { + private async performExtraction(targetNode: Node, options: CommandOptions): Promise { if (options.all) { - await this.extractAllOccurrences(targetNode, options.name as string); + return await this.extractAllOccurrences(targetNode, options.name as string); } else { await this.extractSingleOccurrence(targetNode, options.name as string); + return 1; } } @@ -59,12 +64,13 @@ export class ExtractVariableCommand implements RefactoringCommand { targetNode.replaceWithText(uniqueName); } - private async extractAllOccurrences(targetNode: Node, variableName: string): Promise { + private async extractAllOccurrences(targetNode: Node, variableName: string): Promise { this.validateExpressionNode(targetNode); const allExpressions = this.expressionMatcher.findAllMatchingExpressions(targetNode); this.validateExpressionsFound(allExpressions); this.extractInEachScope(this.expressionMatcher.groupExpressionsByScope(allExpressions), variableName); + return allExpressions.length; } private validateExpressionNode(node: Node): void { diff --git a/src/core/commands/rename-command.ts b/src/core/commands/rename-command.ts index e4a1b58..42c83c6 100644 --- a/src/core/commands/rename-command.ts +++ b/src/core/commands/rename-command.ts @@ -26,9 +26,9 @@ export class RenameCommand implements RefactoringCommand { this.astService = ASTService.createForFile(file); this.variableLocator = new VariableLocator(this.astService.getProject()); this.nameValidator = new VariableNameValidator(); - await this.performRename(this.astService.findNodeByLocation(location), options.to as string); + const result = await this.performRename(this.astService.findNodeByLocation(location), options.to as string); await this.astService.saveSourceFile(this.astService.loadSourceFile(file)); - return new RefactoringCommandResult(); + return result; } validateOptions(options: CommandOptions): void { @@ -44,13 +44,22 @@ export class RenameCommand implements RefactoringCommand { return '\nExamples:\n refakts rename "[src/file.ts 5:8-5:18]" --to newName'; } - private async performRename(node: Node, newName: string): Promise { + private async performRename(node: Node, newName: string): Promise { NodeAnalyzer.validateIdentifierNode(node); - const sourceFile = node.getSourceFile(); - const nodeResult = this.findVariableNodesAtPosition(node, sourceFile); - + const nodeResult = this.findVariableNodesAtPosition(node, node.getSourceFile()); this.validateNewName(nodeResult.declaration, newName); - await this.createRenameTransformation(nodeResult, newName).transform(sourceFile); + return this.applyRename(nodeResult, nodeResult.variable, newName); + } + + private async applyRename(nodeResult: VariableNodeResult, oldName: string, newName: string): Promise { + const transformResult = await this.createRenameTransformation(nodeResult, newName).transformWithResult(); + if (!transformResult.success) { + throw new Error(transformResult.message || 'Rename transformation failed'); + } + const count = transformResult.changesCount; + return new RefactoringCommandResult( + `Successfully renamed '${oldName}' to '${newName}' (${count} occurrence${count === 1 ? '' : 's'} renamed)` + ); } private findVariableNodesAtPosition(node: Node, sourceFile: SourceFile) { diff --git a/src/core/commands/sort-methods-command.ts b/src/core/commands/sort-methods-command.ts index c15ab9b..a0d2bd2 100644 --- a/src/core/commands/sort-methods-command.ts +++ b/src/core/commands/sort-methods-command.ts @@ -22,9 +22,12 @@ export class SortMethodsCommand implements RefactoringCommand { this.validateOptions(options); const location = LocationRange.from(options.location as LocationRange); this.astService = ASTService.createForFile(file); - await this.performMethodSorting(this.findTargetClass(this.astService.findNodeByLocation(location))); + const targetClass = this.findTargetClass(this.astService.findNodeByLocation(location)); + const { className, methodCount } = await this.performMethodSorting(targetClass); await this.astService.saveSourceFile(this.astService.loadSourceFile(file)); - return new RefactoringCommandResult(); + return new RefactoringCommandResult( + `Successfully sorted ${methodCount} method${methodCount === 1 ? '' : 's'} in class '${className}'` + ); } private findTargetClass(targetNode: Node): ClassDeclaration { @@ -60,11 +63,13 @@ export class SortMethodsCommand implements RefactoringCommand { this.consoleOutput = consoleOutput; } - private async performMethodSorting(targetClass: ClassDeclaration): Promise { + private async performMethodSorting(targetClass: ClassDeclaration): Promise<{ className: string; methodCount: number }> { const methods = this.methodFinder.findMethods(targetClass); - if (this.shouldSkipSorting(methods)) return; - - this.reorderMethodsInClass(targetClass, this.getSortedMethods(methods)); + const className = targetClass.getName() ?? 'unknown'; + if (!this.shouldSkipSorting(methods)) { + this.reorderMethodsInClass(targetClass, this.getSortedMethods(methods)); + } + return { className, methodCount: targetClass.getMethods().length }; } private shouldSkipSorting(methods: MethodInfo[]): boolean { diff --git a/tests/fixtures/commands/extract-variable/cli-location-format.expected.out b/tests/fixtures/commands/extract-variable/cli-location-format.expected.out new file mode 100644 index 0000000..7e4d1b5 --- /dev/null +++ b/tests/fixtures/commands/extract-variable/cli-location-format.expected.out @@ -0,0 +1 @@ +Successfully extracted variable 'sum' diff --git a/tests/fixtures/commands/extract-variable/complex-expression-first-single-occurence.expected.out b/tests/fixtures/commands/extract-variable/complex-expression-first-single-occurence.expected.out new file mode 100644 index 0000000..6fedec3 --- /dev/null +++ b/tests/fixtures/commands/extract-variable/complex-expression-first-single-occurence.expected.out @@ -0,0 +1 @@ +Successfully extracted variable 'upperName' diff --git a/tests/fixtures/commands/extract-variable/complex-expression-last-selected.expected.out b/tests/fixtures/commands/extract-variable/complex-expression-last-selected.expected.out new file mode 100644 index 0000000..710d4fa --- /dev/null +++ b/tests/fixtures/commands/extract-variable/complex-expression-last-selected.expected.out @@ -0,0 +1 @@ +Successfully extracted variable 'upperName' (2 occurrences replaced) diff --git a/tests/fixtures/commands/extract-variable/complex-expression-last-single-occurence.expected.out b/tests/fixtures/commands/extract-variable/complex-expression-last-single-occurence.expected.out new file mode 100644 index 0000000..6fedec3 --- /dev/null +++ b/tests/fixtures/commands/extract-variable/complex-expression-last-single-occurence.expected.out @@ -0,0 +1 @@ +Successfully extracted variable 'upperName' diff --git a/tests/fixtures/commands/extract-variable/complex-expression.expected.out b/tests/fixtures/commands/extract-variable/complex-expression.expected.out new file mode 100644 index 0000000..710d4fa --- /dev/null +++ b/tests/fixtures/commands/extract-variable/complex-expression.expected.out @@ -0,0 +1 @@ +Successfully extracted variable 'upperName' (2 occurrences replaced) diff --git a/tests/fixtures/commands/extract-variable/conditional-block.expected.out b/tests/fixtures/commands/extract-variable/conditional-block.expected.out new file mode 100644 index 0000000..2d0b587 --- /dev/null +++ b/tests/fixtures/commands/extract-variable/conditional-block.expected.out @@ -0,0 +1 @@ +Successfully extracted variable 'userAge' diff --git a/tests/fixtures/commands/extract-variable/extract-variable-holding-function.expected.out b/tests/fixtures/commands/extract-variable/extract-variable-holding-function.expected.out new file mode 100644 index 0000000..6bc3986 --- /dev/null +++ b/tests/fixtures/commands/extract-variable/extract-variable-holding-function.expected.out @@ -0,0 +1 @@ +Successfully extracted variable 'toUpperCase' (2 occurrences replaced) diff --git a/tests/fixtures/commands/extract-variable/location-simple-expression.expected.out b/tests/fixtures/commands/extract-variable/location-simple-expression.expected.out new file mode 100644 index 0000000..68279b9 --- /dev/null +++ b/tests/fixtures/commands/extract-variable/location-simple-expression.expected.out @@ -0,0 +1 @@ +Successfully extracted variable 'result' diff --git a/tests/fixtures/commands/extract-variable/multi-arg-method-call.expected.out b/tests/fixtures/commands/extract-variable/multi-arg-method-call.expected.out new file mode 100644 index 0000000..8c923de --- /dev/null +++ b/tests/fixtures/commands/extract-variable/multi-arg-method-call.expected.out @@ -0,0 +1 @@ +Successfully extracted variable 'formattedName' (2 occurrences replaced) diff --git a/tests/fixtures/commands/extract-variable/multi-arg-method-reference.expected.out b/tests/fixtures/commands/extract-variable/multi-arg-method-reference.expected.out new file mode 100644 index 0000000..fa5506a --- /dev/null +++ b/tests/fixtures/commands/extract-variable/multi-arg-method-reference.expected.out @@ -0,0 +1 @@ +Successfully extracted variable 'substringMethod' (2 occurrences replaced) diff --git a/tests/fixtures/commands/extract-variable/multiple-occurrences.expected.out b/tests/fixtures/commands/extract-variable/multiple-occurrences.expected.out new file mode 100644 index 0000000..c4658d1 --- /dev/null +++ b/tests/fixtures/commands/extract-variable/multiple-occurrences.expected.out @@ -0,0 +1 @@ +Successfully extracted variable 'product' (3 occurrences replaced) diff --git a/tests/fixtures/commands/extract-variable/name-conflict.expected.out b/tests/fixtures/commands/extract-variable/name-conflict.expected.out new file mode 100644 index 0000000..d3e12a6 --- /dev/null +++ b/tests/fixtures/commands/extract-variable/name-conflict.expected.out @@ -0,0 +1 @@ +Successfully extracted variable 'product' diff --git a/tests/fixtures/commands/extract-variable/nested-calls.expected.out b/tests/fixtures/commands/extract-variable/nested-calls.expected.out new file mode 100644 index 0000000..7e4d1b5 --- /dev/null +++ b/tests/fixtures/commands/extract-variable/nested-calls.expected.out @@ -0,0 +1 @@ +Successfully extracted variable 'sum' diff --git a/tests/fixtures/commands/extract-variable/scope-isolation.expected.out b/tests/fixtures/commands/extract-variable/scope-isolation.expected.out new file mode 100644 index 0000000..63d0d40 --- /dev/null +++ b/tests/fixtures/commands/extract-variable/scope-isolation.expected.out @@ -0,0 +1 @@ +Successfully extracted variable 'sum' (2 occurrences replaced) diff --git a/tests/fixtures/commands/extract-variable/simple-expression.expected.out b/tests/fixtures/commands/extract-variable/simple-expression.expected.out new file mode 100644 index 0000000..cb7f2ad --- /dev/null +++ b/tests/fixtures/commands/extract-variable/simple-expression.expected.out @@ -0,0 +1 @@ +Successfully extracted variable 'area' diff --git a/tests/fixtures/commands/rename/arrow-function.expected.out b/tests/fixtures/commands/rename/arrow-function.expected.out new file mode 100644 index 0000000..a8ca7a3 --- /dev/null +++ b/tests/fixtures/commands/rename/arrow-function.expected.out @@ -0,0 +1 @@ +Successfully renamed 'x' to 'value' (3 occurrences renamed) diff --git a/tests/fixtures/commands/rename/block-scope.expected.out b/tests/fixtures/commands/rename/block-scope.expected.out new file mode 100644 index 0000000..3bdba69 --- /dev/null +++ b/tests/fixtures/commands/rename/block-scope.expected.out @@ -0,0 +1 @@ +Successfully renamed 'temp' to 'temporaryValue' (3 occurrences renamed) diff --git a/tests/fixtures/commands/rename/function-argument.expected.out b/tests/fixtures/commands/rename/function-argument.expected.out new file mode 100644 index 0000000..07d4ade --- /dev/null +++ b/tests/fixtures/commands/rename/function-argument.expected.out @@ -0,0 +1 @@ +Successfully renamed 'oldParam' to 'newParam' (3 occurrences renamed) diff --git a/tests/fixtures/commands/rename/location-simple-variable.expected.out b/tests/fixtures/commands/rename/location-simple-variable.expected.out new file mode 100644 index 0000000..3599e01 --- /dev/null +++ b/tests/fixtures/commands/rename/location-simple-variable.expected.out @@ -0,0 +1 @@ +Successfully renamed 'temp' to 'newName' (2 occurrences renamed) diff --git a/tests/fixtures/commands/rename/nested-scopes.expected.out b/tests/fixtures/commands/rename/nested-scopes.expected.out new file mode 100644 index 0000000..cdc81ed --- /dev/null +++ b/tests/fixtures/commands/rename/nested-scopes.expected.out @@ -0,0 +1 @@ +Successfully renamed 'items' to 'itemList' (5 occurrences renamed) diff --git a/tests/fixtures/commands/rename/scope-isolation.expected.out b/tests/fixtures/commands/rename/scope-isolation.expected.out new file mode 100644 index 0000000..e7d8ba4 --- /dev/null +++ b/tests/fixtures/commands/rename/scope-isolation.expected.out @@ -0,0 +1 @@ +Successfully renamed 'data' to 'processedData' (2 occurrences renamed) diff --git a/tests/fixtures/commands/rename/simple-variable.expected.out b/tests/fixtures/commands/rename/simple-variable.expected.out new file mode 100644 index 0000000..78ddf4d --- /dev/null +++ b/tests/fixtures/commands/rename/simple-variable.expected.out @@ -0,0 +1 @@ +Successfully renamed 'oldName' to 'newName' (2 occurrences renamed) diff --git a/tests/fixtures/commands/sort-methods/recursive-methods.expected.out b/tests/fixtures/commands/sort-methods/recursive-methods.expected.out new file mode 100644 index 0000000..772924c --- /dev/null +++ b/tests/fixtures/commands/sort-methods/recursive-methods.expected.out @@ -0,0 +1 @@ +Successfully sorted 2 methods in class 'MathProcessor' diff --git a/tests/fixtures/commands/sort-methods/simple-class.expected.out b/tests/fixtures/commands/sort-methods/simple-class.expected.out new file mode 100644 index 0000000..b2070b9 --- /dev/null +++ b/tests/fixtures/commands/sort-methods/simple-class.expected.out @@ -0,0 +1 @@ +Successfully sorted 4 methods in class 'Calculator'