Skip to content
Closed
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
35 changes: 35 additions & 0 deletions tests/Issues/RepeatedThreeTimes/Fixture/fixture.php.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace Rector\Tests\Issues\RepeatedThreeTimes\Fixture;

use Rector\Tests\Issues\RepeatedThreeTimes\Source\SomeArrayCollection;

final class Fixture
{
/**
* @param array|SomeArrayCollection $items
*/
public function run($items)
{
}
}

?>
-----
<?php

namespace Rector\Tests\Issues\RepeatedThreeTimes\Fixture;

use Rector\Tests\Issues\RepeatedThreeTimes\Source\SomeCollection;

final class Fixture
{
/**
* @param SomeCollection $items
*/
public function run(SomeCollection $items)
{
}
}

?>
34 changes: 34 additions & 0 deletions tests/Issues/RepeatedThreeTimes/RepeatedThreeTimesTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace Rector\Tests\Issues\RepeatedThreeTimes;

use Iterator;
use PHPUnit\Framework\Attributes\DataProvider;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;

/**
* Each rule depends on the output of the rule registered after it,
* so the file only reaches its final state on the 3rd pass
*
* @see https://github.com/rectorphp/rector-src/pull/8826
*/
final class RepeatedThreeTimesTest extends AbstractRectorTestCase
{
#[DataProvider('provideData')]
public function test(string $filePath): void
{
$this->doTestFile($filePath);
}

public static function provideData(): Iterator
{
return self::yieldFilesFromDirectory(__DIR__ . '/Fixture');
}

public function provideConfigFilePath(): string
{
return __DIR__ . '/config/configured_rule.php';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

declare(strict_types=1);

namespace Rector\Tests\Issues\RepeatedThreeTimes\Source;

use PhpParser\Node;
use PhpParser\Node\Stmt\ClassMethod;
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
use PHPStan\Type\ObjectType;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
use Rector\BetterPhpDocParser\ValueObject\Type\FullyQualifiedIdentifierTypeNode;
use Rector\Comments\NodeDocBlock\DocBlockUpdater;
use Rector\Rector\AbstractRector;
use Rector\RuleDoc\RuleDefinition;
use Rector\StaticTypeMapper\StaticTypeMapper;

/**
* @param array|SomeArrayCollection $items → @param array|SomeCollection $items
*/
final class NarrowArrayCollectionDocblockRector extends AbstractRector
{
public function __construct(
private readonly PhpDocInfoFactory $phpDocInfoFactory,
private readonly DocBlockUpdater $docBlockUpdater,
private readonly StaticTypeMapper $staticTypeMapper
) {
}

public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('', []);
}

/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
return [ClassMethod::class];
}

/**
* @param ClassMethod $node
*/
public function refactor(Node $node): ?ClassMethod
{
$phpDocInfo = $this->phpDocInfoFactory->createFromNode($node);
if ($phpDocInfo === null) {
return null;
}

$hasChanged = false;
foreach ($phpDocInfo->getParamTagValueNodes() as $paramTagValueNode) {
if (! $paramTagValueNode->type instanceof UnionTypeNode) {
continue;
}

foreach ($paramTagValueNode->type->types as $key => $unionedTypeNode) {
$unionedType = $this->staticTypeMapper->mapPHPStanPhpDocTypeNodeToPHPStanType($unionedTypeNode, $node);
if (! $unionedType->equals(new ObjectType(SomeArrayCollection::class))) {
continue;
}

$paramTagValueNode->type->types[$key] = new FullyQualifiedIdentifierTypeNode(SomeCollection::class);
$hasChanged = true;
}
}

if (! $hasChanged) {
return null;
}

$this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($node);

return $node;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?php

declare(strict_types=1);

namespace Rector\Tests\Issues\RepeatedThreeTimes\Source;

use PhpParser\Node;
use PhpParser\Node\Stmt\ClassMethod;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
use PHPStan\Type\ObjectType;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
use Rector\Comments\NodeDocBlock\DocBlockUpdater;
use Rector\Rector\AbstractRector;
use Rector\RuleDoc\RuleDefinition;
use Rector\StaticTypeMapper\StaticTypeMapper;

/**
* @param array|SomeCollection $items → @param SomeCollection $items
*/
final class NarrowParamUnionDocblockRector extends AbstractRector
{
public function __construct(
private readonly PhpDocInfoFactory $phpDocInfoFactory,
private readonly DocBlockUpdater $docBlockUpdater,
private readonly StaticTypeMapper $staticTypeMapper
) {
}

public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('', []);
}

/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
return [ClassMethod::class];
}

/**
* @param ClassMethod $node
*/
public function refactor(Node $node): ?ClassMethod
{
$phpDocInfo = $this->phpDocInfoFactory->createFromNode($node);
if ($phpDocInfo === null) {
return null;
}

$hasChanged = false;
foreach ($phpDocInfo->getParamTagValueNodes() as $paramTagValueNode) {
if (! $paramTagValueNode->type instanceof UnionTypeNode) {
continue;
}

if (count($paramTagValueNode->type->types) !== 2) {
continue;
}

[$firstTypeNode, $secondTypeNode] = $paramTagValueNode->type->types;
if (! $firstTypeNode instanceof IdentifierTypeNode || $firstTypeNode->name !== 'array') {
continue;
}

$secondType = $this->staticTypeMapper->mapPHPStanPhpDocTypeNodeToPHPStanType($secondTypeNode, $node);
if (! $secondType->equals(new ObjectType(SomeCollection::class))) {
continue;
}

$paramTagValueNode->type = $secondTypeNode;
$hasChanged = true;
}

if (! $hasChanged) {
return null;
}

$this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($node);

return $node;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

declare(strict_types=1);

namespace Rector\Tests\Issues\RepeatedThreeTimes\Source;

use PhpParser\Node;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Stmt\ClassMethod;
use PHPStan\Type\ObjectType;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
use Rector\Rector\AbstractRector;
use Rector\RuleDoc\RuleDefinition;

/**
* @param SomeCollection $items → native SomeCollection $items param type
*/
final class ParamTypeFromDocblockRector extends AbstractRector
{
public function __construct(
private readonly PhpDocInfoFactory $phpDocInfoFactory
) {
}

public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('', []);
}

/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
return [ClassMethod::class];
}

/**
* @param ClassMethod $node
*/
public function refactor(Node $node): ?ClassMethod
{
$phpDocInfo = $this->phpDocInfoFactory->createFromNode($node);
if ($phpDocInfo === null) {
return null;
}

$hasChanged = false;
foreach ($node->params as $param) {
if ($param->type instanceof Node) {
continue;
}

$paramType = $phpDocInfo->getParamType($this->getName($param));
if (! $paramType->equals(new ObjectType(SomeCollection::class))) {
continue;
}

$param->type = new FullyQualified(SomeCollection::class);
$hasChanged = true;
}

if (! $hasChanged) {
return null;
}

return $node;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace Rector\Tests\Issues\RepeatedThreeTimes\Source;

final class SomeArrayCollection implements SomeCollection
{
}
9 changes: 9 additions & 0 deletions tests/Issues/RepeatedThreeTimes/Source/SomeCollection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace Rector\Tests\Issues\RepeatedThreeTimes\Source;

interface SomeCollection
{
}
20 changes: 20 additions & 0 deletions tests/Issues/RepeatedThreeTimes/config/configured_rule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

use Rector\Config\RectorConfig;
use Rector\Tests\Issues\RepeatedThreeTimes\Source\NarrowArrayCollectionDocblockRector;
use Rector\Tests\Issues\RepeatedThreeTimes\Source\NarrowParamUnionDocblockRector;
use Rector\Tests\Issues\RepeatedThreeTimes\Source\ParamTypeFromDocblockRector;

return RectorConfig::configure()
->withImportNames()
// registered in reverse order of dependency:
// 3rd pass: native param type from @param SomeCollection
// 2nd pass: @param array|SomeCollection to @param SomeCollection
// 1st pass: @param array|SomeArrayCollection to @param array|SomeCollection
->withRules([
ParamTypeFromDocblockRector::class,
NarrowParamUnionDocblockRector::class,
NarrowArrayCollectionDocblockRector::class,
]);
Loading