Skip to content
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
2 changes: 1 addition & 1 deletion docs/AllRectorsOverview.md
Original file line number Diff line number Diff line change
Expand Up @@ -2892,7 +2892,7 @@ Changes rand, srand and getrandmax by new md_* alternatives.

- class: `Rector\Php\Rector\FuncCall\PregReplaceEModifierRector`

The /e modifier is no longer supported, use preg_replace_callback instead
The /e modifier is no longer supported, use preg_replace_callback instead

```diff
class SomeClass
Expand Down
2 changes: 1 addition & 1 deletion docs/NodesOverview.md
Original file line number Diff line number Diff line change
Expand Up @@ -910,7 +910,7 @@ if (true) {

```php
?>
<strong>feel</strong><?php
<strong>feel</strong><?php
```
<br>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Rector\CodingStyle\Imports\ImportsInClassCollection;
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;
Expand All @@ -35,6 +36,11 @@ final class ImportFullyQualifiedNamesRector extends AbstractRector
*/
private $newUseStatements = [];

/**
* @var string[]
*/
private $newFunctionUseStatements = [];

/**
* @var string[]
*/
Expand Down Expand Up @@ -110,52 +116,45 @@ public function getNodeTypes(): array
public function refactor(Node $node): ?Node
{
$this->newUseStatements = [];
$this->newFunctionUseStatements = [];
$this->importsInClassCollection->reset();
$this->docBlockManipulator->resetImportedNames();

/** @var Class_|null $class */
$class = $this->betterNodeFinder->findFirstInstanceOf($node, Class_::class);
if ($class === null) {
return null;
}

$this->resolveAlreadyImportedUses($node);

// "new X" or "X::static()"
$this->resolveAlreadyUsedShortNames($node);

$newUseStatements = $this->importNamesAndCollectNewUseStatements($class);

$newUseStatements = $this->importNamesAndCollectNewUseStatements($node);
$this->addNewUseStatements($node, $newUseStatements);

return $node;
}

private function resolveAlreadyImportedUses(Namespace_ $namespace): void
{
/** @var Class_ $class */
/** @var Class_|null $class */
$class = $this->betterNodeFinder->findFirstInstanceOf($namespace->stmts, Class_::class);

// add class itself
$className = $this->getName($class);
if ($className === null) {
return;
if ($class) {
$className = $this->getName($class);
if ($className !== null) {
$this->importsInClassCollection->addImport($className);
}
}

$this->importsInClassCollection->addImport($className);

/** @var Use_[] $uses */
$uses = $this->betterNodeFinder->find($namespace->stmts, function (Node $node) {
$this->callableNodeTraverser->traverseNodesWithCallable($namespace->stmts, function (Node $node) {
if (! $node instanceof Use_) {
return false;
return null;
}

// only import uses
return $node->type === Use_::TYPE_NORMAL;
});
if ($node->type !== Use_::TYPE_NORMAL) {
return null;
}

foreach ($uses as $use) {
foreach ($use->uses as $useUse) {
foreach ($node->uses as $useUse) {
$name = $this->getName($useUse);
if ($name === null) {
throw new ShouldNotHappenException();
Expand All @@ -168,58 +167,62 @@ private function resolveAlreadyImportedUses(Namespace_ $namespace): void

$this->importsInClassCollection->addImport($name);
}
}

/** @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);
});
}

/**
* @param string[] $newUseStatements
*/
private function addNewUseStatements(Namespace_ $namespace, array $newUseStatements): void
{
if ($newUseStatements === []) {
if ($newUseStatements === [] && $this->newFunctionUseStatements === []) {
return;
}

$newUses = [];
$newUseStatements = array_unique($newUseStatements);

$namespaceName = $this->getName($namespace);
if ($namespaceName === null) {
throw new ShouldNotHappenException();
}

foreach ($newUseStatements as $newUseStatement) {
if ($this->isCurrentNamespace($namespaceName, $newUseStatement)) {
continue;
}

// already imported in previous cycle
$useUse = new UseUse(new Name($newUseStatement));
$newUses[] = new Use_([$useUse]);

$this->importsInClassCollection->addImport($newUseStatement);
}

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(Class_ $class): array
private function importNamesAndCollectNewUseStatements(Namespace_ $node): array
{
// probably anonymous class
if ($class->name === null) {
return [];
}

$this->newUseStatements = [];
$this->newFunctionUseStatements = [];

$classShortName = $class->name->toString();

$this->callableNodeTraverser->traverseNodesWithCallable([$class], function (Node $node) use ($classShortName) {
$this->callableNodeTraverser->traverseNodesWithCallable($node->stmts, function (Node $node) {
if (! $node instanceof Name) {
return null;
}
Expand All @@ -230,6 +233,8 @@ private function importNamesAndCollectNewUseStatements(Class_ $class): array
if (! Strings::contains($name->toString(), '\\')) {
return null;
}
} else {
return null;
}

// the short name is already used, skip it
Expand All @@ -240,11 +245,6 @@ private function importNamesAndCollectNewUseStatements(Class_ $class): array
return null;
}

// 0. name is same as class name → skip it
if ($node->getLast() === $classShortName) {
return null;
}

if ($this->getName($node) === $node->toString()) {
$fullyQualifiedName = $this->getName($node);

Expand All @@ -254,16 +254,20 @@ private function importNamesAndCollectNewUseStatements(Class_ $class): array
}

$shortName = $this->classNaming->getShortName($fullyQualifiedName);
if (isset($this->newUseStatements[$shortName])) {
if ($fullyQualifiedName === $this->newUseStatements[$shortName]) {
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)) {
$this->newUseStatements[$shortName] = $fullyQualifiedName;
if ($node->getAttribute(AttributeKey::PARENT_NODE) instanceof Node\Expr\FuncCall) {
$this->newFunctionUseStatements[$shortName] = $fullyQualifiedName;
} else {
$this->newUseStatements[$shortName] = $fullyQualifiedName;
}
}

// possibly aliased
Expand All @@ -278,7 +282,7 @@ private function importNamesAndCollectNewUseStatements(Class_ $class): array
});

// for doc blocks
$this->callableNodeTraverser->traverseNodesWithCallable([$class], function (Node $node): void {
$this->callableNodeTraverser->traverseNodesWithCallable($node->stmts, function (Node $node): void {
$importedDocUseStatements = $this->docBlockManipulator->importNames($node);
$this->newUseStatements = array_merge($this->newUseStatements, $importedDocUseStatements);
});
Expand Down Expand Up @@ -306,7 +310,11 @@ private function shouldSkipName(string $fullyQualifiedName): bool

private function resolveAlreadyUsedShortNames(Namespace_ $namespace): void
{
$this->callableNodeTraverser->traverseNodesWithCallable($namespace->stmts, function (Node $node): 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;
}
Expand All @@ -324,4 +332,14 @@ private function resolveAlreadyUsedShortNames(Namespace_ $namespace): void
$this->alreadyUsedShortNames[$name->toString()] = $node->toString();
});
}

private function isCurrentNamespace(string $namespaceName, string $newUseStatement): bool
{
$afterCurrentNamespace = Strings::after($newUseStatement, $namespaceName . '\\');
if ($afterCurrentNamespace === false) {
return false;
}

return ! Strings::contains($afterCurrentNamespace, '\\');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

namespace Rector\CodingStyle\Tests\Rector\Namespace_\ImportFullyQualifiedNamesRector\Fixture;

class ImportFunction
{
public function run()
{
return \Safe\count([1]);
}
}

function someFunctionWithNoEffect()
{
}

\Rector\CodingStyle\Tests\Rector\Namespace_\ImportFullyQualifiedNamesRector\Fixture\someFunctionWithNoEffect();

?>
-----
<?php

namespace Rector\CodingStyle\Tests\Rector\Namespace_\ImportFullyQualifiedNamesRector\Fixture;

use function Safe\count;
class ImportFunction
{
public function run()
{
return count([1]);
}
}

function someFunctionWithNoEffect()
{
}

someFunctionWithNoEffect();

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace Rector\CodingStyle\Tests\Rector\Namespace_\ImportFullyQualifiedNamesRector\Fixture;

function someOtherFunctionWithNoEffect()
{
}

\Rector\CodingStyle\Tests\Rector\Namespace_\ImportFullyQualifiedNamesRector\Fixture\someOtherFunctionWithNoEffect();

?>
-----
<?php

namespace Rector\CodingStyle\Tests\Rector\Namespace_\ImportFullyQualifiedNamesRector\Fixture;

function someOtherFunctionWithNoEffect()
{
}

someOtherFunctionWithNoEffect();

?>
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ public function test(): void
__DIR__ . '/Fixture/many_imports.php.inc',
__DIR__ . '/Fixture/keep_static_method.php.inc',
__DIR__ . '/Fixture/keep_various_request.php.inc',

// function
__DIR__ . '/Fixture/import_function.php.inc',
__DIR__ . '/Fixture/import_function_no_class.php.inc',
]);
}

Expand Down