add ImportsInClassCollection

This commit is contained in:
Tomas Votruba 2019-05-01 13:15:18 +02:00
parent 85ff969f30
commit c5fc331857
9 changed files with 212 additions and 128 deletions

View File

@ -0,0 +1,8 @@
services:
_defaults:
autowire: true
public: true
Rector\CodingStyle\:
resource: '../src'
exclude: '../src/{Rector/**/*Rector.php}'

View File

@ -0,0 +1,60 @@
<?php declare(strict_types=1);
namespace Rector\CodingStyle\Imports;
use Rector\CodingStyle\Naming\ClassNaming;
final class ImportsInClassCollection
{
/**
* @var string[]
*/
private $importsInClass = [];
/**
* @var ClassNaming
*/
private $classNaming;
public function __construct(ClassNaming $classNaming)
{
$this->classNaming = $classNaming;
}
public function addImport(string $import): void
{
$this->importsInClass[] = $import;
}
public function hasImport(string $import): bool
{
return in_array($import, $this->importsInClass, true);
}
public function canImportBeAdded(string $import): bool
{
$shortImport = $this->classNaming->getShortName($import);
foreach ($this->importsInClass as $importsInClass) {
$shortImportInClass = $this->classNaming->getShortName($importsInClass);
if ($importsInClass !== $import && $shortImportInClass === $shortImport) {
return true;
}
}
return false;
}
public function reset(): void
{
$this->importsInClass = [];
}
/**
* @return string[]
*/
public function get(): array
{
return $this->importsInClass;
}
}

View File

@ -0,0 +1,13 @@
<?php declare(strict_types=1);
namespace Rector\CodingStyle\Naming;
use Nette\Utils\Strings;
final class ClassNaming
{
public function getShortName(string $fullyQualifiedName): string
{
return Strings::after($fullyQualifiedName, '\\', -1) ?: $fullyQualifiedName;
}
}

View File

