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

Commit 7b97167

Browse files
committed
Merge branch 'GH-25'
2 parents 0acac74 + ece0854 commit 7b97167

File tree

15 files changed

+478
-20
lines changed

15 files changed

+478
-20
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,7 @@ refactor.phar
44
.pear
55
build/
66
composer.phar
7+
8+
/php-refactoring-browser.sublime-project
9+
10+
/php-refactoring-browser.sublime-workspace

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,15 @@ the command to fix class and namespaces.
9090

9191
php refactor.phar fix-class-names <dir>
9292

93+
### Optimize use statements
94+
95+
Optimizes the use of Fully qualified names in a file so that FQN is imported with
96+
"use" at the top of the file and the FQN is replaced with its classname.
97+
98+
All other use statements will be untouched, only new ones will be added.
99+
100+
php refactor.phar optimize-use <file>
101+
93102
## Roadmap
94103

95104
Not prioritized.

features/bootstrap/FeatureContext.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,17 @@ protected function formatExpectedPatch($patch)
125125
$line = preg_replace('~/(?<!(a|b)/)~', '\\', $line);
126126
}
127127

128+
$line = preg_replace_callback('~^((?:---|\+\+\+)\s*(?:a|b)/)(.*)~', function ($match) {
129+
list($all, $diff, $path) = $match;
130+
131+
if (0 === preg_match('~^[a-z]+://~i', $path)) {
132+
$path = str_replace('/', '\\', $path);
133+
}
134+
135+
return $diff.$path;
136+
137+
}, $line);
138+
128139
return trim($line);
129140
};
130141

features/optimize_use.feature

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
Feature: Optimize use
2+
To optimize the use statements in my code
3+
As a developer
4+
I need to convert every FQN in the code to a use statement in the file
5+
6+
Scenario: Convert FQN and leave relative names
7+
Given a PHP File named "src/Foo.php" with:
8+
"""
9+
<?php
10+
11+
namespace Bar;
12+
13+
use Bar\Baz\Service;
14+
15+
class Foo
16+
{
17+
public function operation()
18+
{
19+
$flag = Qux\Adapter::CONSTANT_ARG;
20+
21+
$service = new Service();
22+
$service->operation(new \Bar\Qux\Adapter($flag));
23+
24+
return $service;
25+
}
26+
}
27+
"""
28+
When I use refactoring "optimize-use" with:
29+
| arg | value |
30+
| file | src/Foo.php |
31+
Then the PHP File "src/Foo.php" should be refactored:
32+
"""
33+
--- a/vfs://project/src/Foo.php
34+
+++ b/vfs://project/src/Foo.php
35+
@@ -3,5 +3,6 @@
36+
namespace Bar;
37+
38+
use Bar\Baz\Service;
39+
+use Bar\Qux\Adapter;
40+
41+
class Foo
42+
@@ -12,5 +12,5 @@
43+
44+
$service = new Service();
45+
- $service->operation(new \Bar\Qux\Adapter($flag));
46+
+ $service->operation(new Adapter($flag));
47+
48+
return $service;
49+
"""
50+

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
use QafooLabs\Refactoring\Adapters\PHPParser\Visitor\PhpNameCollector;
66
use QafooLabs\Refactoring\Domain\Services\PhpNameScanner;
77
use QafooLabs\Refactoring\Domain\Model\File;
8+
use QafooLabs\Refactoring\Domain\Model\LineRange;
9+
use QafooLabs\Refactoring\Domain\Model\UseStatement;
810
use QafooLabs\Refactoring\Domain\Model\PhpName;
911
use QafooLabs\Refactoring\Domain\Model\PhpNameOccurance;
1012

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

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -42,24 +42,29 @@ class PhpNameCollector extends \PHPParser_NodeVisitorAbstract
4242

