Skip to content

Commit acc316f

Browse files
committed
Add rename property command
1 parent 5696332 commit acc316f

File tree

12 files changed

+481
-3
lines changed

12 files changed

+481
-3
lines changed

src/main/QafooLabs/Refactoring/Adapters/PHPParser/ParserVariableScanner.php

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
use PhpParser\NodeTraverser;
1818
use PhpParser\ParserFactory;
19+
use QafooLabs\Refactoring\Adapters\PHPParser\Visitor\PropertiesCollector;
20+
use QafooLabs\Refactoring\Domain\Model\DefinedProperties;
1921
use QafooLabs\Refactoring\Domain\Model\LineRange;
2022
use QafooLabs\Refactoring\Domain\Model\File;
2123
use QafooLabs\Refactoring\Domain\Model\DefinedVariables;
@@ -29,9 +31,7 @@ class ParserVariableScanner implements VariableScanner
2931
{
3032
public function scanForVariables(File $file, LineRange $range)
3133
{
32-
$parserFactory = new ParserFactory();
33-
$parser = $parserFactory->create(ParserFactory::PREFER_PHP7);
34-
$stmts = $parser->parse($file->getCode());
34+
$stmts = $this->parse($file);
3535

3636
$collector = new LineRangeStatementCollector($range);
3737

@@ -57,4 +57,40 @@ public function scanForVariables(File $file, LineRange $range)
5757

5858
return new DefinedVariables($localVariables, $assignments);
5959
}
60+
61+
public function scanForProperties(File $file, LineRange $range)
62+
{
63+
$stmts = $this->parse($file);
64+
65+
$collector = new LineRangeStatementCollector($range);
66+
67+
$traverser = new NodeTraverser();
68+
$traverser->addVisitor($collector);
69+
70+
$traverser->traverse($stmts);
71+
72+
$selectedStatements = $collector->getStatements();
73+
74+
if ( ! $selectedStatements) {
75+
throw new \RuntimeException('No statements found in line range.');
76+
}
77+
78+
$propertiesCollector = new PropertiesCollector();
79+
$traverser = new NodeTraverser();
80+
$traverser->addVisitor($propertiesCollector);
81+
$traverser->traverse($selectedStatements);
82+
83+
$definitions = $propertiesCollector->getDeclarations();
84+
$usages = $propertiesCollector->getUsages();
85+
86+
return new DefinedProperties($definitions, $usages);
87+
}
88+
89+
private function parse(File $file)
90+
{
91+
$parserFactory = new ParserFactory();
92+
$parser = $parserFactory->create(ParserFactory::PREFER_PHP7);
93+
94+
return $parser->parse($file->getCode());
95+
}
6096
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
/**
3+
* Qafoo PHP Refactoring Browser
4+
*
5+
* LICENSE
6+
*
7+
* This source file is subject to the MIT license that is bundled
8+
* with this package in the file LICENSE.txt.
9+
* If you did not receive a copy of the license and are unable to
10+
* obtain it through the world-wide-web, please send an email
11+
* to [email protected] so I can send you a copy immediately.
12+
*/
13+
14+
15+
namespace QafooLabs\Refactoring\Adapters\PHPParser\Visitor;
16+
17+
use PhpParser\Node;
18+
use PhpParser\NodeVisitorAbstract;
19+
use SplObjectStorage;
20+
21+
/**
22+
* Collects class properties definitions and usages.
23+
*/
24+
class PropertiesCollector extends NodeVisitorAbstract
25+
{
26+
private $usages = array();
27+
private $declarations = array();
28+
29+
private $seenPropertyUsages;
30+
31+
public function __construct()
32+
{
33+
$this->seenPropertyUsages = new SplObjectStorage();
34+
}
35+
36+
public function enterNode(Node $node)
37+
{
38+
if ($node instanceof Node\Stmt\Property) {
39+
$this->declarations[$node->props[0]->name] = $node->getLine();
40+
}
41+
42+
if ($node instanceof Node\Expr\PropertyFetch && !$this->seenPropertyUsages->contains($node)) {
43+
$this->usages[$node->name][] = $node->getLine();
44+
$this->seenPropertyUsages->attach($node);
45+
}
46+
}
47+
48+
public function getDeclarations()
49+
{
50+
return $this->declarations;
51+
}
52+
53+
public function getUsages()
54+
{
55+
return $this->usages;
56+
}
57+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ protected function getDefaultCommands()
4040
$commands = parent::getDefaultCommands();
4141
$commands[] = new Commands\ExtractMethodCommand();
4242
$commands[] = new Commands\RenameLocalVariableCommand();
43+
$commands[] = new Commands\RenamePropertyCommand();
4344
$commands[] = new Commands\ConvertLocalToInstanceVariableCommand();
4445
$commands[] = new Commands\FixClassNamesCommand();
4546
$commands[] = new Commands\OptimizeUseCommand();
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
/**
3+
* Qafoo PHP Refactoring Browser
4+
*
5+
* LICENSE
6+
*
7+
* This source file is subject to the MIT license that is bundled
8+
* with this package in the file LICENSE.txt.
9+
* If you did not receive a copy of the license and are unable to
10+
* obtain it through the world-wide-web, please send an email
11+
* to [email protected] so I can send you a copy immediately.
12+
*/
13+
14+
15+
namespace QafooLabs\Refactoring\Adapters\Symfony\Commands;
16+
17+
use Symfony\Component\Console\Input\InputArgument;
18+
19+
use Symfony\Component\Console\Input\InputInterface;
20+
use Symfony\Component\Console\Output\OutputInterface;
21+
use Symfony\Component\Console\Command\Command;
22+
23+
use QafooLabs\Refactoring\Application\RenameProperty;
24+
use QafooLabs\Refactoring\Domain\Model;
25+
26+
use QafooLabs\Refactoring\Adapters\PHPParser\ParserVariableScanner;
27+
use QafooLabs\Refactoring\Adapters\TokenReflection\StaticCodeAnalysis;
28+
use QafooLabs\Refactoring\Adapters\PatchBuilder\PatchEditor;
29+
use QafooLabs\Refactoring\Adapters\Symfony\OutputPatchCommand;
30+
31+
class RenamePropertyCommand extends Command
32+
{
33+
protected function configure()
34+
{
35+
$this
36+
->setName('rename-property')
37+
->setDescription('Rename a class property.')
38+
->addArgument('file', InputArgument::REQUIRED, 'File that contains the class')
39+
->addArgument('line', InputArgument::REQUIRED, 'Line where the property is defined or used.')
40+
->addArgument('name', InputArgument::REQUIRED, 'Current name of the property without the "$this->"')
41+
->addArgument('new-name', InputArgument::REQUIRED, 'New name of the property')
42+
->setHelp(<<<HELP
43+
Rename a class property.
44+
45+
<comment>Operations:</comment>
46+
47+
1. Renames a property by giving it a new name inside the class.
48+
49+
<comment>Pre-Conditions:</comment>
50+
51+
1. Check that new property name does not exist (NOT YET CHECKED).
52+
53+
<comment>Usage:</comment>
54+
55+
<info>php refactor.phar rename-property file.php 17 hello newHello</info>
56+
57+
Renames <info>\$hello</info> in line <info>17</info> of <info>file.php</info> into <info>\$newHello</info>.
58+
59+
HELP
60+
);
61+
}
62+
63+
protected function execute(InputInterface $input, OutputInterface $output)
64+
{
65+
$file = Model\File::createFromPath($input->getArgument('file'), getcwd());
66+
$line = (int)$input->getArgument('line');
67+
$name = new Model\Variable($input->getArgument('name'));
68+
$newName = new Model\Variable($input->getArgument('new-name'));
69+
70+
$scanner = new ParserVariableScanner();
71+
$codeAnalysis = new StaticCodeAnalysis();
72+
$editor = new PatchEditor(new OutputPatchCommand($output));
73+
74+
$renameLocalVariable = new RenameProperty($scanner, $codeAnalysis, $editor);
75+
$renameLocalVariable->refactor($file, $line, $name, $newName);
76+
}
77+
}

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

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,28 @@ public function __construct()
3333
// caching in memory gives us error for now :(
3434
}
3535

36+
public function getClassStartLine(File $file, LineRange $range)
37+
{
38+
$class = $this->findMatchingClass($file, $range);
39+
40+
if ($class === null) {
41+
throw new \InvalidArgumentException('Could not find class start line.');
42+
}
43+
44+
return $class->getStartLine();
45+
}
46+
47+
public function getClassEndLine(File $file, LineRange $range)
48+
{
49+
$class = $this->findMatchingClass($file, $range);
50+
51+
if ($class === null) {
52+
throw new \InvalidArgumentException('Could not find class end line.');
53+
}
54+
55+
return $class->getEndLine();
56+
}
57+
3658
public function isMethodStatic(File $file, LineRange $range)
3759
{
3860
$method = $this->findMatchingMethod($file, $range);
@@ -123,6 +145,11 @@ public function isLocalScope(File $file, LineRange $range)
123145
return $this->isInsideMethod($file, $range) || $this->isInsideFunction($file, $range);
124146
}
125147

148+
public function isClassScope(File $file, LineRange $range)
149+
{
150+
return $this->findMatchingClass($file, $range) !== null;
151+
}
152+
126153
/**
127154
* @param File $file
128155
* @return PhpClass[]
@@ -148,6 +175,26 @@ public function findClasses(File $file)
148175
return $classes;
149176
}
150177

178+
private function findMatchingClass(File $file, LineRange $range)
179+
{
180+
$foundClass = null;
181+
182+
$this->broker = new Broker(new Memory);
183+
$file = $this->broker->processString($file->getCode(), $file->getRelativePath(), true);
184+
$lastLine = $range->getEnd();
185+
186+
foreach ($file->getNamespaces() as $namespace) {
187+
foreach ($namespace->getClasses() as $class) {
188+
if ($class->getStartLine() <= $lastLine && $lastLine <= $class->getEndLine()) {
189+
$foundClass = $class;
190+
break;
191+
}
192+
}
193+
}
194+
195+
return $foundClass;
196+
}
197+
151198
private function findMatchingMethod(File $file, LineRange $range)
152199
{
153200
$foundMethod = null;
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
namespace QafooLabs\Refactoring\Application;
4+
5+
use QafooLabs\Refactoring\Domain\Model\EditingAction;
6+
use QafooLabs\Refactoring\Domain\Model\File;
7+
use QafooLabs\Refactoring\Domain\Model\Variable;
8+
9+
use QafooLabs\Refactoring\Domain\Model\LineRange;
10+
use QafooLabs\Refactoring\Domain\Model\RefactoringException;
11+
12+
/**
13+
* Rename Property Refactoring
14+
*/
15+
class RenameProperty extends SingleFileRefactoring
16+
{
17+
/**
18+
* @var Variable
19+
*/
20+
private $oldName;
21+
22+
/**
23+
* @var Variable
24+
*/
25+
private $newName;
26+
27+
/**
28+
* @param int $line
29+
*/
30+
public function refactor(File $file, $line, Variable $oldName, Variable $newName)
31+
{
32+
$this->file = $file;
33+
$this->line = $line;
34+
$this->newName = $newName;
35+
$this->oldName = $oldName;
36+
37+
$this->assertIsClassScope();
38+
39+
$this->startEditingSession();
40+
$this->renameProperty();
41+
$this->completeEditingSession();
42+
}
43+
44+
private function renameProperty()
45+
{
46+
$definedProperties = $this->getDefinedProperties();
47+
48+
if ( ! $definedProperties->contains($this->oldName)) {
49+
throw RefactoringException::propertyNotInRange($this->oldName, LineRange::fromSingleLine($this->line));
50+
}
51+
52+
$this->session->addEdit(new EditingAction\RenameProperty($definedProperties, $this->oldName, $this->newName));
53+
}
54+
}

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,13 @@ protected function assertIsLocalScope()
6666
}
6767
}
6868

69+
protected function assertIsClassScope()
70+
{
71+
if ( ! $this->codeAnalysis->isClassScope($this->file, LineRange::fromSingleLine($this->line)) ) {
72+
throw RefactoringException::rangeIsNotClassScope(LineRange::fromSingleLine($this->line));
73+
}
74+
}
75+
6976
protected function startEditingSession()
7077
{
7178
$buffer = $this->editor->openBuffer($this->file);
@@ -94,4 +101,13 @@ protected function getDefinedVariables()
94101

95102
return $definedVariables;
96103
}
104+
105+
protected function getDefinedProperties()
106+
{
107+
$selectedClassLineRange = $this->codeAnalysis->findClassRange($this->file, LineRange::fromSingleLine($this->line));
108+
109+
return $this->variableScanner->scanForProperties(
110+
$this->file, $selectedClassLineRange
111+
);
112+
}
97113
}

0 commit comments

Comments
 (0)