@ -9,6 +9,8 @@ use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node\Stmt\Use_;
use PhpParser\Node\Stmt\UseUse;
use Rector\CodingStyle\Imports\ImportsInClassCollection;
use Rector\CodingStyle\Naming\ClassNaming;
use Rector\Exception\ShouldNotHappenException;
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator;
use Rector\PhpParser\NodeTraverser\CallableNodeTraverser;
@ -28,11 +30,6 @@ final class ImportFullyQualifiedNamesRector extends AbstractRector
*/
private $newUseStatements = [];
/**
* @var string[]
*/
private $alreadyImportedUses = [];
/**
* @var string[]
*/
@ -43,10 +40,26 @@ final class ImportFullyQualifiedNamesRector extends AbstractRector
*/
private $docBlockManipulator;
public function __construct(CallableNodeTraverser $callableNodeTraverser, DocBlockManipulator $docBlockManipulator)
{
/**
* @var ImportsInClassCollection
*/
private $importsInClassCollection;
/**
* @var ClassNaming
*/
private $classNaming;
public function __construct(
CallableNodeTraverser $callableNodeTraverser,
DocBlockManipulator $docBlockManipulator,
ImportsInClassCollection $importsInClassCollection,
ClassNaming $classNaming
) {
$this->callableNodeTraverser = $callableNodeTraverser;
$this->docBlockManipulator = $docBlockManipulator;
$this->importsInClassCollection = $importsInClassCollection;
$this->classNaming = $classNaming;
}
public function getDefinition(): RectorDefinition
@ -91,8 +104,9 @@ CODE_SAMPLE
*/
public function refactor(Node $node): ?Node
{
$this->alreadyImportedUses = [];
$this->newUseStatements = [];
$this->importsInClassCollection->reset();
$this->docBlockManipulator->resetImportedNames();
/** @var Class_|null $class */
$class = $this->betterNodeFinder->findFirstInstanceOf($node, Class_::class);
@ -110,6 +124,17 @@ CODE_SAMPLE
private function resolveAlreadyImportedUses(Namespace_ $namespace): void
{
/** @var Class_ $class */
$class = $this->betterNodeFinder->findFirstInstanceOf($namespace->stmts, Class_::class);
// add class itself
$className = $this->getName($class);
if ($className === null) {
return;
}
$this->importsInClassCollection->addImport($className);
/** @var Use_[] $uses */
$uses = $this->betterNodeFinder->find($namespace->stmts, function (Node $node) {
if (! $node instanceof Use_) {
@ -132,7 +157,7 @@ CODE_SAMPLE
$this->aliasedUses[] = $name;
}
$this->alreadyImportedUses[] = $name;
$this->importsInClassCollection->addImport($name);
}
}
@ -145,7 +170,7 @@ CODE_SAMPLE
return;
}
$this->alreadyImportedUses[] = $className;
$this->importsInClassCollection->addImport($className);
}
/**
@ -161,14 +186,14 @@ CODE_SAMPLE
foreach ($newUseStatements as $newUseStatement) {
// already imported in previous cycle
if (in_array($newUseStatement, $this->alreadyImportedUses, true)) {
if ($this->importsInClassCollection->hasImport($newUseStatement)) {
continue;
}
$useUse = new UseUse(new Name($newUseStatement));
$newUses[] = new Use_([$useUse]);
$this->alreadyImportedUses[] = $newUseStatement;
$this->importsInClassCollection->addImport($newUseStatement);
}
$namespace->stmts = array_merge($newUses, $namespace->stmts);
@ -179,6 +204,7 @@ CODE_SAMPLE
*/
private function importNamesAndCollectNewUseStatements(Class_ $class): array
{
// probably anonymous class
if ($class->name === null) {
return [];
}
@ -213,7 +239,7 @@ CODE_SAMPLE
return null;
}
$shortName = $this->getShortName($fullyQualifiedName);
$shortName = $this->classNaming->getShortName($fullyQualifiedName);
if (isset($this->newUseStatements[$shortName])) {
if ($fullyQualifiedName === $this->newUseStatements[$shortName]) {
return new Name($shortName);
@ -222,7 +248,7 @@ CODE_SAMPLE
return null;
}
if (! in_array($fullyQualifiedName, $this->alreadyImportedUses, true)) {
if (! $this->importsInClassCollection->hasImport($fullyQualifiedName)) {
$this->newUseStatements[$shortName] = $fullyQualifiedName;
}
@ -237,18 +263,13 @@ CODE_SAMPLE
// for doc blocks
$this->callableNodeTraverser->traverseNodesWithCallable([$class], function (Node $node): void {
$importedDocUseStatements = $this->docBlockManipulator->importNames($node, $this->alreadyImportedUses);
$importedDocUseStatements = $this->docBlockManipulator->importNames($node);
$this->newUseStatements = array_merge($this->newUseStatements, $importedDocUseStatements);
});
return $this->newUseStatements;
}
private function getShortName(string $fullyQualifiedName): string
{
return Strings::after($fullyQualifiedName, '\\', -1) ?: $fullyQualifiedName;
}
// 1. name is fully qualified → import it
private function shouldSkipName(string $fullyQualifiedName): bool
{
@ -257,20 +278,13 @@ CODE_SAMPLE
return true;
}
$shortName = $this->getShortName($fullyQualifiedName);
$shortName = $this->classNaming->getShortName($fullyQualifiedName);
// nothing to change
if ($shortName === $fullyQualifiedName) {
return true;
}
foreach ($this->alreadyImportedUses as $alreadyImportedUse) {
$shortAlreadyImportedUsed = $this->getShortName($alreadyImportedUse);
if ($alreadyImportedUse !== $fullyQualifiedName && $shortAlreadyImportedUsed === $shortName) {
return true;
}
}
return false;
return $this->importsInClassCollection->canImportBeAdded($fullyQualifiedName);
}
}

View File

@ -7,9 +7,33 @@ use Some\Trait_;
final class SameEnd
{
/**
* @param Another\Trait_ $phpStanScopeFactory
* @param \Some\Trait_ $firstTrait
* @param Another\Trait_ $secondTrait
* @param \Some\Trait_ $thirdTrait
*/
public function __construct(Another\Trait_ $phpStanScopeFactory)
public function __construct(\Some\Trait_ $firstTrait, Another\Trait_ $secondTrait, \Some\Trait_ $thirdTrait)
{
}
}
?>
-----
<?php
namespace Rector\CodingStyle\Tests\Rector\Namespace_\ImportFullyQualifiedNamesRector\Fixture;
use Some\Trait_;
final class SameEnd
{
/**
* @param Trait_ $firstTrait
* @param Another\Trait_ $secondTrait
* @param Trait_ $thirdTrait
*/
public function __construct(Trait_ $firstTrait, Another\Trait_ $secondTrait, Trait_ $thirdTrait)
{
}
}
?>

