mirror of
https://github.com/rectorphp/rector.git
synced 2024-06-15 23:52:23 +00:00
refactor ImportFullyQualifiedNamesRector to allow non-namespaced elements
This commit is contained in:
parent
5757e867e0
commit
53cd941dcd
5
ecs.yaml
5
ecs.yaml
|
@ -66,9 +66,7 @@ parameters:
|
|||
PhpCsFixer\Fixer\Phpdoc\PhpdocTypesFixer:
|
||||
- 'packages/Php/src/Rector/Double/RealToFloatTypeCastRector.php'
|
||||
|
||||
Symplify\CodingStandard\Sniffs\CleanCode\ForbiddenReferenceSniff:
|
||||
# 3rd party contract
|
||||
- 'src/PhpParser/Printer/BetterStandardPrinter.php'
|
||||
Symplify\CodingStandard\Sniffs\CleanCode\ForbiddenReferenceSniff: ~
|
||||
|
||||
Symplify\CodingStandard\Fixer\Php\ClassStringToClassConstantFixer:
|
||||
# classes might not exist
|
||||
|
@ -146,3 +144,4 @@ parameters:
|
|||
Symplify\CodingStandard\Sniffs\DependencyInjection\NoClassInstantiationSniff:
|
||||
# 3rd party api
|
||||
- 'src/PhpParser/Node/Value/ValueResolver.php'
|
||||
|
||||
|
|
|
@ -3,7 +3,11 @@
|
|||
namespace Rector\CodeQuality\Rector\FuncCall;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\Array_;
|
||||
use PhpParser\Node\Expr\ArrayItem;
|
||||
use PhpParser\Node\Expr\FuncCall;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\Node\Scalar\String_;
|
||||
use Rector\Rector\AbstractRector;
|
||||
use Rector\RectorDefinition\CodeSample;
|
||||
use Rector\RectorDefinition\RectorDefinition;
|
||||
|
@ -64,13 +68,11 @@ CODE_SAMPLE
|
|||
return null;
|
||||
}
|
||||
|
||||
$array = new Node\Expr\Array_();
|
||||
$array = new Array_();
|
||||
|
||||
foreach ($node->args as $arg) {
|
||||
$variableName = $this->getValue($arg->value);
|
||||
$array->items[] = new Node\Expr\ArrayItem(new Node\Expr\Variable($variableName), new Node\Scalar\String_(
|
||||
$variableName
|
||||
));
|
||||
$array->items[] = new ArrayItem(new Variable($variableName), new String_($variableName));
|
||||
}
|
||||
|
||||
return $array;
|
||||
|
|
201
packages/CodingStyle/src/Application/UseAddingCommander.php
Normal file
201
packages/CodingStyle/src/Application/UseAddingCommander.php
Normal file
|
@ -0,0 +1,201 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\CodingStyle\Application;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Stmt;
|
||||
use PhpParser\Node\Stmt\Namespace_;
|
||||
use Rector\CodingStyle\Imports\UsedImportsResolver;
|
||||
use Rector\CodingStyle\Naming\ClassNaming;
|
||||
use Rector\Contract\PhpParser\Node\CommanderInterface;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\PhpParser\Node\BetterNodeFinder;
|
||||
use Symplify\PackageBuilder\FileSystem\SmartFileInfo;
|
||||
|
||||
final class UseAddingCommander implements CommanderInterface
|
||||
{
|
||||
/**
|
||||
* @var string[][]
|
||||
*/
|
||||
private $useImportsInFilePath = [];
|
||||
|
||||
/**
|
||||
* @var string[][]
|
||||
*/
|
||||
private $functionUseImportsInFilePath = [];
|
||||
|
||||
/**
|
||||
* @var UseImportsAdder
|
||||
*/
|
||||
private $useImportsAdder;
|
||||
|
||||
/**
|
||||
* @var ClassNaming
|
||||
*/
|
||||
private $classNaming;
|
||||
|
||||
/**
|
||||
* @var UsedImportsResolver
|
||||
*/
|
||||
private $usedImportsResolver;
|
||||
|
||||
/**
|
||||
* @var BetterNodeFinder
|
||||
*/
|
||||
private $betterNodeFinder;
|
||||
|
||||
public function __construct(
|
||||
UseImportsAdder $useImportsAdder,
|
||||
ClassNaming $classNaming,
|
||||
UsedImportsResolver $usedImportsResolver,
|
||||
BetterNodeFinder $betterNodeFinder
|
||||
) {
|
||||
$this->useImportsAdder = $useImportsAdder;
|
||||
$this->classNaming = $classNaming;
|
||||
$this->usedImportsResolver = $usedImportsResolver;
|
||||
$this->betterNodeFinder = $betterNodeFinder;
|
||||
}
|
||||
|
||||
public function addUseImport(Node $node, string $useImport): void
|
||||
{
|
||||
/** @var SmartFileInfo|null $fileInfo */
|
||||
$fileInfo = $node->getAttribute(AttributeKey::FILE_INFO);
|
||||
|
||||
if ($fileInfo === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->useImportsInFilePath[$fileInfo->getRealPath()][] = $useImport;
|
||||
}
|
||||
|
||||
public function addFunctionUseImport(Node $node, string $functionUseImport): void
|
||||
{
|
||||
/** @var SmartFileInfo $fileInfo */
|
||||
$fileInfo = $node->getAttribute(AttributeKey::FILE_INFO);
|
||||
$this->functionUseImportsInFilePath[$fileInfo->getRealPath()][] = $functionUseImport;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Stmt[] $nodes
|
||||
* @return Stmt[]
|
||||
*/
|
||||
public function traverseNodes(array $nodes): array
|
||||
{
|
||||
// no nodes → just return
|
||||
if (! isset($nodes[0])) {
|
||||
return $nodes;
|
||||
}
|
||||
|
||||
$filePath = $this->getRealPathFromNode($nodes[0]);
|
||||
|
||||
$useImports = $this->useImportsInFilePath[$filePath] ?? [];
|
||||
$functionUseImports = $this->functionUseImportsInFilePath[$filePath] ?? [];
|
||||
|
||||
// nothing to import
|
||||
if ($useImports === [] && $functionUseImports === []) {
|
||||
return $nodes;
|
||||
}
|
||||
|
||||
// clear applied imports, so isActive() doesn't return any false positives
|
||||
unset($this->useImportsInFilePath[$filePath], $this->functionUseImportsInFilePath[$filePath]);
|
||||
|
||||
// A. has namespace? add under it
|
||||
$namespace = $this->betterNodeFinder->findFirstInstanceOf($nodes, Namespace_::class);
|
||||
if ($namespace instanceof Namespace_) {
|
||||
$this->useImportsAdder->addImportsToNamespace($namespace, $useImports, $functionUseImports);
|
||||
return $nodes;
|
||||
}
|
||||
|
||||
// B. no namespace? add in the top
|
||||
return $this->useImportsAdder->addImportsToStmts($nodes, $useImports, $functionUseImports);
|
||||
}
|
||||
|
||||
public function isActive(): bool
|
||||
{
|
||||
return count($this->useImportsInFilePath) > 0 || count($this->functionUseImportsInFilePath) > 0;
|
||||
}
|
||||
|
||||
public function isShortImported(Node $node, string $fullyQualifiedName): bool
|
||||
{
|
||||
$filePath = $this->getRealPathFromNode($node);
|
||||
$shortName = $this->classNaming->getShortName($fullyQualifiedName);
|
||||
|
||||
$fileUseImports = $this->useImportsInFilePath[$filePath] ?? [];
|
||||
foreach ($fileUseImports as $fileUseImport) {
|
||||
$shortFileUseImport = $this->classNaming->getShortName($fileUseImport);
|
||||
if ($shortFileUseImport === $shortName) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
$fileFunctionUseImports = $this->functionUseImportsInFilePath[$filePath] ?? [];
|
||||
foreach ($fileFunctionUseImports as $fileFunctionUseImport) {
|
||||
$shortFileFunctionUseImport = $this->classNaming->getShortName($fileFunctionUseImport);
|
||||
if ($shortFileFunctionUseImport === $shortName) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isImportShortable(Node $node, string $fullyQualifiedName): bool
|
||||
{
|
||||
$filePath = $this->getRealPathFromNode($node);
|
||||
|
||||
$fileUseImports = $this->useImportsInFilePath[$filePath] ?? [];
|
||||
if (in_array($fullyQualifiedName, $fileUseImports, true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$functionUseImports = $this->functionUseImportsInFilePath[$filePath] ?? [];
|
||||
if (in_array($fullyQualifiedName, $functionUseImports, true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function analyseFileInfoUseStatements(Node $node): void
|
||||
{
|
||||
$filePath = $this->getRealPathFromNode($node);
|
||||
|
||||
$usedImports = $this->usedImportsResolver->resolveForNode($node);
|
||||
|
||||
foreach ($usedImports as $usedImport) {
|
||||
$this->useImportsInFilePath[$filePath][] = $usedImport;
|
||||
}
|
||||
}
|
||||
|
||||
public function hasImport(Name $name, string $fullyQualifiedName): bool
|
||||
{
|
||||
$filePath = $this->getRealPathFromNode($name);
|
||||
|
||||
return in_array($fullyQualifiedName, $this->useImportsInFilePath[$filePath] ?? [], true);
|
||||
}
|
||||
|
||||
public function canImportBeAdded(Name $name, string $import): bool
|
||||
{
|
||||
$shortImport = $this->classNaming->getShortName($import);
|
||||
|
||||
$filePath = $this->getRealPathFromNode($name);
|
||||
|
||||
foreach ($this->useImportsInFilePath[$filePath] ?? [] as $importsInClass) {
|
||||
$shortImportInClass = $this->classNaming->getShortName($importsInClass);
|
||||
if ($importsInClass !== $import && $shortImportInClass === $shortImport) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function getRealPathFromNode(Node $node): ?string
|
||||
{
|
||||
/** @var SmartFileInfo|null $fileInfo */
|
||||
$fileInfo = $node->getAttribute(AttributeKey::FILE_INFO);
|
||||
|
||||
return $fileInfo->getRealPath();
|
||||
}
|
||||
}
|
121
packages/CodingStyle/src/Application/UseImportsAdder.php
Normal file
121
packages/CodingStyle/src/Application/UseImportsAdder.php
Normal file
|
@ -0,0 +1,121 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\CodingStyle\Application;
|
||||
|
||||
use Nette\Utils\Strings;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Stmt;
|
||||
use PhpParser\Node\Stmt\Namespace_;
|
||||
use PhpParser\Node\Stmt\Use_;
|
||||
use PhpParser\Node\Stmt\UseUse;
|
||||
use Rector\CodingStyle\Imports\UsedImportsResolver;
|
||||
|
||||
final class UseImportsAdder
|
||||
{
|
||||
/**
|
||||
* @var UsedImportsResolver
|
||||
*/
|
||||
private $usedImportsResolver;
|
||||
|
||||
public function __construct(UsedImportsResolver $usedImportsResolver)
|
||||
{
|
||||
$this->usedImportsResolver = $usedImportsResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Stmt[] $stmts
|
||||
* @param string[] $useImports
|
||||
* @param string[] $functionUseImports
|
||||
* @return Stmt[]
|
||||
*/
|
||||
public function addImportsToStmts(array $stmts, array $useImports, array $functionUseImports): array
|
||||
{
|
||||
$existingUseImports = $this->usedImportsResolver->resolveForStmts($stmts);
|
||||
$existingFunctionUseImports = $this->usedImportsResolver->resolveFunctionImportsForStmts($stmts);
|
||||
|
||||
$useImports = array_unique($useImports);
|
||||
$functionUseImports = array_unique($functionUseImports);
|
||||
|
||||
$useImports = array_diff($useImports, $existingUseImports);
|
||||
$functionUseImports = array_diff($functionUseImports, $existingFunctionUseImports);
|
||||
|
||||
$newUses = $this->createUses($useImports, $functionUseImports, null);
|
||||
|
||||
return array_merge($newUses, $stmts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $useImports
|
||||
* @param string[] $functionUseImports
|
||||
*/
|
||||
public function addImportsToNamespace(Namespace_ $namespace, array $useImports, array $functionUseImports): void
|
||||
{
|
||||
$namespaceName = $this->getNamespaceName($namespace);
|
||||
|
||||
$existingUseImports = $this->usedImportsResolver->resolveForNode($namespace);
|
||||
$existingFunctionUseImports = $this->usedImportsResolver->resolveFunctionImportsForNode($namespace);
|
||||
|
||||
$useImports = array_unique($useImports);
|
||||
$functionUseImports = array_unique($functionUseImports);
|
||||
|
||||
$useImports = array_diff($useImports, $existingUseImports);
|
||||
$functionUseImports = array_diff($functionUseImports, $existingFunctionUseImports);
|
||||
|
||||
$newUses = $this->createUses($useImports, $functionUseImports, $namespaceName);
|
||||
$namespace->stmts = array_merge($newUses, $namespace->stmts);
|
||||
}
|
||||
|
||||
private function getNamespaceName(Namespace_ $namespace): ?string
|
||||
{
|
||||
if ($namespace->name === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $namespace->name->toString();
|
||||
}
|
||||
|
||||
private function isCurrentNamespace(?string $namespaceName, string $useImports): bool
|
||||
{
|
||||
if ($namespaceName === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$afterCurrentNamespace = Strings::after($useImports, $namespaceName . '\\');
|
||||
if (! $afterCurrentNamespace) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ! Strings::contains($afterCurrentNamespace, '\\');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $useImports
|
||||
* @param string[] $functionUseImports
|
||||
* @return Use_[]
|
||||
*/
|
||||
private function createUses(array $useImports, array $functionUseImports, ?string $namespaceName): array
|
||||
{
|
||||
$newUses = [];
|
||||
|
||||
foreach ($useImports as $useImport) {
|
||||
if ($this->isCurrentNamespace($namespaceName, $useImport)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// already imported in previous cycle
|
||||
$useUse = new UseUse(new Name($useImport));
|
||||
$newUses[] = new Use_([$useUse]);
|
||||
}
|
||||
|
||||
foreach ($functionUseImports as $functionUseImport) {
|
||||
if ($this->isCurrentNamespace($namespaceName, $functionUseImport)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// already imported in previous cycle
|
||||
$useUse = new UseUse(new Name($functionUseImport), null, Use_::TYPE_FUNCTION);
|
||||
$newUses[] = new Use_([$useUse]);
|
||||
}
|
||||
return $newUses;
|
||||
}
|
||||
}
|
53
packages/CodingStyle/src/Imports/AliasUsesResolver.php
Normal file
53
packages/CodingStyle/src/Imports/AliasUsesResolver.php
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\CodingStyle\Imports;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt\Namespace_;
|
||||
use PhpParser\Node\Stmt\UseUse;
|
||||
|
||||
final class AliasUsesResolver
|
||||
{
|
||||
/**
|
||||
* @var UseImportsTraverser
|
||||
*/
|
||||
private $useImportsTraverser;
|
||||
|
||||
public function __construct(UseImportsTraverser $useImportsTraverser)
|
||||
{
|
||||
$this->useImportsTraverser = $useImportsTraverser;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function resolveForNode(Node $node): array
|
||||
{
|
||||
if ($node instanceof Namespace_) {
|
||||
return $this->resolveForNamespace($node);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function resolveForNamespace(Namespace_ $node): array
|
||||
{
|
||||
$aliasedUses = [];
|
||||
|
||||
$this->useImportsTraverser->traverserStmts($node->stmts, function (
|
||||
UseUse $useUse,
|
||||
string $name
|
||||
) use (&$aliasedUses): void {
|
||||
if ($useUse->alias === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$aliasedUses[] = $name;
|
||||
});
|
||||
|
||||
return $aliasedUses;
|
||||
}
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
<?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;
|
||||
}
|
||||
}
|
67
packages/CodingStyle/src/Imports/ShortNameResolver.php
Normal file
67
packages/CodingStyle/src/Imports/ShortNameResolver.php
Normal file
|
@ -0,0 +1,67 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\CodingStyle\Imports;
|
||||
|
||||
use Nette\Utils\Strings;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Stmt\Namespace_;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\PhpParser\NodeTraverser\CallableNodeTraverser;
|
||||
|
||||
final class ShortNameResolver
|
||||
{
|
||||
/**
|
||||
* @var CallableNodeTraverser
|
||||
*/
|
||||
private $callableNodeTraverser;
|
||||
|
||||
public function __construct(CallableNodeTraverser $callableNodeTraverser)
|
||||
{
|
||||
$this->callableNodeTraverser = $callableNodeTraverser;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function resolveForNode(Node $node): array
|
||||
{
|
||||
$namespace = $node->getAttribute(AttributeKey::NAMESPACE_NODE);
|
||||
|
||||
if ($namespace instanceof Namespace_) {
|
||||
return $this->resolveForNamespace($namespace);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function resolveForNamespace(Namespace_ $node): array
|
||||
{
|
||||
$shortNames = [];
|
||||
|
||||
$this->callableNodeTraverser->traverseNodesWithCallable($node->stmts, function (Node $node) use (
|
||||
&$shortNames
|
||||
): void {
|
||||
if (! $node instanceof Name) {
|
||||
return;
|
||||
}
|
||||
|
||||
$originalName = $node->getAttribute('originalName');
|
||||
if (! $originalName instanceof Name) {
|
||||
return;
|
||||
}
|
||||
|
||||
// already short
|
||||
if (Strings::contains($originalName->toString(), '\\')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$shortNames[$originalName->toString()] = $node->toString();
|
||||
});
|
||||
|
||||
return $shortNames;
|
||||
}
|
||||
}
|
50
packages/CodingStyle/src/Imports/UseImportsTraverser.php
Normal file
50
packages/CodingStyle/src/Imports/UseImportsTraverser.php
Normal file
|
@ -0,0 +1,50 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\CodingStyle\Imports;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt;
|
||||
use PhpParser\Node\Stmt\Use_;
|
||||
use Rector\PhpParser\Node\Resolver\NameResolver;
|
||||
use Rector\PhpParser\NodeTraverser\CallableNodeTraverser;
|
||||
|
||||
final class UseImportsTraverser
|
||||
{
|
||||
/**
|
||||
* @var NameResolver
|
||||
*/
|
||||
private $nameResolver;
|
||||
|
||||
/**
|
||||
* @var CallableNodeTraverser
|
||||
*/
|
||||
private $callableNodeTraverser;
|
||||
|
||||
public function __construct(NameResolver $nameResolver, CallableNodeTraverser $callableNodeTraverser)
|
||||
{
|
||||
$this->nameResolver = $nameResolver;
|
||||
$this->callableNodeTraverser = $callableNodeTraverser;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Stmt[] $stmts
|
||||
*/
|
||||
public function traverserStmts(array $stmts, callable $callable, int $type = Use_::TYPE_NORMAL): void
|
||||
{
|
||||
$this->callableNodeTraverser->traverseNodesWithCallable($stmts, function (Node $node) use ($callable, $type) {
|
||||
if (! $node instanceof Use_) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// only import uses
|
||||
if ($node->type !== $type) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($node->uses as $useUse) {
|
||||
$name = $this->nameResolver->resolve($useUse);
|
||||
$callable($useUse, $name);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
117
packages/CodingStyle/src/Imports/UsedImportsResolver.php
Normal file
117
packages/CodingStyle/src/Imports/UsedImportsResolver.php
Normal file
|
@ -0,0 +1,117 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\CodingStyle\Imports;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\Namespace_;
|
||||
use PhpParser\Node\Stmt\Use_;
|
||||
use PhpParser\Node\Stmt\UseUse;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\PhpParser\Node\BetterNodeFinder;
|
||||
use Rector\PhpParser\Node\Resolver\NameResolver;
|
||||
|
||||
final class UsedImportsResolver
|
||||
{
|
||||
/**
|
||||
* @var BetterNodeFinder
|
||||
*/
|
||||
private $betterNodeFinder;
|
||||
|
||||
/**
|
||||
* @var NameResolver
|
||||
*/
|
||||
private $nameResolver;
|
||||
|
||||
/**
|
||||
* @var UseImportsTraverser
|
||||
*/
|
||||
private $useImportsTraverser;
|
||||
|
||||
public function __construct(
|
||||
BetterNodeFinder $betterNodeFinder,
|
||||
NameResolver $nameResolver,
|
||||
UseImportsTraverser $useImportsTraverser
|
||||
) {
|
||||
$this->betterNodeFinder = $betterNodeFinder;
|
||||
$this->nameResolver = $nameResolver;
|
||||
$this->useImportsTraverser = $useImportsTraverser;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function resolveForNode(Node $node): array
|
||||
{
|
||||
$namespace = $node->getAttribute(AttributeKey::NAMESPACE_NODE);
|
||||
if ($namespace instanceof Namespace_) {
|
||||
return $this->resolveForNamespace($namespace);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Stmt[] $stmts
|
||||
* @return string[]
|
||||
*/
|
||||
public function resolveForStmts(array $stmts): array
|
||||
{
|
||||
$usedImports = [];
|
||||
|
||||
/** @var Class_|null $class */
|
||||
$class = $this->betterNodeFinder->findFirstInstanceOf($stmts, Class_::class);
|
||||
|
||||
// add class itself
|
||||
if ($class !== null) {
|
||||
$className = $this->nameResolver->resolve($class);
|
||||
if ($className !== null) {
|
||||
$usedImports[] = $className;
|
||||
}
|
||||
}
|
||||
|
||||
$this->useImportsTraverser->traverserStmts($stmts, function (
|
||||
UseUse $useUse,
|
||||
string $name
|
||||
) use (&$usedImports): void {
|
||||
$usedImports[] = $name;
|
||||
});
|
||||
|
||||
return $usedImports;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Stmt[] $stmts
|
||||
* @return string[]
|
||||
*/
|
||||
public function resolveFunctionImportsForStmts(array $stmts): array
|
||||
{
|
||||
$usedFunctionImports = [];
|
||||
|
||||
$this->useImportsTraverser->traverserStmts($stmts, function (
|
||||
UseUse $useUse,
|
||||
string $name
|
||||
) use (&$usedFunctionImports): void {
|
||||
$usedFunctionImports[] = $name;
|
||||
}, Use_::TYPE_FUNCTION);
|
||||
|
||||
return $usedFunctionImports;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function resolveFunctionImportsForNode(Namespace_ $namespace): array
|
||||
{
|
||||
return $this->resolveFunctionImportsForStmts($namespace->stmts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function resolveForNamespace(Namespace_ $node): array
|
||||
{
|
||||
return $this->resolveForStmts($node->stmts);
|
||||
}
|
||||
}
|
|
@ -6,41 +6,24 @@ use Nette\Utils\Strings;
|
|||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\FuncCall;
|
||||
use PhpParser\Node\Name;
|
||||
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\Application\UseAddingCommander;
|
||||
use Rector\CodingStyle\Imports\AliasUsesResolver;
|
||||
use Rector\CodingStyle\Imports\ShortNameResolver;
|
||||
use Rector\CodingStyle\Naming\ClassNaming;
|
||||
use Rector\Exception\ShouldNotHappenException;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator;
|
||||
use Rector\PhpParser\NodeTraverser\CallableNodeTraverser;
|
||||
use Rector\Rector\AbstractRector;
|
||||
use Rector\RectorDefinition\CodeSample;
|
||||
use Rector\RectorDefinition\RectorDefinition;
|
||||
|
||||
final class ImportFullyQualifiedNamesRector extends AbstractRector
|
||||
{
|
||||
/**
|
||||
* @var CallableNodeTraverser
|
||||
*/
|
||||
private $callableNodeTraverser;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $alreadyUsedShortNames = [];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $newUseStatements = [];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $newFunctionUseStatements = [];
|
||||
private $shortNames = [];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
|
@ -52,11 +35,6 @@ final class ImportFullyQualifiedNamesRector extends AbstractRector
|
|||
*/
|
||||
private $docBlockManipulator;
|
||||
|
||||
/**
|
||||
* @var ImportsInClassCollection
|
||||
*/
|
||||
private $importsInClassCollection;
|
||||
|
||||
/**
|
||||
* @var ClassNaming
|
||||
*/
|
||||
|
@ -67,18 +45,35 @@ final class ImportFullyQualifiedNamesRector extends AbstractRector
|
|||
*/
|
||||
private $shouldImportDocBlocks = true;
|
||||
|
||||
/**
|
||||
* @var AliasUsesResolver
|
||||
*/
|
||||
private $aliasUsesResolver;
|
||||
|
||||
/**
|
||||
* @var UseAddingCommander
|
||||
*/
|
||||
private $useAddingCommander;
|
||||
|
||||
/**
|
||||
* @var ShortNameResolver
|
||||
*/
|
||||
private $shortNameResolver;
|
||||
|
||||
public function __construct(
|
||||
CallableNodeTraverser $callableNodeTraverser,
|
||||
DocBlockManipulator $docBlockManipulator,
|
||||
ImportsInClassCollection $importsInClassCollection,
|
||||
ClassNaming $classNaming,
|
||||
AliasUsesResolver $aliasUsesResolver,
|
||||
UseAddingCommander $useAddingCommander,
|
||||
ShortNameResolver $shortNameResolver,
|
||||
bool $shouldImportDocBlocks = true
|
||||
) {
|
||||
$this->callableNodeTraverser = $callableNodeTraverser;
|
||||
$this->docBlockManipulator = $docBlockManipulator;
|
||||
$this->importsInClassCollection = $importsInClassCollection;
|
||||
$this->classNaming = $classNaming;
|
||||
$this->shouldImportDocBlocks = $shouldImportDocBlocks;
|
||||
$this->useAddingCommander = $useAddingCommander;
|
||||
$this->aliasUsesResolver = $aliasUsesResolver;
|
||||
$this->shortNameResolver = $shortNameResolver;
|
||||
}
|
||||
|
||||
public function getDefinition(): RectorDefinition
|
||||
|
@ -115,191 +110,98 @@ CODE_SAMPLE
|
|||
*/
|
||||
public function getNodeTypes(): array
|
||||
{
|
||||
return [Namespace_::class];
|
||||
return [Name::class, Node::class];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Namespace_ $node
|
||||
* @param Name|Node $node
|
||||
*/
|
||||
public function refactor(Node $node): ?Node
|
||||
{
|
||||
$this->resetCollectedNames();
|
||||
$this->useAddingCommander->analyseFileInfoUseStatements($node);
|
||||
|
||||
$this->resolveAlreadyImportedUses($node);
|
||||
if ($node instanceof Name) {
|
||||
if (! $this->canBeNameImported($node)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// "new X" or "X::static()"
|
||||
$this->resolveAlreadyUsedShortNames($node);
|
||||
$this->aliasedUses = $this->aliasUsesResolver->resolveForNode($node);
|
||||
|
||||
$newUseStatements = $this->importNamesAndCollectNewUseStatements($node);
|
||||
$this->addNewUseStatements($node, $newUseStatements);
|
||||
// "new X" or "X::static()"
|
||||
$this->shortNames = $this->shortNameResolver->resolveForNode($node);
|
||||
|
||||
return $node;
|
||||
return $this->importNamesAndCollectNewUseStatements($node);
|
||||
}
|
||||
|
||||
// process every doc block node
|
||||
if ($this->shouldImportDocBlocks) {
|
||||
$useImports = $this->docBlockManipulator->importNames($node);
|
||||
foreach ($useImports as $useImport) {
|
||||
$this->useAddingCommander->addUseImport($node, $useImport);
|
||||
}
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function resolveAlreadyImportedUses(Namespace_ $namespace): void
|
||||
private function importNamesAndCollectNewUseStatements(Name $name): ?Name
|
||||
{
|
||||
/** @var Class_|null $class */
|
||||
$class = $this->betterNodeFinder->findFirstInstanceOf($namespace->stmts, Class_::class);
|
||||
$originalName = $name->getAttribute('originalName');
|
||||
|
||||
// add class itself
|
||||
if ($class !== null) {
|
||||
$className = $this->getName($class);
|
||||
if ($className !== null) {
|
||||
$this->importsInClassCollection->addImport($className);
|
||||
}
|
||||
}
|
||||
|
||||
$this->callableNodeTraverser->traverseNodesWithCallable($namespace->stmts, function (Node $node) {
|
||||
if (! $node instanceof Use_) {
|
||||
if ($originalName instanceof Name) {
|
||||
// already short
|
||||
if (! Strings::contains($originalName->toString(), '\\')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// only import uses
|
||||
if ($node->type !== Use_::TYPE_NORMAL) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($node->uses as $useUse) {
|
||||
$name = $this->getName($useUse);
|
||||
if ($name === null) {
|
||||
throw new ShouldNotHappenException();
|
||||
}
|
||||
|
||||
if ($useUse->alias !== null) {
|
||||
// alias workaround
|
||||
$this->aliasedUses[] = $name;
|
||||
}
|
||||
|
||||
$this->importsInClassCollection->addImport($name);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $newUseStatements
|
||||
*/
|
||||
private function addNewUseStatements(Namespace_ $namespace, array $newUseStatements): void
|
||||
{
|
||||
if ($newUseStatements === [] && $this->newFunctionUseStatements === []) {
|
||||
return;
|
||||
} else {
|
||||
// not sure what to do
|
||||
return null;
|
||||
}
|
||||
|
||||
$newUses = [];
|
||||
$newUseStatements = array_unique($newUseStatements);
|
||||
|
||||
$namespaceName = $this->getName($namespace);
|
||||
if ($namespaceName === null) {
|
||||
throw new ShouldNotHappenException();
|
||||
// the short name is already used, skip it
|
||||
// @todo this is duplicated check of - $this->useAddingCommander->isShortImported?
|
||||
$shortName = $this->classNaming->getShortName($name->toString());
|
||||
if ($this->isShortNameAlreadyUsedForDifferentFqn($name, $shortName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($newUseStatements as $newUseStatement) {
|
||||
if ($this->isCurrentNamespace($namespaceName, $newUseStatement)) {
|
||||
continue;
|
||||
}
|
||||
$fullyQualifiedName = $this->getName($name);
|
||||
|
||||
// already imported in previous cycle
|
||||
$useUse = new UseUse(new Name($newUseStatement));
|
||||
$newUses[] = new Use_([$useUse]);
|
||||
|
||||
$this->importsInClassCollection->addImport($newUseStatement);
|
||||
// the similar end is already imported → skip
|
||||
if ($this->shouldSkipName($name, $fullyQualifiedName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($this->newFunctionUseStatements as $newFunctionUseStatement) {
|
||||
if ($this->isCurrentNamespace($namespaceName, $newFunctionUseStatement)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// already imported in previous cycle
|
||||
$useUse = new UseUse(new Name($newFunctionUseStatement), null, Use_::TYPE_FUNCTION);
|
||||
$newUses[] = new Use_([$useUse]);
|
||||
|
||||
$this->importsInClassCollection->addImport($newFunctionUseStatement);
|
||||
}
|
||||
|
||||
$namespace->stmts = array_merge($newUses, $namespace->stmts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function importNamesAndCollectNewUseStatements(Namespace_ $node): array
|
||||
{
|
||||
$this->newUseStatements = [];
|
||||
$this->newFunctionUseStatements = [];
|
||||
|
||||
$this->callableNodeTraverser->traverseNodesWithCallable($node->stmts, function (Node $node): ?Name {
|
||||
if (! $node instanceof Name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$name = $node->getAttribute('originalName');
|
||||
|
||||
if ($name instanceof Name) {
|
||||
// already short
|
||||
if (! Strings::contains($name->toString(), '\\')) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
// the short name is already used, skip it
|
||||
$shortName = $this->classNaming->getShortName($name->toString());
|
||||
if ($this->isShortNameAlreadyUsedForDifferentFqn($node, $shortName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($this->getName($node) === $node->toString()) {
|
||||
$fullyQualifiedName = $this->getName($node);
|
||||
|
||||
// the similar end is already imported → skip
|
||||
if ($this->shouldSkipName($fullyQualifiedName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$shortName = $this->classNaming->getShortName($fullyQualifiedName);
|
||||
if (isset($this->newUseStatements[$shortName]) || isset($this->newFunctionUseStatements[$shortName])) {
|
||||
if ($fullyQualifiedName === $this->newUseStatements[$shortName] || $fullyQualifiedName === $this->newFunctionUseStatements[$shortName]) {
|
||||
return new Name($shortName);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! $this->importsInClassCollection->hasImport($fullyQualifiedName)) {
|
||||
if ($node->getAttribute(AttributeKey::PARENT_NODE) instanceof FuncCall) {
|
||||
$this->newFunctionUseStatements[$shortName] = $fullyQualifiedName;
|
||||
} else {
|
||||
$this->newUseStatements[$shortName] = $fullyQualifiedName;
|
||||
}
|
||||
}
|
||||
|
||||
// possibly aliased
|
||||
if (in_array($fullyQualifiedName, $this->aliasedUses, true)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->importsInClassCollection->addImport($fullyQualifiedName);
|
||||
$shortName = $this->classNaming->getShortName($fullyQualifiedName);
|
||||
|
||||
if ($this->useAddingCommander->isShortImported($name, $fullyQualifiedName)) {
|
||||
if ($this->useAddingCommander->isImportShortable($name, $fullyQualifiedName)) {
|
||||
return new Name($shortName);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
if ($this->shouldImportDocBlocks) {
|
||||
// for doc blocks
|
||||
$this->callableNodeTraverser->traverseNodesWithCallable($node->stmts, function (Node $node): void {
|
||||
$importedDocUseStatements = $this->docBlockManipulator->importNames($node);
|
||||
$this->newUseStatements = array_merge($this->newUseStatements, $importedDocUseStatements);
|
||||
});
|
||||
}
|
||||
|
||||
return $this->newUseStatements;
|
||||
if (! $this->useAddingCommander->hasImport($name, $fullyQualifiedName)) {
|
||||
if ($name->getAttribute(AttributeKey::PARENT_NODE) instanceof FuncCall) {
|
||||
$this->useAddingCommander->addFunctionUseImport($name, $fullyQualifiedName);
|
||||
} else {
|
||||
$this->useAddingCommander->addUseImport($name, $fullyQualifiedName);
|
||||
}
|
||||
}
|
||||
|
||||
// possibly aliased
|
||||
if (in_array($fullyQualifiedName, $this->aliasedUses, true)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Name($shortName);
|
||||
}
|
||||
|
||||
// 1. name is fully qualified → import it
|
||||
private function shouldSkipName(string $fullyQualifiedName): bool
|
||||
private function shouldSkipName(Name $name, string $fullyQualifiedName): bool
|
||||
{
|
||||
// not namespaced class
|
||||
if (! Strings::contains($fullyQualifiedName, '\\')) {
|
||||
|
@ -313,60 +215,27 @@ CODE_SAMPLE
|
|||
return true;
|
||||
}
|
||||
|
||||
return $this->importsInClassCollection->canImportBeAdded($fullyQualifiedName);
|
||||
}
|
||||
|
||||
private function resolveAlreadyUsedShortNames(Namespace_ $namespace): void
|
||||
{
|
||||
if ($namespace->name instanceof Name) {
|
||||
$this->alreadyUsedShortNames[$namespace->name->toString()] = $namespace->name->toString();
|
||||
}
|
||||
|
||||
$this->callableNodeTraverser->traverseNodesWithCallable((array) $namespace->stmts, function (Node $node): void {
|
||||
if (! $node instanceof Name) {
|
||||
return;
|
||||
}
|
||||
|
||||
$name = $node->getAttribute('originalName');
|
||||
if (! $name instanceof Name) {
|
||||
return;
|
||||
}
|
||||
|
||||
// already short
|
||||
if (Strings::contains($name->toString(), '\\')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->alreadyUsedShortNames[$name->toString()] = $node->toString();
|
||||
});
|
||||
}
|
||||
|
||||
private function isCurrentNamespace(string $namespaceName, string $newUseStatement): bool
|
||||
{
|
||||
$afterCurrentNamespace = Strings::after($newUseStatement, $namespaceName . '\\');
|
||||
if (! $afterCurrentNamespace) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ! Strings::contains($afterCurrentNamespace, '\\');
|
||||
}
|
||||
|
||||
private function resetCollectedNames(): void
|
||||
{
|
||||
$this->newUseStatements = [];
|
||||
$this->newFunctionUseStatements = [];
|
||||
$this->alreadyUsedShortNames = [];
|
||||
$this->importsInClassCollection->reset();
|
||||
$this->docBlockManipulator->resetImportedNames();
|
||||
return $this->useAddingCommander->canImportBeAdded($name, $fullyQualifiedName);
|
||||
}
|
||||
|
||||
// is already used
|
||||
private function isShortNameAlreadyUsedForDifferentFqn(Name $name, string $shortName): bool
|
||||
{
|
||||
if (! isset($this->alreadyUsedShortNames[$shortName])) {
|
||||
if (! isset($this->shortNames[$shortName])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->alreadyUsedShortNames[$shortName] !== $this->getName($name);
|
||||
return $this->shortNames[$shortName] !== $this->getName($name);
|
||||
}
|
||||
|
||||
private function canBeNameImported(Name $name): bool
|
||||
{
|
||||
$parentNode = $name->getAttribute(AttributeKey::PARENT_NODE);
|
||||
|
||||
if ($parentNode instanceof Namespace_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ! $parentNode instanceof UseUse;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
use Foo\Bar;
|
||||
|
||||
function importFromThisFunctionToo() {
|
||||
$baz = new \Foo\Bar;
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
use Foo\Bar;
|
||||
|
||||
function importFromThisFunctionToo() {
|
||||
$baz = new Bar;
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
use function Safe\count;
|
||||
|
||||
class ImportFunctionWithoutNamespace
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
return \Safe\count([1]);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
use function Safe\count;
|
||||
|
||||
class ImportFunctionWithoutNamespace
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
return count([1]);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
function importFromThisFunction() {
|
||||
$baz = new \Foo\Bar;
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
use Foo\Bar;
|
||||
function importFromThisFunction() {
|
||||
$baz = new Bar;
|
||||
}
|
|
@ -7,7 +7,6 @@ use PhpParser\Node\Arg;
|
|||
|
||||
final class CallableThisArrayToAnonymousFunctionRector
|
||||
{
|
||||
|
||||
/**
|
||||
* @param Node\Param[] $params
|
||||
* @return Node\Arg[]
|
||||
|
@ -29,7 +28,6 @@ use PhpParser\Node\Arg;
|
|||
|
||||
final class CallableThisArrayToAnonymousFunctionRector
|
||||
{
|
||||
|
||||
/**
|
||||
* @param Param[] $params
|
||||
* @return Arg[]
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\CodingStyle\Tests\Rector\Namespace_\ImportFullyQualifiedNamesRector\Fixture;
|
||||
|
||||
function noClass()
|
||||
{
|
||||
return new \SomeAnother\AnotherClass;
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\CodingStyle\Tests\Rector\Namespace_\ImportFullyQualifiedNamesRector\Fixture;
|
||||
|
||||
use SomeAnother\AnotherClass;
|
||||
function noClass()
|
||||
{
|
||||
return new AnotherClass;
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,23 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\CodingStyle\Tests\Rector\Namespace_\ImportFullyQualifiedNamesRector;
|
||||
|
||||
use Rector\CodingStyle\Rector\Namespace_\ImportFullyQualifiedNamesRector;
|
||||
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
|
||||
|
||||
final class ImportFullyQualifiedNamesRectorNonNamespacedTest extends AbstractRectorTestCase
|
||||
{
|
||||
public function test(): void
|
||||
{
|
||||
$this->doTestFiles([
|
||||
__DIR__ . '/Fixture/NonNamespaced/simple.php.inc',
|
||||
__DIR__ . '/Fixture/NonNamespaced/already_imported.php.inc',
|
||||
__DIR__ . '/Fixture/NonNamespaced/function_import.php.inc',
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getRectorClass(): string
|
||||
{
|
||||
return ImportFullyQualifiedNamesRector::class;
|
||||
}
|
||||
}
|
|
@ -2,42 +2,53 @@
|
|||
|
||||
namespace Rector\CodingStyle\Tests\Rector\Namespace_\ImportFullyQualifiedNamesRector;
|
||||
|
||||
use Iterator;
|
||||
use Rector\CodingStyle\Rector\Namespace_\ImportFullyQualifiedNamesRector;
|
||||
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
|
||||
|
||||
final class ImportFullyQualifiedNamesRectorTest extends AbstractRectorTestCase
|
||||
{
|
||||
public function test(): void
|
||||
/**
|
||||
* @dataProvider provideNamespacedClasses()
|
||||
* @dataProvider provideFunctions()
|
||||
*/
|
||||
public function test(string $file): void
|
||||
{
|
||||
$this->doTestFiles([
|
||||
__DIR__ . '/Fixture/fixture.php.inc',
|
||||
__DIR__ . '/Fixture/double_import.php.inc',
|
||||
__DIR__ . '/Fixture/double_import_with_existing.php.inc',
|
||||
__DIR__ . '/Fixture/already_with_use.php.inc',
|
||||
__DIR__ . '/Fixture/already_class_name.php.inc',
|
||||
$this->doTestFile($file);
|
||||
}
|
||||
|
||||
// keep
|
||||
__DIR__ . '/Fixture/keep.php.inc',
|
||||
__DIR__ . '/Fixture/keep_same_end.php.inc',
|
||||
__DIR__ . '/Fixture/keep_trait_use.php.inc',
|
||||
public function provideFunctions(): Iterator
|
||||
{
|
||||
yield [__DIR__ . '/Fixture/import_function.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/import_function_no_class.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/import_return_doc.php.inc'];
|
||||
}
|
||||
|
||||
// php doc
|
||||
__DIR__ . '/Fixture/import_param_doc.php.inc',
|
||||
__DIR__ . '/Fixture/import_return_doc.php.inc',
|
||||
__DIR__ . '/Fixture/doc_combined.php.inc',
|
||||
__DIR__ . '/Fixture/conflicting_endings.php.inc',
|
||||
__DIR__ . '/Fixture/already_class_name_in_param_doc.php.inc',
|
||||
public function provideNamespacedClasses(): Iterator
|
||||
{
|
||||
yield [__DIR__ . '/Fixture/fixture.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/double_import.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/double_import_with_existing.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/already_with_use.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/already_class_name.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/no_class.php.inc'];
|
||||
|
||||
// buggy
|
||||
__DIR__ . '/Fixture/many_imports.php.inc',
|
||||
__DIR__ . '/Fixture/keep_static_method.php.inc',
|
||||
__DIR__ . '/Fixture/keep_various_request.php.inc',
|
||||
__DIR__ . '/Fixture/instance_of.php.inc',
|
||||
// keep
|
||||
yield [__DIR__ . '/Fixture/keep.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/keep_same_end.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/keep_trait_use.php.inc'];
|
||||
|
||||
// function
|
||||
__DIR__ . '/Fixture/import_function.php.inc',
|
||||
__DIR__ . '/Fixture/import_function_no_class.php.inc',
|
||||
]);
|
||||
// php doc
|
||||
yield [__DIR__ . '/Fixture/import_param_doc.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/doc_combined.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/conflicting_endings.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/already_class_name_in_param_doc.php.inc'];
|
||||
|
||||
// buggy
|
||||
yield [__DIR__ . '/Fixture/many_imports.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/keep_static_method.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/keep_various_request.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/instance_of.php.inc'];
|
||||
}
|
||||
|
||||
protected function getRectorClass(): string
|
||||
|
|
|
@ -5,6 +5,7 @@ namespace Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer;
|
|||
use Nette\Utils\Strings;
|
||||
use PhpParser\Comment\Doc;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Name;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\InvalidTagValueNode;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocChildNode;
|
||||
|
@ -32,7 +33,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\CodingStyle\Application\UseAddingCommander;
|
||||
use Rector\Exception\ShouldNotHappenException;
|
||||
use Rector\NodeTypeResolver\Exception\MissingTagException;
|
||||
use Rector\NodeTypeResolver\Php\ParamTypeInfo;
|
||||
|
@ -78,9 +79,9 @@ final class DocBlockManipulator
|
|||
private $importedNames = [];
|
||||
|
||||
/**
|
||||
* @var ImportsInClassCollection
|
||||
* @var UseAddingCommander
|
||||
*/
|
||||
private $importsInClassCollection;
|
||||
private $useAddingCommander;
|
||||
|
||||
public function __construct(
|
||||
PhpDocInfoFactory $phpDocInfoFactory,
|
||||
|
@ -89,7 +90,7 @@ final class DocBlockManipulator
|
|||
AttributeAwareNodeFactory $attributeAwareNodeFactory,
|
||||
StringsTypePhpDocNodeDecorator $stringsTypePhpDocNodeDecorator,
|
||||
NodeTraverser $nodeTraverser,
|
||||
ImportsInClassCollection $importsInClassCollection
|
||||
UseAddingCommander $useAddingCommander
|
||||
) {
|
||||
$this->phpDocInfoFactory = $phpDocInfoFactory;
|
||||
$this->phpDocInfoPrinter = $phpDocInfoPrinter;
|
||||
|
@ -97,7 +98,7 @@ final class DocBlockManipulator
|
|||
$this->attributeAwareNodeFactory = $attributeAwareNodeFactory;
|
||||
$this->stringsTypePhpDocNodeDecorator = $stringsTypePhpDocNodeDecorator;
|
||||
$this->nodeTraverser = $nodeTraverser;
|
||||
$this->importsInClassCollection = $importsInClassCollection;
|
||||
$this->useAddingCommander = $useAddingCommander;
|
||||
}
|
||||
|
||||
public function hasTag(Node $node, string $name): bool
|
||||
|
@ -411,22 +412,22 @@ final class DocBlockManipulator
|
|||
$phpDocNode = $phpDocInfo->getPhpDocNode();
|
||||
|
||||
$this->nodeTraverser->traverseWithCallable($phpDocNode, function (
|
||||
AttributeAwareNodeInterface $node
|
||||
): AttributeAwareNodeInterface {
|
||||
if (! $node instanceof IdentifierTypeNode) {
|
||||
return $node;
|
||||
AttributeAwareNodeInterface $docNode
|
||||
) use ($node): AttributeAwareNodeInterface {
|
||||
if (! $docNode instanceof IdentifierTypeNode) {
|
||||
return $docNode;
|
||||
}
|
||||
|
||||
// is class without namespaced name → skip
|
||||
$name = ltrim($node->name, '\\');
|
||||
$name = ltrim($docNode->name, '\\');
|
||||
if (! Strings::contains($name, '\\')) {
|
||||
return $node;
|
||||
return $docNode;
|
||||
}
|
||||
|
||||
$fullyQualifiedName = $this->getFullyQualifiedName($node);
|
||||
$fullyQualifiedName = $this->getFullyQualifiedName($docNode);
|
||||
$shortName = $this->getShortName($name);
|
||||
|
||||
return $this->processFqnNameImport($node, $shortName, $fullyQualifiedName);
|
||||
return $this->processFqnNameImport($node, $docNode, $shortName, $fullyQualifiedName);
|
||||
});
|
||||
|
||||
$this->updateNodeWithPhpDocInfo($node, $phpDocInfo);
|
||||
|
@ -620,26 +621,21 @@ final class DocBlockManipulator
|
|||
* @param AttributeAwareIdentifierTypeNode $attributeAwareNode
|
||||
*/
|
||||
private function processFqnNameImport(
|
||||
Node $node,
|
||||
AttributeAwareNodeInterface $attributeAwareNode,
|
||||
string $shortName,
|
||||
string $fullyQualifiedName
|
||||
): AttributeAwareNodeInterface {
|
||||
$alreadyImportedUses = array_merge($this->importedNames, $this->importsInClassCollection->get());
|
||||
$alreadyImportedUses = array_unique($alreadyImportedUses);
|
||||
|
||||
foreach ($alreadyImportedUses as $alreadyImportedUse) {
|
||||
$shortAlreadyImportedUsed = $this->getShortName($alreadyImportedUse);
|
||||
if ($shortAlreadyImportedUsed === $shortName) {
|
||||
if ($alreadyImportedUse === $fullyQualifiedName) {
|
||||
$attributeAwareNode->name = $shortName;
|
||||
}
|
||||
|
||||
return $attributeAwareNode;
|
||||
if ($this->useAddingCommander->isShortImported($node, $fullyQualifiedName)) {
|
||||
if ($this->useAddingCommander->isImportShortable($node, $fullyQualifiedName)) {
|
||||
$attributeAwareNode->name = $shortName;
|
||||
}
|
||||
|
||||
return $attributeAwareNode;
|
||||
}
|
||||
|
||||
$attributeAwareNode->name = $shortName;
|
||||
$this->importedNames[] = $fullyQualifiedName;
|
||||
$this->useAddingCommander->addUseImport($node, $fullyQualifiedName);
|
||||
|
||||
return $attributeAwareNode;
|
||||
}
|
||||
|
|
|
@ -86,7 +86,7 @@ CODE_SAMPLE
|
|||
|
||||
private function isIteratorToArrayFuncCall(Expr $expr): bool
|
||||
{
|
||||
return $expr instanceof Node\Expr\FuncCall && $this->isName($expr, 'iterator_to_array');
|
||||
return $expr instanceof FuncCall && $this->isName($expr, 'iterator_to_array');
|
||||
}
|
||||
|
||||
private function resolveValue(Expr $expr): Expr
|
||||
|
@ -100,7 +100,7 @@ CODE_SAMPLE
|
|||
return $expr;
|
||||
}
|
||||
|
||||
if (! $expr->cond instanceof Node\Expr\FuncCall) {
|
||||
if (! $expr->cond instanceof FuncCall) {
|
||||
return $expr;
|
||||
}
|
||||
|
||||
|
|
|
@ -165,3 +165,7 @@ parameters:
|
|||
- '#Class PhpParser\\Node\\Expr\\ArrayItem constructor invoked with 5 parameters, 1\-4 required#'
|
||||
|
||||
- '#Parameter \#2 \$name of method Rector\\Rector\\AbstractRector\:\:isName\(\) expects string, string\|null given#'
|
||||
# file always exists here
|
||||
- '#Cannot call method getRealPath\(\) on Symplify\\PackageBuilder\\FileSystem\\SmartFileInfo\|null#'
|
||||
# cascade irelevant
|
||||
- '#Parameter (.*?) expects array<PhpParser\\Node\\Stmt\>, array<PhpParser\\Node\> given#'
|
||||
|
|
|
@ -15,4 +15,4 @@ parameters:
|
|||
php_version_features: '7.1'
|
||||
|
||||
services:
|
||||
# Rector\CodingStyle\Rector\Namespace_\ImportFullyQualifiedNamesRector: ~
|
||||
Rector\CodingStyle\Rector\Namespace_\ImportFullyQualifiedNamesRector: ~
|
||||
|
|
|
@ -134,6 +134,10 @@ abstract class AbstractRector extends NodeVisitorAbstract implements PhpRectorIn
|
|||
$nodes = $this->nodeRemovingCommander->traverseNodes($nodes);
|
||||
}
|
||||
|
||||
if ($this->useAddingCommander->isActive()) {
|
||||
$nodes = $this->useAddingCommander->traverseNodes($nodes);
|
||||
}
|
||||
|
||||
$this->tearDown();
|
||||
|
||||
return $nodes;
|
||||
|
|
|
@ -5,6 +5,7 @@ namespace Rector\Rector;
|
|||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use Rector\Application\AppliedRectorCollector;
|
||||
use Rector\CodingStyle\Application\UseAddingCommander;
|
||||
use Rector\PhpParser\Node\Commander\NodeAddingCommander;
|
||||
use Rector\PhpParser\Node\Commander\NodeRemovingCommander;
|
||||
use Rector\PhpParser\Node\Commander\PropertyAddingCommander;
|
||||
|
@ -33,17 +34,24 @@ trait NodeCommandersTrait
|
|||
*/
|
||||
private $propertyAddingCommander;
|
||||
|
||||
/**
|
||||
* @var UseAddingCommander
|
||||
*/
|
||||
private $useAddingCommander;
|
||||
|
||||
/**
|
||||
* @required
|
||||
*/
|
||||
public function setRequiredCommanders(
|
||||
NodeRemovingCommander $nodeRemovingCommander,
|
||||
NodeAddingCommander $nodeAddingCommander,
|
||||
PropertyAddingCommander $propertyAddingCommander
|
||||
PropertyAddingCommander $propertyAddingCommander,
|
||||
UseAddingCommander $useAddingCommander
|
||||
): void {
|
||||
$this->nodeRemovingCommander = $nodeRemovingCommander;
|
||||
$this->nodeAddingCommander = $nodeAddingCommander;
|
||||
$this->propertyAddingCommander = $propertyAddingCommander;
|
||||
$this->useAddingCommander = $useAddingCommander;
|
||||
}
|
||||
|
||||
protected function addNodeAfterNode(Node $newNode, Node $positionNode): void
|
||||
|
@ -73,6 +81,16 @@ trait NodeCommandersTrait
|
|||
return $this->nodeRemovingCommander->isNodeRemoved($node);
|
||||
}
|
||||
|
||||
protected function addUseImport(Node $node, string $useImport): void
|
||||
{
|
||||
$this->useAddingCommander->addUseImport($node, $useImport);
|
||||
}
|
||||
|
||||
protected function addFunctionUseImport(Node $node, string $functionUseImport): void
|
||||
{
|
||||
$this->useAddingCommander->addFunctionUseImport($node, $functionUseImport);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Node[] $nodes
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue
Block a user