From bc9285ce108a7b80d1de507e329e385a958a45b0 Mon Sep 17 00:00:00 2001 From: Tom Oram Date: Fri, 27 Dec 2013 15:37:55 +0000 Subject: [PATCH] Break EditingSession down into individual editing action classes. --- .../ConvertLocalToInstanceVariable.php | 62 +++-- .../Refactoring/Application/ExtractMethod.php | 85 ++++--- .../Application/RenameLocalVariable.php | 65 +++-- .../Application/SingleFileRefactoring.php | 85 +++++++ .../Domain/Model/EditingAction.php | 8 + .../Domain/Model/EditingAction/AddMethod.php | 150 ++++++++++++ .../Model/EditingAction/AddProperty.php | 37 +++ .../EditingAction/LocalVariableToInstance.php | 38 +++ .../Model/EditingAction/RenameVariable.php | 62 +++++ .../EditingAction/ReplaceWithMethodCall.php | 79 ++++++ .../Domain/Model/EditingSession.php | 121 +-------- .../Domain/Model/IndentationDetector.php | 56 +++++ .../Domain/Model/IndentingLineCollection.php | 36 +++ .../Refactoring/Domain/Model/Line.php | 43 ++++ .../Domain/Model/LineCollection.php | 82 +++++++ .../Refactoring/Utils/ToStringIterator.php | 52 ++++ .../RenameLocalVariableTest.php.orig | 51 ---- .../Model/EditingAction/AddMethodTest.php | 229 ++++++++++++++++++ .../Model/EditingAction/AddPropertyTest.php | 50 ++++ .../LocalVariableToInstanceTest.php | 114 +++++++++ .../EditingAction/RenameVariableTest.php | 117 +++++++++ .../ReplaceWithMethodCallTest.php | 122 ++++++++++ .../Domain/Model/EditingSessionTest.php | 36 +++ .../Domain/Model/IndentationDetectorTest.php | 68 ++++++ .../Model/IndentingLineCollectionTest.php | 109 +++++++++ .../Domain/Model/LineCollectionTest.php | 114 +++++++++ .../Refactoring/Domain/Model/LineTest.php | 43 ++++ .../Utils/ToStringIteratorTest.php | 49 ++++ 28 files changed, 1905 insertions(+), 258 deletions(-) create mode 100644 src/main/QafooLabs/Refactoring/Application/SingleFileRefactoring.php create mode 100644 src/main/QafooLabs/Refactoring/Domain/Model/EditingAction.php create mode 100644 src/main/QafooLabs/Refactoring/Domain/Model/EditingAction/AddMethod.php create mode 100644 src/main/QafooLabs/Refactoring/Domain/Model/EditingAction/AddProperty.php create mode 100644 src/main/QafooLabs/Refactoring/Domain/Model/EditingAction/LocalVariableToInstance.php create mode 100644 src/main/QafooLabs/Refactoring/Domain/Model/EditingAction/RenameVariable.php create mode 100644 src/main/QafooLabs/Refactoring/Domain/Model/EditingAction/ReplaceWithMethodCall.php create mode 100644 src/main/QafooLabs/Refactoring/Domain/Model/IndentationDetector.php create mode 100644 src/main/QafooLabs/Refactoring/Domain/Model/IndentingLineCollection.php create mode 100644 src/main/QafooLabs/Refactoring/Domain/Model/Line.php create mode 100644 src/main/QafooLabs/Refactoring/Domain/Model/LineCollection.php create mode 100644 src/main/QafooLabs/Refactoring/Utils/ToStringIterator.php delete mode 100644 src/test/QafooLabs/Refactoring/Application/RenameLocalVariableTest.php.orig create mode 100644 src/test/QafooLabs/Refactoring/Domain/Model/EditingAction/AddMethodTest.php create mode 100644 src/test/QafooLabs/Refactoring/Domain/Model/EditingAction/AddPropertyTest.php create mode 100644 src/test/QafooLabs/Refactoring/Domain/Model/EditingAction/LocalVariableToInstanceTest.php create mode 100644 src/test/QafooLabs/Refactoring/Domain/Model/EditingAction/RenameVariableTest.php create mode 100644 src/test/QafooLabs/Refactoring/Domain/Model/EditingAction/ReplaceWithMethodCallTest.php create mode 100644 src/test/QafooLabs/Refactoring/Domain/Model/EditingSessionTest.php create mode 100644 src/test/QafooLabs/Refactoring/Domain/Model/IndentationDetectorTest.php create mode 100644 src/test/QafooLabs/Refactoring/Domain/Model/IndentingLineCollectionTest.php create mode 100644 src/test/QafooLabs/Refactoring/Domain/Model/LineCollectionTest.php create mode 100644 src/test/QafooLabs/Refactoring/Domain/Model/LineTest.php create mode 100644 src/test/QafooLabs/Refactoring/Utils/ToStringIteratorTest.php diff --git a/src/main/QafooLabs/Refactoring/Application/ConvertLocalToInstanceVariable.php b/src/main/QafooLabs/Refactoring/Application/ConvertLocalToInstanceVariable.php index 2246cd7..5c7e925 100644 --- a/src/main/QafooLabs/Refactoring/Application/ConvertLocalToInstanceVariable.php +++ b/src/main/QafooLabs/Refactoring/Application/ConvertLocalToInstanceVariable.php @@ -13,56 +13,50 @@ use QafooLabs\Refactoring\Domain\Services\VariableScanner; use QafooLabs\Refactoring\Domain\Services\CodeAnalysis; use QafooLabs\Refactoring\Domain\Services\Editor; +use QafooLabs\Refactoring\Domain\Model\EditingAction\AddProperty; +use QafooLabs\Refactoring\Domain\Model\EditingAction\LocalVariableToInstance; -class ConvertLocalToInstanceVariable +class ConvertLocalToInstanceVariable extends SingleFileRefactoring { /** - * @var \QafooLabs\Refactoring\Domain\Services\VariableScanner + * @var Variable */ - private $variableScanner; + private $convertVariable; /** - * @var \QafooLabs\Refactoring\Domain\Services\CodeAnalysis + * @param int $line */ - private $codeAnalysis; + public function refactor(File $file, $line, Variable $convertVariable) + { + $this->file = $file; + $this->line = $line; + $this->convertVariable = $convertVariable; - /** - * @var \QafooLabs\Refactoring\Domain\Services\Editor - */ - private $editor; + $this->assertIsInsideMethod(); - public function __construct(VariableScanner $variableScanner, CodeAnalysis $codeAnalysis, Editor $editor) - { - $this->variableScanner = $variableScanner; - $this->codeAnalysis = $codeAnalysis; - $this->editor = $editor; + $this->startEditingSession(); + $this->addProperty(); + $this->convertVariablesToInstanceVariables(); + $this->completeEditingSession(); } - public function refactor(File $file, $line, Variable $convertVariable) + private function addProperty() { - if ( ! $this->codeAnalysis->isInsideMethod($file, LineRange::fromSingleLine($line))) { - throw RefactoringException::rangeIsNotInsideMethod(LineRange::fromSingleLine($line)); - } + $line = $this->codeAnalysis->getLineOfLastPropertyDefinedInScope($this->file, $this->line); - $instanceVariable = $convertVariable->convertToInstance(); - $lastPropertyLine = $this->codeAnalysis->getLineOfLastPropertyDefinedInScope($file, $line); - - $selectedMethodLineRange = $this->codeAnalysis->findMethodRange($file, LineRange::fromSingleLine($line)); - $definedVariables = $this->variableScanner->scanForVariables( - $file, $selectedMethodLineRange + $this->session->addEdit( + new AddProperty($line, $this->convertVariable->getName()) ); + } - if ( ! $definedVariables->contains($convertVariable)) { - throw RefactoringException::variableNotInRange($convertVariable, $selectedMethodLineRange); - } - - $buffer = $this->editor->openBuffer($file); + private function convertVariablesToInstanceVariables() + { + $definedVariables = $this->getDefinedVariables(); - $session = new EditingSession($buffer); - $session->addProperty($lastPropertyLine, $convertVariable->getName()); - $session->replaceString($definedVariables, $convertVariable, $instanceVariable); + if ( ! $definedVariables->contains($this->convertVariable)) { + throw RefactoringException::variableNotInRange($this->convertVariable, $selectedMethodLineRange); + } - $this->editor->save(); + $this->session->addEdit(new LocalVariableToInstance($definedVariables, $this->convertVariable)); } } - diff --git a/src/main/QafooLabs/Refactoring/Application/ExtractMethod.php b/src/main/QafooLabs/Refactoring/Application/ExtractMethod.php index 079bced..121138b 100644 --- a/src/main/QafooLabs/Refactoring/Application/ExtractMethod.php +++ b/src/main/QafooLabs/Refactoring/Application/ExtractMethod.php @@ -11,60 +11,91 @@ use QafooLabs\Refactoring\Domain\Services\VariableScanner; use QafooLabs\Refactoring\Domain\Services\CodeAnalysis; use QafooLabs\Refactoring\Domain\Services\Editor; +use QafooLabs\Refactoring\Domain\Model\LineCollection; +use QafooLabs\Refactoring\Domain\Model\EditingAction\AddMethod; +use QafooLabs\Refactoring\Domain\Model\EditingAction\ReplaceWithMethodCall; /** * Extract Method Refactoring */ -class ExtractMethod +class ExtractMethod extends SingleFileRefactoring { /** - * @var \QafooLabs\Refactoring\Domain\Services\VariableScanner + * @var LineRange */ - private $variableScanner; + private $extractRange; /** - * @var \QafooLabs\Refactoring\Domain\Services\CodeAnalysis + * @var MethodSignature */ - private $codeAnalysis; + private $newMethod; /** - * @var \QafooLabs\Refactoring\Domain\Services\Editor + * @param string $newMethodName */ - private $editor; - - public function __construct(VariableScanner $variableScanner, CodeAnalysis $codeAnalysis, Editor $editor) + public function refactor(File $file, LineRange $extractRange, $newMethodName) { - $this->variableScanner = $variableScanner; - $this->codeAnalysis = $codeAnalysis; - $this->editor = $editor; + $this->file = $file; + $this->extractRange = $extractRange; + + $this->assertIsInsideMethod(); + + $this->createNewMethodSignature($newMethodName); + + $this->startEditingSession(); + $this->replaceCodeWithMethodCall(); + $this->addNewMethod(); + $this->completeEditingSession(); } - public function refactor(File $file, LineRange $extractRange, $newMethodName) + protected function assertIsInsideMethod() { - if ( ! $this->codeAnalysis->isInsideMethod($file, $extractRange)) { - throw RefactoringException::rangeIsNotInsideMethod($extractRange); + if ( ! $this->codeAnalysis->isInsideMethod($this->file, $this->extractRange)) { + throw RefactoringException::rangeIsNotInsideMethod($this->extractRange); } + } - $isStatic = $this->codeAnalysis->isMethodStatic($file, $extractRange); - $methodRange = $this->codeAnalysis->findMethodRange($file, $extractRange); - $selectedCode = $extractRange->sliceCode($file->getCode()); - - $extractVariables = $this->variableScanner->scanForVariables($file, $extractRange); - $methodVariables = $this->variableScanner->scanForVariables($file, $methodRange); + private function createNewMethodSignature($newMethodName) + { + $extractVariables = $this->variableScanner->scanForVariables($this->file, $this->extractRange); + $methodVariables = $this->variableScanner->scanForVariables($this->file, $this->findMethodRange()); - $buffer = $this->editor->openBuffer($file); + $isStatic = $this->codeAnalysis->isMethodStatic($this->file, $this->extractRange); - $newMethod = new MethodSignature( + $this->newMethod = new MethodSignature( $newMethodName, $isStatic ? MethodSignature::IS_STATIC : 0, $methodVariables->variablesFromSelectionUsedBefore($extractVariables), $methodVariables->variablesFromSelectionUsedAfter($extractVariables) ); + } - $session = new EditingSession($buffer); - $session->replaceRangeWithMethodCall($extractRange, $newMethod); - $session->addMethod($methodRange->getEnd(), $newMethod, $selectedCode); + private function addNewMethod() + { + $this->session->addEdit(new AddMethod( + $this->findMethodRange()->getEnd(), + $this->newMethod, + $this->getSelectedCode() + )); + } + + private function replaceCodeWithMethodCall() + { + $this->session->addEdit(new ReplaceWithMethodCall( + $this->extractRange, + $this->newMethod + )); + } - $this->editor->save(); + private function findMethodRange() + { + return $this->codeAnalysis->findMethodRange($this->file, $this->extractRange); + } + + private function getSelectedCode() + { + return LineCollection::createFromArray( + $this->extractRange->sliceCode($this->file->getCode()) + ); } } diff --git a/src/main/QafooLabs/Refactoring/Application/RenameLocalVariable.php b/src/main/QafooLabs/Refactoring/Application/RenameLocalVariable.php index a19171d..4cd51ef 100644 --- a/src/main/QafooLabs/Refactoring/Application/RenameLocalVariable.php +++ b/src/main/QafooLabs/Refactoring/Application/RenameLocalVariable.php @@ -12,63 +12,58 @@ use QafooLabs\Refactoring\Domain\Services\VariableScanner; use QafooLabs\Refactoring\Domain\Services\CodeAnalysis; use QafooLabs\Refactoring\Domain\Services\Editor; +use QafooLabs\Refactoring\Domain\Model\EditingAction\RenameVariable; /** * Rename Local Variable Refactoring */ -class RenameLocalVariable +class RenameLocalVariable extends SingleFileRefactoring { /** - * @var \QafooLabs\Refactoring\Domain\Services\VariableScanner + * @var Variable */ - private $variableScanner; + private $oldName; /** - * @var \QafooLabs\Refactoring\Domain\Services\CodeAnalysis + * @var Variable */ - private $codeAnalysis; + private $newName; /** - * @var \QafooLabs\Refactoring\Domain\Services\Editor + * @param int $line */ - private $editor; - - public function __construct(VariableScanner $variableScanner, CodeAnalysis $codeAnalysis, Editor $editor) - { - $this->variableScanner = $variableScanner; - $this->codeAnalysis = $codeAnalysis; - $this->editor = $editor; - } - public function refactor(File $file, $line, Variable $oldName, Variable $newName) { - if ( ! $this->codeAnalysis->isInsideMethod($file, LineRange::fromSingleLine($line))) { - throw RefactoringException::rangeIsNotInsideMethod(LineRange::fromSingleLine($line)); - } + $this->file = $file; + $this->line = $line; + $this->newName = $newName; + $this->oldName = $oldName; - if ( ! $oldName->isLocal()) { - throw RefactoringException::variableNotLocal($oldName); - } + $this->assertIsInsideMethod(); - if ( ! $newName->isLocal()) { - throw RefactoringException::variableNotLocal($newName); - } + $this->assertVariableIsLocal($this->oldName); + $this->assertVariableIsLocal($this->newName); - $selectedMethodLineRange = $this->codeAnalysis->findMethodRange($file, LineRange::fromSingleLine($line)); - $definedVariables = $this->variableScanner->scanForVariables( - $file, $selectedMethodLineRange - ); + $this->startEditingSession(); + $this->renameLocalVariable(); + $this->completeEditingSession(); + } - if ( ! $definedVariables->contains($oldName)) { - throw RefactoringException::variableNotInRange($oldName, $selectedMethodLineRange); + private function assertVariableIsLocal(Variable $variable) + { + if ( ! $variable->isLocal()) { + throw RefactoringException::variableNotLocal($variable); } + } - $buffer = $this->editor->openBuffer($file); + private function renameLocalVariable() + { + $definedVariables = $this->getDefinedVariables(); - $session = new EditingSession($buffer); - $session->replaceString($definedVariables, $oldName, $newName); + if ( ! $definedVariables->contains($this->oldName)) { + throw RefactoringException::variableNotInRange($this->oldName, $selectedMethodLineRange); + } - $this->editor->save(); + $this->session->addEdit(new RenameVariable($definedVariables, $this->oldName, $this->newName)); } } - diff --git a/src/main/QafooLabs/Refactoring/Application/SingleFileRefactoring.php b/src/main/QafooLabs/Refactoring/Application/SingleFileRefactoring.php new file mode 100644 index 0000000..b9cd329 --- /dev/null +++ b/src/main/QafooLabs/Refactoring/Application/SingleFileRefactoring.php @@ -0,0 +1,85 @@ +variableScanner = $variableScanner; + $this->codeAnalysis = $codeAnalysis; + $this->editor = $editor; + } + + protected function assertIsInsideMethod() + { + if ( ! $this->codeAnalysis->isInsideMethod($this->file, LineRange::fromSingleLine($this->line))) { + throw RefactoringException::rangeIsNotInsideMethod(LineRange::fromSingleLine($this->line)); + } + } + + protected function startEditingSession() + { + $buffer = $this->editor->openBuffer($this->file); + + $this->session = new EditingSession($buffer); + } + + protected function completeEditingSession() + { + $this->session->performEdits(); + + $this->editor->save(); + } + + protected function getDefinedVariables() + { + $selectedMethodLineRange = $this->codeAnalysis->findMethodRange($this->file, LineRange::fromSingleLine($this->line)); + + $definedVariables = $this->variableScanner->scanForVariables( + $this->file, $selectedMethodLineRange + ); + + return $definedVariables; + } +} diff --git a/src/main/QafooLabs/Refactoring/Domain/Model/EditingAction.php b/src/main/QafooLabs/Refactoring/Domain/Model/EditingAction.php new file mode 100644 index 0000000..513a855 --- /dev/null +++ b/src/main/QafooLabs/Refactoring/Domain/Model/EditingAction.php @@ -0,0 +1,8 @@ +lineNumber = $lineNumber; + $this->newMethod = $newMethod; + $this->selectedCode = $selectedCode; + } + + public function performEdit(EditorBuffer $buffer) + { + $this->newCode = new IndentingLineCollection(); + + $this->newCode->addIndentation(); + + $this->addMethodOpening(); + $this->addMethodBody(); + $this->addReturnStatement(); + $this->addMethodClosing(); + + $buffer->append($this->lineNumber, $this->getNewCodeAsStringArray()); + } + + private function addMethodOpening() + { + $this->newCode->appendBlankLine(); + + $this->newCode->appendString($this->getNewMethodSignatureString()); + $this->newCode->appendString('{'); + + $this->newCode->addIndentation(); + } + + /** + * @return string + */ + private function getNewMethodSignatureString() + { + return sprintf( + 'private %sfunction %s(%s)', + ($this->newMethod->isStatic() ? 'static ' : ''), + $this->newMethod->getName(), + $this->createVariableList($this->newMethod->arguments()) + ); + } + + /** + * @param string[] $variables + * + * @return string + */ + private function createVariableList(array $variables) + { + return implode(', ', array_map(function ($variableName) { + return '$' . $variableName; + }, $variables)); + } + + private function addMethodBody() + { + $this->newCode->appendLines($this->getUnindentedSelectedCode()); + } + + private function getUnindentedSelectedCode() + { + $detector = new IndentationDetector($this->selectedCode); + + $lines = array_map(function ($line) use ($detector) { + return substr($line, $detector->getMinIndentation()); + }, iterator_to_array(new ToStringIterator($this->selectedCode->getIterator()))); + + return LineCollection::createFromArray($lines); + } + + private function addReturnStatement() + { + $returnVars = $this->newMethod->returnVariables(); + + $numVariables = count($returnVars); + + if ($numVariables === 0) { + return; + } + + $returnVariable = '$' . reset($returnVars); + + if ($numVariables > 1) { + $returnVariable = 'array(' . $this->createVariableList($returnVars) . ')'; + } + + $this->newCode->appendBlankLine(); + $this->newCode->appendString('return ' . $returnVariable . ';'); + } + + private function addMethodClosing() + { + $this->newCode->removeIndentation(); + $this->newCode->appendString('}'); + } + + + /** + * @return string[] + */ + private function getNewCodeAsStringArray() + { + $toString = new ToStringIterator($this->newCode->getIterator()); + + return iterator_to_array($toString); + } +} diff --git a/src/main/QafooLabs/Refactoring/Domain/Model/EditingAction/AddProperty.php b/src/main/QafooLabs/Refactoring/Domain/Model/EditingAction/AddProperty.php new file mode 100644 index 0000000..fde3cad --- /dev/null +++ b/src/main/QafooLabs/Refactoring/Domain/Model/EditingAction/AddProperty.php @@ -0,0 +1,37 @@ +line = $line; + $this->propertyName = $propertyName; + } + + public function performEdit(EditorBuffer $buffer) + { + $buffer->append($this->line, array( + ' private $' . $this->propertyName . ';', + '' + )); + } +} diff --git a/src/main/QafooLabs/Refactoring/Domain/Model/EditingAction/LocalVariableToInstance.php b/src/main/QafooLabs/Refactoring/Domain/Model/EditingAction/LocalVariableToInstance.php new file mode 100644 index 0000000..036bcf1 --- /dev/null +++ b/src/main/QafooLabs/Refactoring/Domain/Model/EditingAction/LocalVariableToInstance.php @@ -0,0 +1,38 @@ +definedVars = $definedVars; + $this->variable = $variable; + } + + public function performEdit(EditorBuffer $buffer) + { + $renamer = new RenameVariable( + $this->definedVars, + $this->variable, + $this->variable->convertToInstance() + ); + + $renamer->performEdit($buffer); + } +} diff --git a/src/main/QafooLabs/Refactoring/Domain/Model/EditingAction/RenameVariable.php b/src/main/QafooLabs/Refactoring/Domain/Model/EditingAction/RenameVariable.php new file mode 100644 index 0000000..69a4ab1 --- /dev/null +++ b/src/main/QafooLabs/Refactoring/Domain/Model/EditingAction/RenameVariable.php @@ -0,0 +1,62 @@ +definedVars = $definedVars; + $this->oldName = $oldName; + $this->newName = $newName; + } + + public function performEdit(EditorBuffer $buffer) + { + foreach ($this->getLinesVariableIsUsedOn() as $line) { + $buffer->replaceString( + $line, + $this->oldName->getToken(), + $this->newName->getToken() + ); + } + } + + /** + * @return int[] + */ + private function getLinesVariableIsUsedOn() + { + $variables = $this->definedVars->all(); + $variableName = $this->oldName->getName(); + + $lines = array(); + + if (isset($variables[$variableName])) { + $lines = $variables[$variableName]; + } + + return $lines; + } +} + diff --git a/src/main/QafooLabs/Refactoring/Domain/Model/EditingAction/ReplaceWithMethodCall.php b/src/main/QafooLabs/Refactoring/Domain/Model/EditingAction/ReplaceWithMethodCall.php new file mode 100644 index 0000000..c7010e0 --- /dev/null +++ b/src/main/QafooLabs/Refactoring/Domain/Model/EditingAction/ReplaceWithMethodCall.php @@ -0,0 +1,79 @@ +range = $range; + $this->newMethod = $newMethod; + } + + public function performEdit(EditorBuffer $buffer) + { + $buffer->replace($this->range, array($this->getIndent() . $this->getMethodCall())); + } + + private function getIndent() + { + return ' '; + } + + private function getMethodCall() + { + return sprintf( + '%s%s%s(%s);', + $this->getReturnVariables(), + ($this->newMethod->isStatic() ? 'self::' : '$this->'), + $this->newMethod->getName(), + $this->createVariableList($this->newMethod->arguments()) + ); + } + + private function getReturnVariables() + { + $returnVars = $this->newMethod->returnVariables(); + + $numVariables = count($returnVars); + + if ($numVariables === 0) { + return; + } + + $returnVariable = '$' . reset($returnVars); + + if ($numVariables > 1) { + $returnVariable = 'list(' . $this->createVariableList($returnVars) . ')'; + } + + return $returnVariable . ' = '; + } + + /** + * @param string[] $variables + * + * @return string + */ + private function createVariableList(array $variables) + { + return implode(', ', array_map(function ($variableName) { + return '$' . $variableName; + }, $variables)); + } +} diff --git a/src/main/QafooLabs/Refactoring/Domain/Model/EditingSession.php b/src/main/QafooLabs/Refactoring/Domain/Model/EditingSession.php index b3c37c5..1cc60f5 100644 --- a/src/main/QafooLabs/Refactoring/Domain/Model/EditingSession.php +++ b/src/main/QafooLabs/Refactoring/Domain/Model/EditingSession.php @@ -21,126 +21,25 @@ class EditingSession */ private $buffer; + /** + * @var EditingAction[] + */ + private $actions = array(); + public function __construct(EditorBuffer $buffer) { $this->buffer = $buffer; } - public function replaceString(DefinedVariables $definedVariables, Variable $oldName, Variable $newName) - { - $this->replaceStringInArray($definedVariables->all(), $oldName, $newName); - } - - private function replaceStringInArray(array $variables, Variable $oldName, Variable $newName) - { - if (isset($variables[$oldName->getName()])) { - foreach ($variables[$oldName->getName()] as $line) { - $this->buffer->replaceString($line, $oldName->getToken(), $newName->getToken()); - } - } - } - - public function replaceRangeWithMethodCall(LineRange $range, MethodSignature $newMethod) - { - $argumentLine = $this->implodeVariables($newMethod->arguments()); - - $code = $newMethod->isStatic() ? 'self::%s(%s);' : '$this->%s(%s);'; - $call = sprintf($code, $newMethod->getName(), $argumentLine); - - if (count($newMethod->returnVariables()) == 1) { - $call = '$' . $newMethod->returnVariables()[0] . ' = ' . $call; - } else if (count($newMethod->returnVariables()) > 1) { - $call = 'list(' . $this->implodeVariables($newMethod->returnVariables()) . ') = ' . $call; - } - - $this->buffer->replace($range, array($this->whitespace(8) . $call)); - } - - public function addMethod($line, MethodSignature $newMethod, $selectedCode) - { - if (count($newMethod->returnVariables()) == 1) { - $selectedCode[] = ''; - $selectedCode[] = $this->whitespace(8) . 'return $' . $newMethod->returnVariables()[0] . ';'; - } else if (count($newMethod->returnVariables()) > 1) { - $selectedCode[] = ''; - $selectedCode[] = $this->whitespace(8) . 'return array(' . $this->implodeVariables($newMethod->returnVariables()) . ');'; - } - - $methodCode = array_merge( - array( - '', - $this->whitespace(4) . $this->renderMethodSignature($newMethod), - $this->whitespace(4) . '{' - ), - $this->realign($selectedCode, 8), - array($this->whitespace(4) . '}') - ); - - $this->buffer->append($line, $methodCode); - } - - private function alignedAtWhitespaces(array $lines) + public function addEdit(EditingAction $action) { - return array_reduce($lines, function ($minWhitespace, $line) { - if ($this->isEmptyLine($line)) { - return $minWhitespace; - } - - return min($minWhitespace, $this->leftWhitespacesOf($line)); - }, 100); + $this->actions[] = $action; } - private function realign(array $lines, $atWhitespaces) + public function performEdits() { - $minWhitespaces = $this->alignedAtWhitespaces($lines); - $whitespaceCorrection = $atWhitespaces - $minWhitespaces; - - if ($whitespaceCorrection === 0) { - return $lines; + foreach ($this->actions as $action) { + $action->performEdit($this->buffer); } - - return array_map(function ($line) use($whitespaceCorrection) { - if ($whitespaceCorrection > 0) { - return $this->whitespace($whitespaceCorrection) . $line; - } - - return substr($line, $whitespaceCorrection - 2); // Why -2? - }, $lines); - } - - private function isEmptyLine($line) - { - return trim($line) === ""; - } - - private function leftWhitespacesOf($line) - { - return strlen($line) - strlen(ltrim($line)); - } - - private function renderMethodSignature(MethodSignature $method) - { - $paramLine = $this->implodeVariables($method->arguments()); - - return sprintf('private%sfunction %s(%s)', $method->isStatic() ? ' static ' : ' ', $method->getName(), $paramLine); - } - - private function implodeVariables($variableNames) - { - return implode(', ', array_map(function ($variableName) { - return '$' . $variableName; - }, $variableNames)); - } - - public function addProperty($line, $propertyName) - { - $this->buffer->append($line, array( - $this->whitespace(4) . 'private $' . $propertyName . ';', '' - )); - } - - private function whitespace($number) - { - return str_repeat(' ', $number); } } diff --git a/src/main/QafooLabs/Refactoring/Domain/Model/IndentationDetector.php b/src/main/QafooLabs/Refactoring/Domain/Model/IndentationDetector.php new file mode 100644 index 0000000..7a8d5cc --- /dev/null +++ b/src/main/QafooLabs/Refactoring/Domain/Model/IndentationDetector.php @@ -0,0 +1,56 @@ +lines = $lines; + } + + /** + * @return int + */ + public function getMinIndentation() + { + return array_reduce( + iterator_to_array($this->lines), + function ($minIndentation, $line) { + $indentation = $line->getIndentation(); + + if ($line->isEmpty()) { + return $minIndentation; + } + + if ($minIndentation === null) { + return $indentation; + } + + return min($minIndentation, $indentation); + } + ); + } + + /** + * @return int + */ + public function getFirstLineIndentation() + { + $indentation = null; + + foreach ($this->lines as $line) { + if (!$line->isEmpty()) { + $indentation = $line->getIndentation(); + break; + } + } + + return $indentation; + } +} diff --git a/src/main/QafooLabs/Refactoring/Domain/Model/IndentingLineCollection.php b/src/main/QafooLabs/Refactoring/Domain/Model/IndentingLineCollection.php new file mode 100644 index 0000000..0c7c286 --- /dev/null +++ b/src/main/QafooLabs/Refactoring/Domain/Model/IndentingLineCollection.php @@ -0,0 +1,36 @@ +indentation++; + } + + public function removeIndentation() + { + $this->indentation--; + } + + public function append(Line $line) + { + parent::append(new Line($this->createIndentationString() . (string) $line)); + } + + /** + * @return string + */ + private function createIndentationString() + { + return str_repeat(' ', $this->indentation * self::INDENTATION_SIZE); + } +} diff --git a/src/main/QafooLabs/Refactoring/Domain/Model/Line.php b/src/main/QafooLabs/Refactoring/Domain/Model/Line.php new file mode 100644 index 0000000..46d476b --- /dev/null +++ b/src/main/QafooLabs/Refactoring/Domain/Model/Line.php @@ -0,0 +1,43 @@ +line = (string) $line; + } + + /** + * @return string + */ + public function __toString() + { + return $this->line; + } + + /** + * @return bool + */ + public function isEmpty() + { + return trim($this->line) === ''; + } + + /** + * @return int + */ + public function getIndentation() + { + return strlen($this->line) - strlen(ltrim($this->line)); + } +} diff --git a/src/main/QafooLabs/Refactoring/Domain/Model/LineCollection.php b/src/main/QafooLabs/Refactoring/Domain/Model/LineCollection.php new file mode 100644 index 0000000..5022d5a --- /dev/null +++ b/src/main/QafooLabs/Refactoring/Domain/Model/LineCollection.php @@ -0,0 +1,82 @@ +lines = $lines; + } + + public function getIterator() + { + return new ArrayIterator($this->lines); + } + + /** + * @return Line[] + */ + public function getLines() + { + return $this->lines; + } + + public function append(Line $line) + { + $this->lines[] = $line; + } + + /** + * @param string $line + */ + public function appendString($line) + { + $this->append(new Line($line)); + } + + public function appendLines(LineCollection $lines) + { + foreach ($lines as $line) { + $this->append($line); + } + } + + public function appendBlankLine() + { + $this->lines[] = new Line(''); + } + + /** + * @param string[] $lines + * + * @return LineCollection + */ + public static function createFromArray(array $lines) + { + return new self(array_map(function ($line) { + return new Line($line); + }, $lines)); + } + + /** + * @param string $code + * + * @return LineCollection + */ + public static function createFromString($code) + { + return self::createFromArray(explode("\n", $code)); + } +} diff --git a/src/main/QafooLabs/Refactoring/Utils/ToStringIterator.php b/src/main/QafooLabs/Refactoring/Utils/ToStringIterator.php new file mode 100644 index 0000000..7d8cf8a --- /dev/null +++ b/src/main/QafooLabs/Refactoring/Utils/ToStringIterator.php @@ -0,0 +1,52 @@ +iterator = $it; + } + + /** + * @return string + */ + public function current() + { + return (string) $this->iterator->current(); + } + + /** + * @return scalar + */ + public function key() + { + return $this->iterator->key(); + } + + public function next() + { + $this->iterator->next(); + } + + public function rewind() + { + $this->iterator->rewind(); + } + + /** + * @return bool + */ + public function valid() + { + return $this->iterator->valid(); + } +} diff --git a/src/test/QafooLabs/Refactoring/Application/RenameLocalVariableTest.php.orig b/src/test/QafooLabs/Refactoring/Application/RenameLocalVariableTest.php.orig deleted file mode 100644 index 0a67bd6..0000000 --- a/src/test/QafooLabs/Refactoring/Application/RenameLocalVariableTest.php.orig +++ /dev/null @@ -1,51 +0,0 @@ -scanForVariables(\Phake::anyParameters())->thenReturn( - new DefinedVariables(array('helloWorld' => array(6))) - ); - \Phake::when($editor)->openBuffer(\Phake::anyParameters())->thenReturn($buffer); - - $refactoring = new RenameLocalVariable($scanner, $codeAnalysis, $editor); - - $patch = $refactoring->refactor(new File("foo.php", <<<'PHP' -replaceString(6, '$helloWorld', '$var'); - } - - public function testRenameNonLocalVariable_ThrowsException() - { - } - - public function testRenameIntoNonLocalVariable_ThrowsException() - { - } -} diff --git a/src/test/QafooLabs/Refactoring/Domain/Model/EditingAction/AddMethodTest.php b/src/test/QafooLabs/Refactoring/Domain/Model/EditingAction/AddMethodTest.php new file mode 100644 index 0000000..06cdca2 --- /dev/null +++ b/src/test/QafooLabs/Refactoring/Domain/Model/EditingAction/AddMethodTest.php @@ -0,0 +1,229 @@ +buffer = $this->getMock('QafooLabs\Refactoring\Domain\Model\EditorBuffer'); + } + + public function testItIsAnEditingAction() + { + $this->assertInstanceOf( + 'QafooLabs\Refactoring\Domain\Model\EditingAction', + new AddMethod(0, new MethodSignature('test'), new LineCollection()) + ); + } + + public function testBufferAppendIsPerformedAtTheGivenLineNumber() + { + $lineNumber = 27; + + $this->buffer + ->expects($this->once()) + ->method('append') + ->with($this->equalTo($lineNumber), $this->anything()); + + $action = new AddMethod($lineNumber, new MethodSignature('test'), new LineCollection()); + + $action->performEdit($this->buffer); + } + + public function testAppendsMethod() + { + $action = new AddMethod(0, new MethodSignature('testMethod'), new LineCollection()); + + $this->assertGeneratedCodeMatches(array( + '', + ' private function testMethod()', + ' {', + ' }' + ), $action); + } + + public function testReturnStatementForSingleVariable() + { + $action = new AddMethod( + 0, + $this->createMethodSignatureWithReturnVars(array('returnVar')), + new LineCollection() + ); + + $this->assertGeneratedCodeMatches(array( + '', + ' private function testMethod()', + ' {', + '', + ' return $returnVar;', + ' }' + ), $action); + } + + public function testReturnStatementForSingleVariableHasCorrectName() + { + $action = new AddMethod( + 0, + $this->createMethodSignatureWithReturnVars(array('specialVar')), + new LineCollection() + ); + + $this->assertGeneratedCodeMatches(array( + '', + ' private function testMethod()', + ' {', + '', + ' return $specialVar;', + ' }' + ), $action); + } + + public function testReturnStatementForMultipleVariables() + { + $action = new AddMethod( + 0, + $this->createMethodSignatureWithReturnVars(array('ret1', 'ret2')), + new LineCollection() + ); + + $this->assertGeneratedCodeMatches(array( + '', + ' private function testMethod()', + ' {', + '', + ' return array($ret1, $ret2);', + ' }' + ), $action); + } + + public function testMethodNameIsUsed() + { + $action = new AddMethod( + 0, + new MethodSignature('realMethodName'), + new LineCollection() + ); + + $this->assertGeneratedCodeMatches(array( + '', + ' private function realMethodName()', + ' {', + ' }' + ), $action); + } + + public function testStaticMethodsAreDefinedCorrectly() + { + $action = new AddMethod( + 0, + new MethodSignature( + 'realMethodName', + MethodSignature::IS_PRIVATE | MethodSignature::IS_STATIC + ), + new LineCollection() + ); + + $this->assertGeneratedCodeMatches(array( + '', + ' private static function realMethodName()', + ' {', + ' }' + ), $action); + } + + public function testMethodArgumentsAreDefinedCorrectly() + { + $action = new AddMethod( + 0, + new MethodSignature( + 'testMethod', + MethodSignature::IS_PRIVATE, + array('param1', 'param2') + ), + new LineCollection() + ); + + $this->assertGeneratedCodeMatches(array( + '', + ' private function testMethod($param1, $param2)', + ' {', + ' }' + ), $action); + } + + public function testSelectedCodeIsAdded() + { + $action = new AddMethod( + 0, + $this->createMethodSignatureWithReturnVars(array()), + LineCollection::createFromArray(array( + 'echo "Hello World!";' + )) + ); + + $this->assertGeneratedCodeMatches(array( + '', + ' private function testMethod()', + ' {', + ' echo "Hello World!";', + ' }' + ), $action); + } + + public function testSelectedCodeIsAddedWithCorrectIndetations() + { + $action = new AddMethod( + 0, + $this->createMethodSignatureWithReturnVars(array()), + LineCollection::createFromArray(array( + ' if ($something) {', + ' echo "Hello World!";', + ' }' + )) + ); + + $this->assertGeneratedCodeMatches(array( + '', + ' private function testMethod()', + ' {', + ' if ($something) {', + ' echo "Hello World!";', + ' }', + ' }' + ), $action); + } + + private function assertGeneratedCodeMatches(array $expected, AddMethod $action) + { + $this->makeBufferAppendExpectCode($expected); + + $action->performEdit($this->buffer); + } + + private function createMethodSignatureWithReturnVars(array $returnVars) + { + return new MethodSignature( + 'testMethod', + MethodSignature::IS_PRIVATE, + array(), + $returnVars + ); + } + + private function makeBufferAppendExpectCode(array $codeLines) + { + $this->buffer + ->expects($this->once()) + ->method('append') + ->with($this->anything(), $this->equalTo($codeLines)); + } +} diff --git a/src/test/QafooLabs/Refactoring/Domain/Model/EditingAction/AddPropertyTest.php b/src/test/QafooLabs/Refactoring/Domain/Model/EditingAction/AddPropertyTest.php new file mode 100644 index 0000000..77cc92e --- /dev/null +++ b/src/test/QafooLabs/Refactoring/Domain/Model/EditingAction/AddPropertyTest.php @@ -0,0 +1,50 @@ +buffer = $this->getMock('QafooLabs\Refactoring\Domain\Model\EditorBuffer'); + } + + public function testItIsAnEditingAction() + { + $this->assertInstanceOf( + 'QafooLabs\Refactoring\Domain\Model\EditingAction', + new AddProperty(5, 'testProperty') + ); + } + + public function testPropertyIsAppenedAtGivenLine() + { + $line = 27; + + $action = new AddProperty($line, ''); + + $this->buffer + ->expects($this->once()) + ->method('append') + ->with($line, $this->anything()); + + $action->performEdit($this->buffer); + } + + public function testPropertyCodeIsCorrect() + { + $action = new AddProperty(5, 'testProperty'); + + $this->buffer + ->expects($this->once()) + ->method('append') + ->with($this->anything(), $this->equalTo(array( + ' private $testProperty;', + '' + ))); + + $action->performEdit($this->buffer); + } +} diff --git a/src/test/QafooLabs/Refactoring/Domain/Model/EditingAction/LocalVariableToInstanceTest.php b/src/test/QafooLabs/Refactoring/Domain/Model/EditingAction/LocalVariableToInstanceTest.php new file mode 100644 index 0000000..2afa4f7 --- /dev/null +++ b/src/test/QafooLabs/Refactoring/Domain/Model/EditingAction/LocalVariableToInstanceTest.php @@ -0,0 +1,114 @@ +buffer = $this->getMock('QafooLabs\Refactoring\Domain\Model\EditorBuffer'); + } + + public function testItIsAnEditingAction() + { + $this->assertInstanceOf( + 'QafooLabs\Refactoring\Domain\Model\EditingAction', + new LocalVariableToInstance( + new DefinedVariables(array(), array()), + new Variable('testVar') + ) + ); + } + + public function testItReplacesVariableWithInstanceVariableVersion() + { + $variable = new Variable('varName'); + + $action = new LocalVariableToInstance( + new DefinedVariables(array('varName' => array(1)), array()), + $variable + ); + + $this->buffer + ->expects($this->once()) + ->method('replaceString') + ->with($this->anything(), $this->equalTo('$varName'), $this->equalTo('$this->varName')); + + $action->performEdit($this->buffer); + } + + public function testItReplacesOnLineForReadOnlyVariable() + { + $definedVars = new DefinedVariables(array('theVar' => array(12)), array()); + $variable = new Variable('theVar'); + + $action = new LocalVariableToInstance($definedVars, $variable); + + $this->buffer + ->expects($this->once()) + ->method('replaceString') + ->with($this->equalTo(12), $this->anything(), $this->anything()); + + $action->performEdit($this->buffer); + } + + public function testItReplacesOn2LinesForReadOnlyVariable() + { + $definedVars = new DefinedVariables(array('theVar' => array(12, 15)), array()); + $variable = new Variable('theVar'); + + $action = new LocalVariableToInstance($definedVars, $variable); + + $this->buffer + ->expects($this->at(0)) + ->method('replaceString') + ->with($this->equalTo(12), $this->anything(), $this->anything()); + + $this->buffer + ->expects($this->at(1)) + ->method('replaceString') + ->with($this->equalTo(15), $this->anything(), $this->anything()); + + $action->performEdit($this->buffer); + } + + public function testItReplacesOnLineForChangedVariable() + { + $definedVars = new DefinedVariables(array(), array('theVar' => array(12))); + $variable = new Variable('theVar'); + + $action = new LocalVariableToInstance($definedVars, $variable); + + $this->buffer + ->expects($this->once()) + ->method('replaceString') + ->with($this->equalTo(12), $this->anything(), $this->anything()); + + $action->performEdit($this->buffer); + } + + public function testItReplacesOn2LinesForChangedVariable() + { + $definedVars = new DefinedVariables(array(), array('theVar' => array(12, 15))); + $variable = new Variable('theVar'); + + $action = new LocalVariableToInstance($definedVars, $variable); + + $this->buffer + ->expects($this->at(0)) + ->method('replaceString') + ->with($this->equalTo(12), $this->anything(), $this->anything()); + + $this->buffer + ->expects($this->at(1)) + ->method('replaceString') + ->with($this->equalTo(15), $this->anything(), $this->anything()); + + $action->performEdit($this->buffer); + } +} diff --git a/src/test/QafooLabs/Refactoring/Domain/Model/EditingAction/RenameVariableTest.php b/src/test/QafooLabs/Refactoring/Domain/Model/EditingAction/RenameVariableTest.php new file mode 100644 index 0000000..bc5bc13 --- /dev/null +++ b/src/test/QafooLabs/Refactoring/Domain/Model/EditingAction/RenameVariableTest.php @@ -0,0 +1,117 @@ +buffer = $this->getMock('QafooLabs\Refactoring\Domain\Model\EditorBuffer'); + } + + public function testItIsAnEditingAction() + { + $this->assertInstanceOf( + 'QafooLabs\Refactoring\Domain\Model\EditingAction', + new RenameVariable( + new DefinedVariables(array(), array()), + new Variable('testVar'), + new Variable('newVar') + ) + ); + } + + public function testItReplacesVariableWithInstanceVariableVersion() + { + $oldName = new Variable('varName'); + $newName = new Variable('newName'); + + $action = new RenameVariable( + new DefinedVariables(array('varName' => array(1)), array()), + $oldName, + $newName + ); + + $this->buffer + ->expects($this->once()) + ->method('replaceString') + ->with($this->anything(), $this->equalTo('$varName'), $this->equalTo('$newName')); + + $action->performEdit($this->buffer); + } + + public function testItReplacesOnLineForReadOnlyVariable() + { + $definedVars = new DefinedVariables(array('theVar' => array(12)), array()); + $variable = new Variable('theVar'); + + $action = new RenameVariable($definedVars, $variable, $variable); + + $this->buffer + ->expects($this->once()) + ->method('replaceString') + ->with($this->equalTo(12), $this->anything(), $this->anything()); + + $action->performEdit($this->buffer); + } + + public function testItReplacesOn2LinesForReadOnlyVariable() + { + $definedVars = new DefinedVariables(array('theVar' => array(12, 15)), array()); + $variable = new Variable('theVar'); + + $action = new RenameVariable($definedVars, $variable, $variable); + + $this->buffer + ->expects($this->at(0)) + ->method('replaceString') + ->with($this->equalTo(12), $this->anything(), $this->anything()); + + $this->buffer + ->expects($this->at(1)) + ->method('replaceString') + ->with($this->equalTo(15), $this->anything(), $this->anything()); + + $action->performEdit($this->buffer); + } + + public function testItReplacesOnLineForChangedVariable() + { + $definedVars = new DefinedVariables(array(), array('theVar' => array(12))); + $variable = new Variable('theVar'); + + $action = new RenameVariable($definedVars, $variable, $variable); + + $this->buffer + ->expects($this->once()) + ->method('replaceString') + ->with($this->equalTo(12), $this->anything(), $this->anything()); + + $action->performEdit($this->buffer); + } + + public function testItReplacesOn2LinesForChangedVariable() + { + $definedVars = new DefinedVariables(array(), array('theVar' => array(12, 15))); + $variable = new Variable('theVar'); + + $action = new RenameVariable($definedVars, $variable, $variable); + + $this->buffer + ->expects($this->at(0)) + ->method('replaceString') + ->with($this->equalTo(12), $this->anything(), $this->anything()); + + $this->buffer + ->expects($this->at(1)) + ->method('replaceString') + ->with($this->equalTo(15), $this->anything(), $this->anything()); + + $action->performEdit($this->buffer); + } +} diff --git a/src/test/QafooLabs/Refactoring/Domain/Model/EditingAction/ReplaceWithMethodCallTest.php b/src/test/QafooLabs/Refactoring/Domain/Model/EditingAction/ReplaceWithMethodCallTest.php new file mode 100644 index 0000000..99498aa --- /dev/null +++ b/src/test/QafooLabs/Refactoring/Domain/Model/EditingAction/ReplaceWithMethodCallTest.php @@ -0,0 +1,122 @@ +buffer = $this->getMock('QafooLabs\Refactoring\Domain\Model\EditorBuffer'); + } + + public function testItIsAnEditingAction() + { + $this->assertInstanceOf( + 'QafooLabs\Refactoring\Domain\Model\EditingAction', + new ReplaceWithMethodCall( + LineRange::fromLines(1, 2), + new MethodSignature('testMethod') + ) + ); + } + + public function testBufferReplacesAtGivenRange() + { + $range = LineRange::fromLines(1, 2); + + $action = new ReplaceWithMethodCall( + $range, + new MethodSignature('testMethod') + ); + + $this->buffer + ->expects($this->once()) + ->method('replace') + ->with($this->equalTo($range), $this->anything()); + + $action->performEdit($this->buffer); + } + + public function testMethodCallIsCorrectForSimpleMethod() + { + $action = new ReplaceWithMethodCall( + LineRange::fromLines(1, 2), + new MethodSignature('testMethod') + ); + + $this->assertGeneratedMethodCallMatches('$this->testMethod();', $action); + } + + public function testMethodCallUsesGivenMethodName() + { + $action = new ReplaceWithMethodCall( + LineRange::fromLines(1, 2), + new MethodSignature('realMethod') + ); + + $this->assertGeneratedMethodCallMatches('$this->realMethod();', $action); + } + + public function testStaticMethodCall() + { + $action = new ReplaceWithMethodCall( + LineRange::fromLines(1, 2), + new MethodSignature('testMethod', MethodSignature::IS_STATIC) + ); + + $this->assertGeneratedMethodCallMatches('self::testMethod();', $action); + } + + public function testMethodCallWithSingleReturnVariable() + { + $action = new ReplaceWithMethodCall( + LineRange::fromLines(1, 2), + new MethodSignature('testMethod', 0, array(), array('result')) + ); + + $this->assertGeneratedMethodCallMatches('$result = $this->testMethod();', $action); + } + + public function testMethodCallWithMultipleReturnVariables() + { + $action = new ReplaceWithMethodCall( + LineRange::fromLines(1, 2), + new MethodSignature('testMethod', 0, array(), array('result1', 'result2')) + ); + + $this->assertGeneratedMethodCallMatches( + 'list($result1, $result2) = $this->testMethod();', + $action + ); + } + + public function testMethodCallWithArguments() + { + $action = new ReplaceWithMethodCall( + LineRange::fromLines(1, 2), + new MethodSignature('testMethod', 0, array('arg1', 'arg2')) + ); + + $this->assertGeneratedMethodCallMatches( + '$this->testMethod($arg1, $arg2);', + $action + ); + } + + private function assertGeneratedMethodCallMatches($expected, $action) + { + $expected = ' ' . $expected; + + $this->buffer + ->expects($this->once()) + ->method('replace') + ->with($this->anything(), $this->equalTo(array($expected))); + + $action->performEdit($this->buffer); + } +} diff --git a/src/test/QafooLabs/Refactoring/Domain/Model/EditingSessionTest.php b/src/test/QafooLabs/Refactoring/Domain/Model/EditingSessionTest.php new file mode 100644 index 0000000..9cb12ba --- /dev/null +++ b/src/test/QafooLabs/Refactoring/Domain/Model/EditingSessionTest.php @@ -0,0 +1,36 @@ +buffer = $this->getMock('QafooLabs\Refactoring\Domain\Model\EditorBuffer'); + + $this->session = new EditingSession($this->buffer); + } + + public function testEditActionsArePerformed() + { + $action1 = $this->getMock('QafooLabs\Refactoring\Domain\Model\EditingAction'); + $action2 = $this->getMock('QafooLabs\Refactoring\Domain\Model\EditingAction'); + + $action1->expects($this->once()) + ->method('performEdit') + ->with($this->equalTo($this->buffer)); + + $action2->expects($this->once()) + ->method('performEdit') + ->with($this->equalTo($this->buffer)); + + $this->session->addEdit($action1); + $this->session->addEdit($action2); + + $this->session->performEdits(); + } +} diff --git a/src/test/QafooLabs/Refactoring/Domain/Model/IndentationDetectorTest.php b/src/test/QafooLabs/Refactoring/Domain/Model/IndentationDetectorTest.php new file mode 100644 index 0000000..70c4443 --- /dev/null +++ b/src/test/QafooLabs/Refactoring/Domain/Model/IndentationDetectorTest.php @@ -0,0 +1,68 @@ +createDetector(array(' echo "test";')); + + $this->assertEquals(4, $detector->getMinIndentation()); + } + + public function testGetMinIndentationForFirstLine() + { + $detector = $this->createDetector(array( + ' echo "Line 1";', + ' echo "Line 2";', + )); + + $this->assertEquals(2, $detector->getMinIndentation()); + } + + public function testGetMinIntentationForLaterLine() + { + $detector = $this->createDetector(array( + ' echo "Line 1";', + ' echo "Line 2";', + )); + + $this->assertEquals(2, $detector->getMinIndentation()); + } + + public function testGetMinIndentationWithBlankLines() + { + $detector = $this->createDetector(array( + '', + ' echo "test";', + )); + + $this->assertEquals(4, $detector->getMinIndentation()); + } + + public function testGetFirstLineIndentation() + { + $detector = $this->createDetector(array( + ' echo "line 1";', + ' echo "line 2";', + )); + + $this->assertEquals(4, $detector->getFirstLineIndentation()); + } + + public function testGetFirstLineIndentationWithBlankLines() + { + $detector = $this->createDetector(array( + '', + ' echo "test";', + )); + + $this->assertEquals(2, $detector->getFirstLineIndentation()); + } + + private function createDetector(array $lines) + { + return new IndentationDetector(LineCollection::createFromArray($lines)); + } +} diff --git a/src/test/QafooLabs/Refactoring/Domain/Model/IndentingLineCollectionTest.php b/src/test/QafooLabs/Refactoring/Domain/Model/IndentingLineCollectionTest.php new file mode 100644 index 0000000..b8b8723 --- /dev/null +++ b/src/test/QafooLabs/Refactoring/Domain/Model/IndentingLineCollectionTest.php @@ -0,0 +1,109 @@ +lines = new IndentingLineCollection(); + } + + public function testIsALineCollection() + { + $this->assertInstanceOf( + 'QafooLabs\Refactoring\Domain\Model\LineCollection', + $this->lines + ); + } + + public function testAppendAddsIndentation() + { + $this->lines->addIndentation(); + + $this->lines->append(new Line('echo "test";')); + + $this->assertLinesMatch(array( + ' echo "test";' + )); + } + + public function testAppendAddsMulitpleIndentation() + { + $this->lines->append(new Line('echo "line1";')); + $this->lines->addIndentation(); + $this->lines->append(new Line('echo "line2";')); + $this->lines->addIndentation(); + $this->lines->append(new Line('echo "line3";')); + + $this->assertLinesMatch(array( + 'echo "line1";', + ' echo "line2";', + ' echo "line3";' + )); + } + + public function testAppendRemovesIndentation() + { + $this->lines->append(new Line('echo "line1";')); + $this->lines->addIndentation(); + $this->lines->append(new Line('echo "line2";')); + $this->lines->removeIndentation(); + $this->lines->append(new Line('echo "line3";')); + + $this->assertLinesMatch(array( + 'echo "line1";', + ' echo "line2";', + 'echo "line3";' + )); + } + + public function testAppendStringObeysIndentation() + { + $this->lines->appendString('echo "line1";'); + $this->lines->addIndentation(); + $this->lines->appendString('echo "line2";'); + $this->lines->removeIndentation(); + $this->lines->appendString('echo "line3";'); + + $this->assertLinesMatch(array( + 'echo "line1";', + ' echo "line2";', + 'echo "line3";' + )); + } + + public function testAppendLinesObeysIndentation() + { + $this->lines->addIndentation(); + + $this->lines->appendLines(LineCollection::createFromArray(array( + 'echo "line1";', + 'echo "line2";' + ))); + + $this->assertLinesMatch(array( + ' echo "line1";', + ' echo "line2";', + )); + } + + public function testAddBlankLineContainsNoIndentation() + { + $this->lines->appendBlankLine(); + + $this->assertLinesMatch(array('')); + } + + private function assertLinesMatch(array $expected) + { + $this->assertEquals( + $expected, + iterator_to_array(new ToStringIterator($this->lines->getIterator())) + ); + } +} diff --git a/src/test/QafooLabs/Refactoring/Domain/Model/LineCollectionTest.php b/src/test/QafooLabs/Refactoring/Domain/Model/LineCollectionTest.php new file mode 100644 index 0000000..f96f59f --- /dev/null +++ b/src/test/QafooLabs/Refactoring/Domain/Model/LineCollectionTest.php @@ -0,0 +1,114 @@ +assertSame($lineObjects, $lines->getLines()); + } + + public function testAppendAddsALine() + { + $line1 = new Line('line 1'); + $line2 = new Line('line 2'); + + $lines = new LineCollection(array($line1)); + + $lines->append($line2); + + $this->assertSame(array($line1, $line2), $lines->getLines()); + } + + public function testAppendStringAddsALine() + { + $line1 = 'line 1'; + $line2 = 'line 2'; + + $lines = new LineCollection(array(new Line($line1))); + + $lines->appendString($line2); + + $this->assertEquals( + array(new Line($line1), new Line($line2)), + $lines->getLines() + ); + } + + public function testCreateFromArray() + { + $lines = LineCollection::createFromArray(array( + 'line1', + 'line2', + )); + + $this->assertEquals( + array(new Line('line1'), new Line('line2')), + $lines->getLines() + ); + } + + public function testCreateFromString() + { + $lines = LineCollection::createFromString( + "line1\nline2" + ); + + $this->assertEquals( + array(new Line('line1'), new Line('line2')), + $lines->getLines() + ); + } + + public function testIsIterable() + { + $lineObjects = array( + new Line('line 1'), + new Line('line 2') + ); + + $lines = new LineCollection($lineObjects); + + $this->assertEquals($lineObjects, iterator_to_array($lines)); + } + + public function testAppendLinesAddsGivenLines() + { + $lines = LineCollection::createFromArray(array( + 'line1', + 'line2', + )); + + $lines->appendLines(LineCollection::createFromArray(array( + 'line3', + 'line4', + ))); + + $this->assertEquals( + array('line1', 'line2', 'line3', 'line4'), + iterator_to_array(new ToStringIterator($lines->getIterator())) + ); + } + + public function testAppendlankLine() + { + $lines = new LineCollection(); + + $lines->appendBlankLine(); + + $this->assertEquals( + array(''), + iterator_to_array(new ToStringIterator($lines->getIterator())) + ); + } +} diff --git a/src/test/QafooLabs/Refactoring/Domain/Model/LineTest.php b/src/test/QafooLabs/Refactoring/Domain/Model/LineTest.php new file mode 100644 index 0000000..36d991b --- /dev/null +++ b/src/test/QafooLabs/Refactoring/Domain/Model/LineTest.php @@ -0,0 +1,43 @@ +assertEquals($content, (string) $line); + } + + public function testIsEmptyForEmptyLine() + { + $line = new Line(''); + + $this->assertTrue($line->isEmpty()); + } + + public function testIsEmptyForLineWithContent() + { + $line = new Line('$a = 5;'); + + $this->assertFalse($line->isEmpty()); + } + + public function testGetIndentationFor2Spaces() + { + $line = new Line(' echo "Test";'); + + $this->assertEquals(2, $line->getIndentation()); + } + + public function testGetIndentationFor4Spaces() + { + $line = new Line(' echo "Test";'); + + $this->assertEquals(4, $line->getIndentation()); + } +} diff --git a/src/test/QafooLabs/Refactoring/Utils/ToStringIteratorTest.php b/src/test/QafooLabs/Refactoring/Utils/ToStringIteratorTest.php new file mode 100644 index 0000000..d9f543f --- /dev/null +++ b/src/test/QafooLabs/Refactoring/Utils/ToStringIteratorTest.php @@ -0,0 +1,49 @@ +assertEquals( + array('value1', 'value2'), + iterator_to_array($it) + ); + } +} + +class StringableClass +{ + private $value; + + public function __construct($value) + { + $this->value = $value; + } + + public function __toString() + { + return (string) $this->value; + } +}