View File

@ -1,97 +1,38 @@
<?php
namespace Elasticr\Inventory;
use Doctrine\Common\Persistence\ManagerRegistry;
use Doctrine\DBAL\Connection;
use Doctrine\ORM\QueryBuilder;
use Elasticr\Inventory\Stock\Query;
namespace Rector\CodingStyle\Tests\Rector\Namespace_\ImportFullyQualifiedNamesRector\Source;
final class StockRepository
{
/** @var \Doctrine\Common\Persistence\ManagerRegistry */
private $registry;
public function __construct(ManagerRegistry $registry)
public function filter(Stock\Query $query)
{
$this->registry = $registry;
}
public function clear(array $stockrooms): void
{
/** @var \Doctrine\DBAL\Connection $connection */
$connection = $this->registry->getConnection();
$query = $connection->createQueryBuilder();
$query->delete('elasticr_stocks')
->andWhere('stockroom_id IN (:id)')
->setParameter('id', $stockrooms, Connection::PARAM_INT_ARRAY);
$query->execute();
}
/**
* @throws \Doctrine\DBAL\DBALException
*/
public function add(InventoryItems $items): void
{
/** @var \Doctrine\DBAL\Connection $connection */
$connection = $this->registry->getConnection();
$counter = 0;
$inserts = [];
foreach ($items->items() as $item) {
foreach ($item->stocks() as $stock) {
$index = $counter / 1000;
$inserts[$index]['values'][] = '(:uuid'.$counter.', :stockroom'.$counter.', :quantity'.$counter.')';
$inserts[$index]['parameters'][':uuid'.$counter] = $item->uuid();
$inserts[$index]['parameters'][':stockroom'.$counter] = $stock->stockroom()->id();
$inserts[$index]['parameters'][':quantity'.$counter] = $stock->quantity();
++$counter;
}
}
foreach ($inserts as $insert) {
$query = 'INSERT INTO elasticr_stocks (uuid, stockroom_id, quantity) VALUES'.implode(',', $insert['values']);
$connection->executeUpdate($query, $insert['parameters']);
}
}
public function filter(Stock\Query $query): InventoryItems
{
if (!($query instanceof Stock\Querying\Query)) {
throw new \InvalidArgumentException('Wrong query provided');
}
/** @var Stock\Query $query */
$query = 5;
/** @var Querying\Query $query */
$builder = $this->createBuilder();
$query->build($builder);
/** @var \Elasticr\Inventory\Doctrine\Stock[] $stocks */
$stocks = $builder->getQuery()
->useQueryCache(true)
->useResultCache(true)
->getResult();
$inventoryItems = new InventoryItems();
foreach ($stocks as $stock) {
$inventoryItems->add($stock->uuid->toString(), $stock->stockroom, $stock->quantity);
}
return $inventoryItems;
}
private function createBuilder(): QueryBuilder
{
/** @var \Doctrine\ORM\EntityManager $manager */
$manager = $this->registry->getManager();
return $manager->createQueryBuilder();
}
}
?>
-----
<?php
namespace Rector\CodingStyle\Tests\Rector\Namespace_\ImportFullyQualifiedNamesRector\Source;
use Rector\CodingStyle\Tests\Rector\Namespace_\ImportFullyQualifiedNamesRector\Source\Stock\Query;
final class StockRepository
{
public function filter(Query $query)
{
/** @var Query $query */
$query = 5;
/** @var Querying\Query $query */
$builder = $this->createBuilder();
$query->build($builder);
}
}
?>

View File

@ -0,0 +1,8 @@
<?php declare(strict_types=1);
namespace Rector\CodingStyle\Tests\Rector\Namespace_\ImportFullyQualifiedNamesRector\Source\Querying;
final class Query
{
}

View File

@ -0,0 +1,8 @@
<?php declare(strict_types=1);
namespace Rector\CodingStyle\Tests\Rector\Namespace_\ImportFullyQualifiedNamesRector\Source\Stock;
final class Query
{
}

View File

