Skip to content
This repository was archived by the owner on Jul 12, 2020. It is now read-only.

Commit 78ee890

Browse files
committed
start implementing local variable renaming, refactoring stuff in between.
1 parent 6f94f5b commit 78ee890

File tree

14 files changed

+338
-32
lines changed

14 files changed

+338
-32
lines changed

README.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,22 @@ The refactoring browser is used with:
1919

2020
List of Refactorings to implement:
2121

22-
* Extract Method
23-
* Rename Method
22+
* Extract Method (Prototype Done)
2423
* Rename Local Variable
24+
* Rename Method
25+
* Private Methods Only first
2526
* Rename Instance Variable
27+
* Private Variables Only First
2628
* Rename Class (PSR-0 aware)
2729
* Rename Namespace (PSR-0 aware)
2830
* Extract Interface
2931

30-
Design Goal:
32+
## Design Goals
3133

32-
* Be independent of any Type Inference Engine (PDepend, PHP Analyzer) via Ports+Adapters
34+
* Be independent of third-party libraries and any Type Inference Engine (PDepend, PHP Analyzer) via Ports+Adapters
35+
* Apply Domain-Driven-Design and find suitable Bounded Contexts
3336

34-
Processing steps:
37+
## Processing steps
3538

3639
* Update Type Database (based on filemtime or md5 hashes?)
3740
* Analyze Refactoring (pre conditions)
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
Feature: Rename Local Variable
2+
To keep my code base clean
3+
As a developer
4+
I want to rename local variables
5+
6+
Scenario: Rename Variable In Methods
7+
Given a PHP File named "src/Foo.php" with:
8+
"""
9+
<?php
10+
class Foo
11+
{
12+
public function operation()
13+
{
14+
$var = 2;
15+
16+
for ($i = 0; $i < 3; $i++) {
17+
$var = pow($var, 2);
18+
}
19+
20+
return $var * $var;
21+
}
22+
}
23+
"""
24+
When I use refactoring "rename-local-variable" with:
25+
| arg | value |
26+
| file | src/Foo.php |
27+
| line | 6 |
28+
| name | var |
29+
| new-name | number |
30+
Then the PHP File "src/Foo.php" should be refactored:
31+
"""
32+
@@ -4,11 +4,11 @@
33+
public function operation()
34+
{
35+
- $var = 2;
36+
+ $number = 2;
37+
38+
for ($i = 0; $i < 3; $i++) {
39+
- $var = pow($var, 2);
40+
+ $number = pow($number, 2);
41+
}
42+
43+
- return $var * $var;
44+
+ return $number * $number;
45+
}
46+
}
47+
"""

src/main/QafooLabs/Refactoring/Adapters/PHPParser/Visitor/LocalVariableClassifier.php

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,34 @@
33
namespace QafooLabs\Refactoring\Adapters\PHPParser\Visitor;
44

55
use PHPParser_Node;
6+
use PHPParser_NodeVisitorAbstract;
7+
use PHPParser_Node_Expr_Variable;
8+
use PHPParser_Node_Expr_Assign;
69

