Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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: 2 additions & 0 deletions config/set/downgrade-php85.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
declare(strict_types=1);

use Rector\Config\RectorConfig;
use Rector\DowngradePhp85\Rector\Class_\DowngradeFinalPropertyPromotionRector;
use Rector\DowngradePhp85\Rector\FuncCall\DowngradeArrayFirstLastRector;
use Rector\Renaming\Rector\ClassConstFetch\RenameClassConstFetchRector;
use Rector\Renaming\Rector\MethodCall\RenameMethodRector;
Expand All @@ -14,6 +15,7 @@
$rectorConfig->phpVersion(PhpVersion::PHP_84);
$rectorConfig->rules([
DowngradeArrayFirstLastRector::class,
DowngradeFinalPropertyPromotionRector::class,
]);

// https://wiki.php.net/rfc/deprecations_php_8_5#deprecate_driver_specific_pdo_constants_and_methods
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Rector\Tests\DowngradePhp85\Rector\Class_\DowngradeFinalPropertyPromotionRector;

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

final class DowngradeFinalPropertyPromotionRectorTest 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,28 @@
<?php

namespace Rector\Tests\DowngradeFinalPropertyPromotionRector\Rector\Class_\DowngradePropertyPromotionRector\Fixture;

class Fixture
{
public function __construct(
final public string $id
) {}
}

?>
-----
<?php

namespace Rector\Tests\DowngradeFinalPropertyPromotionRector\Rector\Class_\DowngradePropertyPromotionRector\Fixture;

class Fixture
{
public function __construct(
/**
* @final
*/
public string $id
) {}
}

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@arshidkv12 oh, this is .php file, and skipped from the scan, should be .php.inc, I was wondering why it was working ...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

namespace Rector\Tests\DowngradeFinalPropertyPromotionRector\Rector\Class_\DowngradePropertyPromotionRector\Fixture;

class ImplicitFinalPropertyPromotion
{
public function __construct(final string $foo)
{
}
}
?>
-----
<?php
namespace Rector\Tests\DowngradeFinalPropertyPromotionRector\Rector\Class_\DowngradePropertyPromotionRector\Fixture;

class ImplicitFinalPropertyPromotion
{
public function __construct(
/**
* @final
*/
public string $foo
)
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace Rector\Tests\DowngradeFinalPropertyPromotionRector\Rector\Class_\DowngradePropertyPromotionRector\Fixture;

class SkipFixtureReadonly
{
public function __construct(readonly string $id)
{
}
}

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

declare(strict_types=1);

use Rector\Config\RectorConfig;
use Rector\DowngradePhp85\Rector\Class_\DowngradeFinalPropertyPromotionRector;

return static function (RectorConfig $rectorConfig): void {
$rectorConfig->rule(DowngradeFinalPropertyPromotionRector::class);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
<?php

declare(strict_types=1);

namespace Rector\DowngradePhp85\Rector\Class_;

use PhpParser\Builder\Property;
use PhpParser\Node;
use PhpParser\Node\Param;
use PhpParser\Node\Stmt\ClassMethod;
use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
use Rector\Comments\NodeDocBlock\DocBlockUpdater;
use Rector\Privatization\NodeManipulator\VisibilityManipulator;
use Rector\Rector\AbstractRector;
use Rector\ValueObject\MethodName;
use Rector\ValueObject\Visibility;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

/**
* @changelog https://wiki.php.net/rfc/final_promotion
*
* @see \Rector\Tests\DowngradePhp85\Rector\Class_\DowngradeFinalPropertyPromotionRector\DowngradeFinalPropertyPromotionRectorTest
*/
final class DowngradeFinalPropertyPromotionRector extends AbstractRector
{
/**
* @var string
*/
private const TAGNAME = 'final';

public function __construct(
private readonly VisibilityManipulator $visibilityManipulator,
private readonly DocBlockUpdater $docBlockUpdater,
private readonly PhpDocInfoFactory $phpDocInfoFactory,
) {
}

public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('Change constructor final property promotion to @final annotation assign', [
new CodeSample(
<<<'CODE_SAMPLE'
class SomeClass
{
public function __construct(
final public string $id
){}
}
CODE_SAMPLE

,
<<<'CODE_SAMPLE'
class SomeClass
{
public function __construct(
/** @final */
public string $id
) {}
}
CODE_SAMPLE
),
]);
}

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

/**
* @param ClassMethod $node
*/
public function refactor(Node $node): ?ClassMethod
{

Comment thread
samsonasik marked this conversation as resolved.
Outdated
if (! $this->isName($node, MethodName::CONSTRUCT)) {
return null;
}
Comment thread
samsonasik marked this conversation as resolved.

Comment thread
samsonasik marked this conversation as resolved.
foreach ($node->params as $param) {
if (! $param->isPromoted()) {
continue;
}

if (! $this->visibilityManipulator->hasVisibility($param, Visibility::FINAL)) {
continue;
}
Comment thread
samsonasik marked this conversation as resolved.

Comment thread
samsonasik marked this conversation as resolved.
$this->visibilityManipulator->makeNonFinal($param);

$this->addPhpDocTag($param);

}

Comment thread
samsonasik marked this conversation as resolved.
return null;
Comment thread
samsonasik marked this conversation as resolved.
}

private function addPhpDocTag(Property|Param $node): bool
{
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node);

if ($phpDocInfo->hasByName(self::TAGNAME)) {
return false;
}

$phpDocInfo->addPhpDocTagNode(new PhpDocTagNode('@' . self::TAGNAME, new GenericTagValueNode('')));
$this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($node);
return true;
}
}