4343
public function enterNode(PHPParser_Node $node)
4444
{
45-
if ($node instanceof PHPParser_Node_Stmt_UseUse) {
46-
$name = implode('\\', $node->name->parts);
47-
48-
$this->useStatements[$node->alias] = $name;
49-
$this->nameDeclarations[] = array(
50-
'alias' => $name,
51-
'fqcn' => $name,
52-
'line' => $node->getLine(),
53-
'type' => 'use',
54-
);
45+
if ($node instanceof PHPParser_Node_Stmt_Use) {
46+
foreach ($node->uses as $use) {
47+
if ($use instanceof PHPParser_Node_Stmt_UseUse) {
48+
$name = implode('\\', $use->name->parts);
49+
50+
$this->useStatements[$use->alias] = $name;
51+
$this->nameDeclarations[] = array(
52+
'alias' => $name,
53+
'fqcn' => $name,
54+
'line' => $use->getLine(),
55+
'type' => 'use',
56+
);
57+
}
58+
}
5559
}
5660

61+
5762
if ($node instanceof PHPParser_Node_Expr_New && $node->class instanceof PHPParser_Node_Name) {
5863
$usedAlias = implode('\\', $node->class->parts);
5964

6065
$this->nameDeclarations[] = array(
6166
'alias' => $usedAlias,
62-
'fqcn' => $this->fullyQualifiedNameFor($usedAlias),
67+
'fqcn' => $this->fullyQualifiedNameFor($usedAlias, $node->class->isFullyQualified()),
6368
'line' => $node->getLine(),
6469
'type' => 'usage',
6570
);
@@ -70,7 +75,7 @@ public function enterNode(PHPParser_Node $node)
7075

7176
$this->nameDeclarations[] = array(
7277
'alias' => $usedAlias,
73-
'fqcn' => $this->fullyQualifiedNameFor($usedAlias),
78+
'fqcn' => $this->fullyQualifiedNameFor($usedAlias, $node->class->isFullyQualified()),
7479
'line' => $node->getLine(),
7580
'type' => 'usage',
7681
);
@@ -81,7 +86,7 @@ public function enterNode(PHPParser_Node $node)
8186

8287
$this->nameDeclarations[] = array(
8388
'alias' => $className,
84-
'fqcn' => $this->fullyQualifiedNameFor($className),
89+
'fqcn' => $this->fullyQualifiedNameFor($className, false),
8590
'line' => $node->getLine(),
8691
'type' => 'class',
8792
);
@@ -91,7 +96,7 @@ public function enterNode(PHPParser_Node $node)
9196

9297
$this->nameDeclarations[] = array(
9398
'alias' => $usedAlias,
94-
'fqcn' => $this->fullyQualifiedNameFor($usedAlias),
99+
'fqcn' => $this->fullyQualifiedNameFor($usedAlias, $node->extends->isFullyQualified()),
95100
'line' => $node->extends->getLine(),
96101
'type' => 'usage',
97102
);
@@ -102,7 +107,7 @@ public function enterNode(PHPParser_Node $node)
102107

103108
$this->nameDeclarations[] = array(
104109
'alias' => $usedAlias,
105-
'fqcn' => $this->fullyQualifiedNameFor($usedAlias),
110+
'fqcn' => $this->fullyQualifiedNameFor($usedAlias, $implement->isFullyQualified()),
106111
'line' => $implement->getLine(),
107112
'type' => 'usage',
108113
);
@@ -122,11 +127,11 @@ public function enterNode(PHPParser_Node $node)
122127
}
123128
}
124129

125-
private function fullyQualifiedNameFor($alias)
130+
private function fullyQualifiedNameFor($alias, $isFullyQualified)
126131
{
127132
$isAbsolute = $alias[0] === "\\";
128133

129-
if ($isAbsolute) {
134+
if ($isAbsolute || $isFullyQualified) {
130135
$class = $alias;
131136
} else if (isset($this->useStatements[$alias])) {
132137
$class = $this->useStatements[$alias];

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ protected function getDefaultCommands()
4242
$commands[] = new Commands\RenameLocalVariableCommand();
4343
$commands[] = new Commands\ConvertLocalToInstanceVariableCommand();
4444
$commands[] = new Commands\FixClassNamesCommand();
45+
$commands[] = new Commands\OptimizeUseCommand();
4546

4647
return $commands;
4748
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
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 kontakt@beberlei.de so I can send you a copy immediately.
12+
*/
13+
14+
namespace QafooLabs\Refactoring\Adapters\Symfony\Commands;
15+
16+
use Symfony\Component\Console\Input\InputArgument;
17+
use Symfony\Component\Console\Input\InputOption;
18+
use Symfony\Component\Console\Input\InputInterface;
19+
use Symfony\Component\Console\Output\OutputInterface;
20+
use Symfony\Component\Console\Command\Command;
21+
22+
use QafooLabs\Refactoring\Application\OptimizeUse;
23+
use QafooLabs\Refactoring\Adapters\PHPParser\ParserVariableScanner;
24+
use QafooLabs\Refactoring\Adapters\PHPParser\ParserPhpNameScanner;
25+
use QafooLabs\Refactoring\Adapters\TokenReflection\StaticCodeAnalysis;
26+
use QafooLabs\Refactoring\Adapters\Patches\PatchEditor;
27+
use QafooLabs\Refactoring\Adapters\Symfony\OutputPatchCommand;
28+
29+
use QafooLabs\Refactoring\Domain\Model\LineRange;
30+
use QafooLabs\Refactoring\Domain\Model\File;
31+
32+
/**
33+
* Symfony Adapter to execute the Optimize Use Refactoring
34+
*/
35+
class OptimizeUseCommand extends Command
36+
{
37+
protected function configure()
38+
{
39+
$this
40+
->setName('optimize-use')
41+
->setDescription('Optimize use statements of a file. Replace FQNs with imported aliases.')
42+
->addArgument('file', InputArgument::REQUIRED, 'File that contains the use statements to optimize')
43+
->setHelp(<<<HELP
44+
Optimizes the use of Fully qualified names in a file so that FQN is imported with
45+
"use" at the top of the file and the FQN is replaced with its classname.
46+
47+
All other use statements will be untouched, only new ones will be added.
48+
49+
<comment>Operations:</comment>
50+
51+
1. import found FQNs
52+
2. replace FQNs with the imported classname
53+
54+
<comment>Pre-Conditions:</comment>
55+
56+
1. File has a single namespace defined
57+
58+
<comment>Known issues:</comment>
59+
60+
1. a FQN might be renamed with an conflicting name when the className of the renamend full qualified name is already in use
61+
2. if there is no use statement in the whole file, new ones will be appended after the namespace
62+
63+
<comment>Usage:</comment>
64+
65+
<info>php refactor.phar optimize-use file.php</info>
66+
67+
Will optimize the use statements in <info>file.php</info>.
68+
HELP
69+
);
70+
;
71+
}
72+
73+
protected function execute(InputInterface $input, OutputInterface $output)
74+
{
75+
$file = File::createFromPath($input->getArgument('file'), getcwd());
76+
77+
$codeAnalysis = new StaticCodeAnalysis();
78+
$editor = new PatchEditor(new OutputPatchCommand($output));
79+
$phpNameScanner = new ParserPhpNameScanner();
80+
81+
$optimizeUse = new OptimizeUse($codeAnalysis, $editor, $phpNameScanner);
82+
$optimizeUse->refactor($file);
83+
}
84+
}
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 kontakt@beberlei.de so I can send you a copy immediately.
12+
*/
13+
14+
namespace QafooLabs\Refactoring\Application;
15+
16+
use QafooLabs\Refactoring\Domain\Model\Directory;
17+
use QafooLabs\Refactoring\Domain\Model\File;
18+
use QafooLabs\Refactoring\Domain\Model\PhpClassName;
19+
use QafooLabs\Refactoring\Domain\Model\PhpName;
20+
21+
class OptimizeUse
22+
{
23+
private $codeAnalysis;
24+
private $editor;
25+
private $phpNameScanner;
26+
27+
public function __construct($codeAnalysis, $editor, $phpNameScanner)
28+
{
29+
$this->codeAnalysis = $codeAnalysis;
30+
$this->editor = $editor;
31+
$this->phpNameScanner = $phpNameScanner;
32+
}
33+
34+
public function refactor(File $file)
35+
{
36+
$classes = $this->codeAnalysis->findClasses($file);
37+
$occurances = $this->phpNameScanner->findNames($file);
38+
$class = $classes[0];
39+
40+
$lastUseStatementLine = $class->namespaceDeclarationLine() + 2;
41+
$usedNames = array();
42+
$fqcns = array();
43+
44+
foreach ($occurances as $occurance) {
45+
$name = $occurance->name();
46+
47+
if ($name->type() === PhpName::TYPE_NAMESPACE) {
48+
continue;
49+
}
50+
51+
if ($name->isUse()) {
52+
$lastUseStatementLine = $occurance->declarationLine();
53+
$usedNames[] = $name->fullyQualifiedName();
54+
} elseif ($name->isFullyQualified()) {
55+
$fqcns[] = $occurance;
56+
}
57+
}
58+
59+
if (!$fqcns) {
60+
return;
61+
}
62+
63+
$buffer = $this->editor->openBuffer($file);
64+
65+
foreach ($fqcns as $occurance) {
66+
$name = $occurance->name();
67+
$buffer->replaceString($occurance->declarationLine(), '\\'.$name->fullyQualifiedName(), $name->shortname());
68+
69+
if (!in_array($name->fullyQualifiedName(), $usedNames)) {
70+
$buffer->append($lastUseStatementLine, array(sprintf('use %s;', $name->fullyQualifiedName())));
71+
$lastUseStatementLine++;
72+
}
73+
}
74+
75+
$this->editor->save();
76+
}
77+
}

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,9 @@ public function relativeName()
168168
return $this->relativeName;
169169
}
170170

171+
/**
172+
* @return bool
173+
*/
171174
public function equals(PhpName $other)
172175
{
173176
return $this->fullyQualifiedName === $other->fullyQualifiedName &&
@@ -203,4 +206,24 @@ public function type()
203206
{
204207
return $this->type;
205208
}
209+
210+
/**
211+
* Is the relative name fully qualified ?
212+
*
213+
* @return bool
214+
*/
215+
public function isFullyQualified()
216+
{
217+
return $this->fullyQualifiedName === $this->relativeName;
218+
}
219+
220+
/**
221+
* Is the php name found in a use statement?
222+
*
223+
* @return bool
224+
*/
225+
public function isUse()
226+
{
227+
return $this->type === PhpName::TYPE_USE;
228+
}
206229
}

0 commit comments

Comments
 (0)