@ -32,6 +32,7 @@ use Rector\BetterPhpDocParser\NodeDecorator\StringsTypePhpDocNodeDecorator;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
use Rector\BetterPhpDocParser\Printer\PhpDocInfoPrinter;
use Rector\CodingStyle\Imports\ImportsInClassCollection;
use Rector\Exception\ShouldNotHappenException;
use Rector\NodeTypeResolver\Exception\MissingTagException;
use Rector\NodeTypeResolver\Php\ParamTypeInfo;
@ -76,13 +77,19 @@ final class DocBlockManipulator
*/
private $importedNames = [];
/**
* @var ImportsInClassCollection
*/
private $importsInClassCollection;
public function __construct(
PhpDocInfoFactory $phpDocInfoFactory,
PhpDocInfoPrinter $phpDocInfoPrinter,
TypeAnalyzer $typeAnalyzer,
AttributeAwareNodeFactory $attributeAwareNodeFactory,
StringsTypePhpDocNodeDecorator $stringsTypePhpDocNodeDecorator,
NodeTraverser $nodeTraverser
NodeTraverser $nodeTraverser,
ImportsInClassCollection $importsInClassCollection
) {
$this->phpDocInfoFactory = $phpDocInfoFactory;
$this->phpDocInfoPrinter = $phpDocInfoPrinter;
@ -90,6 +97,7 @@ final class DocBlockManipulator
$this->attributeAwareNodeFactory = $attributeAwareNodeFactory;
$this->stringsTypePhpDocNodeDecorator = $stringsTypePhpDocNodeDecorator;
$this->nodeTraverser = $nodeTraverser;
$this->importsInClassCollection = $importsInClassCollection;
}
public function hasTag(Node $node, string $name): bool
@ -391,27 +399,23 @@ final class DocBlockManipulator
}
/**
* @param string[] $alreadyImportedUses
* @return string[]
*/
public function importNames(Node $node, array $alreadyImportedUses): array
public function importNames(Node $node): array
{
if ($node->getDocComment() === null) {
return [];
}
$this->importedNames = [];
$phpDocInfo = $this->createPhpDocInfoFromNode($node);
$phpDocNode = $phpDocInfo->getPhpDocNode();
$this->nodeTraverser->traverseWithCallable($phpDocNode, function (AttributeAwareNodeInterface $node) use (
$alreadyImportedUses
) {
$this->nodeTraverser->traverseWithCallable($phpDocNode, function (AttributeAwareNodeInterface $node) {
if (! $node instanceof IdentifierTypeNode) {
return $node;
}
// is class without namespaced name → skip
$name = ltrim($node->name, '\\');
if (! Strings::contains($name, '\\')) {
return $node;
@ -420,7 +424,7 @@ final class DocBlockManipulator
$fullyQualifiedName = $this->getFullyQualifiedName($node);
$shortName = $this->getShortName($name);
return $this->processFqnNameImport($node, $alreadyImportedUses, $shortName, $fullyQualifiedName);
return $this->processFqnNameImport($node, $shortName, $fullyQualifiedName);
});
$this->updateNodeWithPhpDocInfo($node, $phpDocInfo);
@ -468,6 +472,11 @@ final class DocBlockManipulator
$this->updateNodeWithPhpDocInfo($node, $phpDocInfo);
}
public function resetImportedNames(): void
{
$this->importedNames = [];
}
private function addTypeSpecificTag(Node $node, string $name, string $type): void
{
// there might be no phpdoc at all
@ -607,15 +616,14 @@ final class DocBlockManipulator
/**
* @param AttributeAwareIdentifierTypeNode $attributeAwareNode
* @param string[] $alreadyImportedUses
*/
private function processFqnNameImport(
AttributeAwareNodeInterface $attributeAwareNode,
array $alreadyImportedUses,
string $shortName,
string $fullyQualifiedName
): AttributeAwareNodeInterface {
$alreadyImportedUses = array_merge($this->importedNames, $alreadyImportedUses);
$alreadyImportedUses = array_merge($this->importedNames, $this->importsInClassCollection->get());
$alreadyImportedUses = array_unique($alreadyImportedUses);
foreach ($alreadyImportedUses as $alreadyImportedUse) {
$shortAlreadyImportedUsed = $this->getShortName($alreadyImportedUse);