710
/**
811
* Classify local variables into assignments and usages,
912
* permanent and temporary variables.
1013
*/
11-
class LocalVariableClassifier extends \PHPParser_NodeVisitorAbstract
14+
class LocalVariableClassifier extends PHPParser_NodeVisitorAbstract
1215
{
1316
private $localVariables = array();
1417
private $assignments = array();
1518

1619
public function enterNode(PHPParser_Node $node)
1720
{
18-
if ($node instanceof \PHPParser_Node_Expr_Variable) {
21+
if ($node instanceof PHPParser_Node_Expr_Variable) {
1922
$this->enterVariableNode($node);
2023
}
2124

22-
if ($node instanceof \PHPParser_Node_Expr_Assign) {
25+
if ($node instanceof PHPParser_Node_Expr_Assign) {
2326
$this->enterAssignment($node);
2427
}
2528
}
2629

2730
private function enterAssignment($node)
2831
{
29-
if ($node->var instanceof \PHPParser_Node_Expr_Variable) {
30-
$this->assignments[] = $node->var->name;
32+
if ($node->var instanceof PHPParser_Node_Expr_Variable) {
33+
$this->assignments[$node->var->name][] = $node->getLine();
3134
}
3235
}
3336

@@ -37,7 +40,7 @@ private function enterVariableNode($node)
3740
return;
3841
}
3942

40-
$this->localVariables[] = $node->name;
43+
$this->localVariables[$node->name][] = $node->getLine();
4144
}
4245

4346
public function getLocalVariables()
@@ -47,8 +50,15 @@ public function getLocalVariables()
4750

4851
public function getUsedLocalVariables()
4952
{
50-
// TODO: wrong if usage before assignment
51-
return array_diff($this->localVariables, $this->assignments);
53+
$usedLocalVariables = $this->localVariables;
54+
55+
foreach ($this->assignments as $assignmentName => $_) {
56+
if (min($usedLocalVariables[$assignmentName]) >= min($this->assignments[$assignmentName])) {
57+
unset($usedLocalVariables[$assignmentName]);
58+
}
59+
}
60+
61+
return $usedLocalVariables;
5262
}
5363

5464
public function getAssignments()

src/main/QafooLabs/Refactoring/Adapters/Symfony/CliApplication.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ protected function getDefaultCommands()
1010
{
1111
$commands = parent::getDefaultCommands();
1212
$commands[] = new Commands\ExtractMethodCommand();
13+
$commands[] = new Commands\RenameLocalVariableCommand();
1314

1415
return $commands;
1516
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
namespace QafooLabs\Refactoring\Adapters\Symfony\Commands;
4+
5+
use Symfony\Component\Console\Input\InputArgument;
6+
use Symfony\Component\Console\Input\InputOption;
7+
use Symfony\Component\Console\Input\InputInterface;
8+
use Symfony\Component\Console\Output\OutputInterface;
9+
use Symfony\Component\Console\Command\Command;
10+
11+
use QafooLabs\Refactoring\Domain\Model\File;
12+
13+
class RenameLocalVariableCommand extends Command
14+
{
15+
protected function configure()
16+
{
17+
$this
18+
->setName('rename-local-variable')
19+
->setDescription('Rename a local variable inside a method')
20+
->addArgument('file', InputArgument::REQUIRED, 'File that contains list of statements to extract')
21+
->addArgument('line', InputArgument::REQUIRED, 'Line where the local variable is defined.')
22+
->addArgument('name', InputArgument::REQUIRED, 'Current name of the variable, with or without the $')
23+
->addArgument('new-name', InputArgument::REQUIRED, 'New name of the variable')
24+
;
25+
}
26+
27+
protected function execute(InputInterface $input, OutputInterface $output)
28+
{
29+
$file = File::createFromPath($input->getArgument('file'), getcwd());
30+
$line = $input->getArgument('line');
31+
$name = $input->getArgument('name');
32+
$newName = $input->getArgument('new-name');
33+
}
34+
}

src/main/QafooLabs/Refactoring/Adapters/TokenReflection/StaticCodeAnalysis.php

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ public function getMethodEndLine(File $file, LineRange $range)
4141
{
4242
$this->broker = new Broker(new Memory);
4343
$file = $this->broker->processString($file->getCode(), $file->getRelativePath(), true);
44-
$endLineClass = 0;
4544
$lastLine = $range->getEnd();
4645

4746
foreach ($file->getNamespaces() as $namespace) {
@@ -51,11 +50,28 @@ public function getMethodEndLine(File $file, LineRange $range)
5150
return $method->getEndLine();
5251
}
5352
}
53+
}
54+
}
55+
56+
throw new \InvalidArgumentException("Could not find method end line.");
57+
}
5458

