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

[OptimizeUse] Implemented feature optimize use #25

Merged
merged 17 commits into from
Aug 17, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@ refactor.phar
.pear
build/
composer.phar

/php-refactoring-browser.sublime-project

/php-refactoring-browser.sublime-workspace
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,15 @@ the command to fix class and namespaces.

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

### Optimize use statements

Optimizes the use of Fully qualified names in a file so that FQN is imported with
"use" at the top of the file and the FQN is replaced with its classname.

All other use statements will be untouched, only new ones will be added.

php refactor.phar optimize-use <file>

## Roadmap

Not prioritized.
Expand Down
36 changes: 35 additions & 1 deletion features/bootstrap/FeatureContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,46 @@ public function iUseRefactoringWith($refactoringName, TableNode $table)
public function thePhpFileShouldBeRefactored($file, PyStringNode $expectedPatch)
{
$output = array_map('trim', explode("\n", rtrim($this->output)));
$expectedPatch = array_map('trim', explode("\n", rtrim((string)$expectedPatch)));
$expectedPatch = $this->formatExpectedPatch((string)$expectedPatch);

assertEquals(
$expectedPatch, $output,
"Refactored File:\n" . $this->output . "\n\n" .
"Diff:\n" . print_r(array_diff($expectedPatch, $output), true)
);
}

/**
* converts / paths in expectedPatch text to \ paths
*
* leaves the a/ b/ slashes untouched
* returns an array of lines
* @return array
*/
protected function formatExpectedPatch($patch)
{
if ('\\' === DIRECTORY_SEPARATOR) {
$formatLine = function ($line) {
$line = preg_replace_callback('~^((?:---|\+\+\+)\s*(?:a|b)/)(.*)~', function ($match) {
list($all, $diff, $path) = $match;

if (0 === preg_match('~^[a-z]+://~i', $path)) {
$path = str_replace('/', '\\', $path);
}

return $diff.$path;

}, $line);

return trim($line);
};

} else {
$formatLine = function ($line) {
return trim($line);
};
}

return array_map($formatLine, explode("\n", rtrim($patch)));
}
}
50 changes: 50 additions & 0 deletions features/optimize_use.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
Feature: Optimize use
To optimize the use statements in my code
As a developer
I need to convert every FQN in the code to a use statement in the file

Scenario: Convert FQN and leave relative names
Given a PHP File named "src/Foo.php" with:
"""
<?php

namespace Bar;

use Bar\Baz\Service;

class Foo
{
public function operation()
{
$flag = Qux\Adapter::CONSTANT_ARG;

$service = new Service();
$service->operation(new \Bar\Qux\Adapter($flag));

return $service;
}
}
"""
When I use refactoring "optimize-use" with:
| arg | value |
| file | src/Foo.php |
Then the PHP File "src/Foo.php" should be refactored:
"""
--- a/vfs://project/src/Foo.php
+++ b/vfs://project/src/Foo.php
@@ -3,5 +3,6 @@
namespace Bar;

use Bar\Baz\Service;
+use Bar\Qux\Adapter;

class Foo
@@ -12,5 +12,5 @@

$service = new Service();
- $service->operation(new \Bar\Qux\Adapter($flag));
+ $service->operation(new Adapter($flag));

return $service;
"""

Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
use QafooLabs\Refactoring\Adapters\PHPParser\Visitor\PhpNameCollector;
use QafooLabs\Refactoring\Domain\Services\PhpNameScanner;
use QafooLabs\Refactoring\Domain\Model\File;
use QafooLabs\Refactoring\Domain\Model\LineRange;
use QafooLabs\Refactoring\Domain\Model\UseStatement;
use QafooLabs\Refactoring\Domain\Model\PhpName;

use PHPParser_Parser;
Expand All @@ -24,8 +26,25 @@ public function findNames(File $file)
$traverser->addVisitor($collector);
$traverser->traverse($stmts);