55-
$endLineClass = $class->getEndLine() - 1;
59+
public function getMethodStartLine(File $file, LineRange $range)
60+
{
61+
$this->broker = new Broker(new Memory);
62+
$file = $this->broker->processString($file->getCode(), $file->getRelativePath(), true);
63+
$lastLine = $range->getEnd();
64+
65+
foreach ($file->getNamespaces() as $namespace) {
66+
foreach ($namespace->geTclasses() as $class) {
67+
foreach ($class->getMethods() as $method) {
68+
if ($method->getStartLine() < $lastLine && $lastLine < $method->getEndLine()) {
69+
return $method->getStartLine();
70+
}
71+
}
5672
}
5773
}
5874

59-
return $endLineClass;
75+
throw new \InvalidArgumentException("Could not find method start line.");
6076
}
6177
}

src/main/QafooLabs/Refactoring/Application/ExtractMethod.php

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
use QafooLabs\Refactoring\Domain\Services\CodeAnalysis;
99
use QafooLabs\Refactoring\Domain\Services\Editor;
1010

11+
/**
12+
* Extract Method Refactoring
13+
*/
1114
class ExtractMethod
1215
{
1316
/**
@@ -53,15 +56,15 @@ public function refactor(File $file, LineRange $range, $newMethodName)
5356
private function generateMethodCall($newMethodName, $definedVariables, $isStatic)
5457
{
5558
$ws = str_repeat(' ', 8);
56-
$argumentLine = $this->implodeVariables($definedVariables->localVariables);
59+
$argumentLine = $this->implodeVariables($definedVariables->getLocalVariables());
5760

5861
$code = $isStatic ? 'self::%s(%s);' : '$this->%s(%s);';
5962
$call = sprintf($code, $newMethodName, $argumentLine);
6063

61-
if (count($definedVariables->assignments) == 1) {
62-
$call = '$' . $definedVariables->assignments[0] . ' = ' . $call;
63-
} else if (count($definedVariables->assignments) > 1) {
64-
$call = 'list(' . $this->implodeVariables($definedVariables->assignments) . ') = ' . $call;
64+
if (count($definedVariables->getAssignments()) == 1) {
65+
$call = '$' . $definedVariables->getAssignments()[0] . ' = ' . $call;
66+
} else if (count($definedVariables->getAssignments()) > 1) {
67+
$call = 'list(' . $this->implodeVariables($definedVariables->getAssignments()) . ') = ' . $call;
6568
}
6669

6770
return $ws . $call;
@@ -72,15 +75,15 @@ private function generateExtractedMethodCode($newMethodName, $selectedCode, $def
7275
$ws = str_repeat(' ', 8);
7376
$wsm = str_repeat(' ', 4);
7477

75-
if (count($definedVariables->assignments) == 1) {
78+
if (count($definedVariables->getAssignments()) == 1) {
7679
$selectedCode[] = '';
77-
$selectedCode[] = $ws . 'return $' . $definedVariables->assignments[0] . ';';
78-
} else if (count($definedVariables->assignments) > 1) {
80+
$selectedCode[] = $ws . 'return $' . $definedVariables->getAssignments()[0] . ';';
81+
} else if (count($definedVariables->getAssignments()) > 1) {
7982
$selectedCode[] = '';
80-
$selectedCode[] = $ws . 'return array(' . $this->implodeVariables($definedVariables->assignments) . ');';
83+
$selectedCode[] = $ws . 'return array(' . $this->implodeVariables($definedVariables->getAssignments()) . ');';
8184
}
8285

83-
$paramLine = $this->implodeVariables($definedVariables->localVariables);
86+
$paramLine = $this->implodeVariables($definedVariables->getLocalVariables());
8487

8588
$methodCode = array_merge(
8689
array('', $wsm . sprintf('private%sfunction %s(%s)', $isStatic ? ' static ' : ' ', $newMethodName, $paramLine), $wsm . '{'),
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
namespace QafooLabs\Refactoring\Application;
4+
5+
use QafooLabs\Refactoring\Domain\Model\File;
6+
use QafooLabs\Refactoring\Domain\Model\LineRange;
7+
8+
use QafooLabs\Refactoring\Domain\Services\VariableScanner;
9+
use QafooLabs\Refactoring\Domain\Services\CodeAnalysis;
10+
use QafooLabs\Refactoring\Domain\Services\Editor;
11+
12+
/**
13+
* Rename Local Variable Refactoring
14+
*/
15+
class RenameLocalVariable
16+
{
17+
/**
18+
* @var \QafooLabs\Refactoring\Domain\Services\VariableScanner
19+
*/
20+
private $variableScanner;
21+
22+
/**
23+
* @var \QafooLabs\Refactoring\Domain\Services\CodeAnalysis
24+
*/
25+
private $codeAnalysis;
26+
27+
/**
28+
* @var \QafooLabs\Refactoring\Domain\Services\Editor
29+
*/
30+
private $editor;
31+
32+
public function __construct(VariableScanner $variableScanner, CodeAnalysis $codeAnalysis, Editor $editor)
33+
{
34+
$this->variableScanner = $variableScanner;
35+
$this->codeAnalysis = $codeAnalysis;
36+
$this->editor = $editor;
37+
}
38+
39+
public function refactor(File $file, $line, $oldName, $newName)
40+
{
41+
$methodRange = $this->findMethodRange($file, $line);
42+
$declaredVariables = $this->variableScanner->scanForVariables($file, $methodRange);
43+
44+
$buffer = $this->editor->openBuffer($file);
45+
46+
$this->editor->save();
47+
}
48+
49+
private function findMethodRange(File $file, $line)
50+
{
51+
$range = LineRange::fromSingleLine($line);
52+
$methodStartLine = $this->codeAnalysis->getMethodStartLine($file, $range);
53+
$methodEndLine = $this->codeAnalysis->getMethodEndLine($file, $range);
54+
55+
return LineRange::fromLines($methodStartLine, $methodEndLine);
56+
}
57+
}
58+

src/main/QafooLabs/Refactoring/Domain/Model/DefinedVariables.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,17 @@ class DefinedVariables extends ValueObject
2828

2929
public function __construct(array $localVariables, array $assignments)
3030
{
31-
$this->localVariables = array_unique($localVariables);
32-
$this->assignments = array_unique($assignments);
31+
$this->localVariables = $localVariables;
32+
$this->assignments = $assignments;
33+
}
34+
35+
public function getLocalVariables()
36+
{
37+
return array_keys($this->localVariables);
38+
}
39+
40+
public function getAssignments()
41+
{
42+
return array_keys($this->assignments);
3343
}
3444
}

src/main/QafooLabs/Refactoring/Domain/Model/LineRange.php

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,22 @@ class LineRange
99
{
1010
private $lines = array();
1111

12-
static public function fromString($range)
12+
/**
13+
* @return LineRange
14+
*/
15+
static public function fromSingleLine($line)
1316
{
14-
list($start, $end) = explode("-", $range);
17+
$list = new self();
18+
$list->lines[$line] = $line;
1519

20+
return $list;
21+
}
22+
23+
/**
24+
* @return LineRange
25+
*/
26+
static public function fromLines($start, $end)
27+
{
1628
$list = new self();
1729

1830
for ($i = $start; $i <= $end; $i++) {
@@ -22,6 +34,16 @@ static public function fromString($range)
2234
return $list;
2335
}
2436

37+
/**
38+
* @return LineRange
39+
*/
40+
static public function fromString($range)
41+
{
42+
list($start, $end) = explode("-", $range);
43+
44+
return self::fromLines($start, $end);
45+
}
46+
2547
public function isInRange($line)
2648
{
2749
return isset($this->lines[$line]);

0 commit comments

Comments
 (0)