return array_map(function ($use) use ($file) {
return new PhpName($use['fqcn'], $use['alias'], $file, $use['line']);
return array_map(function ($name) use ($file) {
return new PhpName(
$name['fqcn'],
$name['alias'],
$file,
$name['line'],
isset($name['parent'])
? $this->createParent($name['parent'], $file)
: null
);

}, $collector->collectedNameDeclarations());
}

private function createParent(Array $parent, File $file)
{
switch ($parent['type']) {
case 'use':
return new UseStatement($file, LineRange::fromLines($parent['lines'][0], $parent['lines'][1]));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,23 +33,35 @@ class PhpNameCollector extends \PHPParser_NodeVisitorAbstract

public function enterNode(PHPParser_Node $node)
{
if ($node instanceof PHPParser_Node_Stmt_UseUse) {
$name = implode('\\', $node->name->parts);

$this->useStatements[$node->alias] = $name;
$this->nameDeclarations[] = array(
'alias' => $name,
'fqcn' => $name,
'line' => $node->getLine()
if ($node instanceof PHPParser_Node_Stmt_Use) {
$parent = array(
'type' => 'use',
'lines' => array($node->getAttribute('startLine'), $node->getAttribute('endLine'))
);

foreach ($node->uses as $use) {
if ($use instanceof PHPParser_Node_Stmt_UseUse) {
$name = implode('\\', $use->name->parts);

$this->useStatements[$use->alias] = $name;
$this->nameDeclarations[] = array(
'alias' => $name,
'fqcn' => $name,
'line' => $use->getLine(),
'parent' => $parent
);
}
}
}


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

$this->nameDeclarations[] = array(
'alias' => $usedAlias,
'fqcn' => $this->fullyQualifiedNameFor($usedAlias),
'fqcn' => $this->fullyQualifiedNameFor($usedAlias, $node->class->isFullyQualified()),
'line' => $node->getLine(),
);
}
Expand All @@ -59,7 +71,7 @@ public function enterNode(PHPParser_Node $node)

$this->nameDeclarations[] = array(
'alias' => $usedAlias,
'fqcn' => $this->fullyQualifiedNameFor($usedAlias),
'fqcn' => $this->fullyQualifiedNameFor($usedAlias, $node->class->isFullyQualified()),
'line' => $node->getLine(),
);
}
Expand All @@ -70,7 +82,7 @@ public function enterNode(PHPParser_Node $node)

$this->nameDeclarations[] = array(
'alias' => $usedAlias,
'fqcn' => $this->fullyQualifiedNameFor($usedAlias),
'fqcn' => $this->fullyQualifiedNameFor($usedAlias, $node->extends->isFullyQualified()),
'line' => $node->extends->getLine(),
);
}
Expand All @@ -80,7 +92,7 @@ public function enterNode(PHPParser_Node $node)

$this->nameDeclarations[] = array(
'alias' => $usedAlias,
'fqcn' => $this->fullyQualifiedNameFor($usedAlias),
'fqcn' => $this->fullyQualifiedNameFor($usedAlias, $implement->isFullyQualified()),
'line' => $implement->getLine(),
);
}
Expand All @@ -92,11 +104,11 @@ public function enterNode(PHPParser_Node $node)
}
}

private function fullyQualifiedNameFor($alias)
private function fullyQualifiedNameFor($alias, $isFullyQualified)
{
$isAbsolute = $alias[0] === "\\";

if ($isAbsolute) {
if ($isAbsolute || $isFullyQualified) {
$class = $alias;
} else if (isset($this->useStatements[$alias])) {
$class = $this->useStatements[$alias];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ protected function getDefaultCommands()
$commands[] = new Commands\RenameLocalVariableCommand();
$commands[] = new Commands\ConvertLocalToInstanceVariableCommand();
$commands[] = new Commands\FixClassNamesCommand();
$commands[] = new Commands\OptimizeUseCommand();

return $commands;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php
/**
* Qafoo PHP Refactoring Browser
*
* LICENSE
*
* This source file is subject to the MIT license that is bundled
* with this package in the file LICENSE.txt.
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to kontakt@beberlei.de so I can send you a copy immediately.
*/

namespace QafooLabs\Refactoring\Adapters\Symfony\Commands;

use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Command\Command;

use QafooLabs\Refactoring\Application\OptimizeUse;
use QafooLabs\Refactoring\Adapters\PHPParser\ParserVariableScanner;
use QafooLabs\Refactoring\Adapters\PHPParser\ParserPhpNameScanner;
use QafooLabs\Refactoring\Adapters\TokenReflection\StaticCodeAnalysis;
use QafooLabs\Refactoring\Adapters\Patches\PatchEditor;
use QafooLabs\Refactoring\Adapters\Symfony\OutputPatchCommand;

use QafooLabs\Refactoring\Domain\Model\LineRange;
use QafooLabs\Refactoring\Domain\Model\File;

/**
* Symfony Adapter to execute the Optimize Use Refactoring
*/
class OptimizeUseCommand extends Command
{
protected function configure()
{
$this
->setName('optimize-use')
->setDescription('Optimize use statements of a file. Replace FQNs with imported aliases.')
->addArgument('file', InputArgument::REQUIRED, 'File that contains the use statements to optimize')
->setHelp(<<<HELP
Optimizes the use of Fully qualified names in a file so that FQN is imported with
"use" at the top of the file and the FQN is replaced with its classname.

All other use statements will be untouched, only new ones will be added.

<comment>Operations:</comment>

1. import found FQNs
2. replace FQNs with the imported classname

<comment>Pre-Conditions:</comment>

1. File has a single namespace defined

<comment>Known issues:</comment>

1. a FQN might be renamed with an conflicting name when the className of the renamend full qualified name is already in use
2. if there is no use statement in the whole file, new ones will be appended after the namespace

<comment>Usage:</comment>

<info>php refactor.phar optimize-use file.php</info>

Will optimize the use statements in <info>file.php</info>.
HELP
);
;
}

protected function execute(InputInterface $input, OutputInterface $output)
{
$file = File::createFromPath($input->getArgument('file'), getcwd());

$codeAnalysis = new StaticCodeAnalysis();
$editor = new PatchEditor(new OutputPatchCommand($output));
$phpNameScanner = new ParserPhpNameScanner();

$optimizeUse = new OptimizeUse($codeAnalysis, $editor, $phpNameScanner);
$optimizeUse->refactor($file);
}
}
Loading