[Transform][Php81] Add configurable ReturnTypeWillChangeRector (#1548)

Co-authored-by: GitHub Action <action@github.com>
This commit is contained in:
Abdul Malik Ikhsan 2021-12-27 22:21:46 +07:00 committed by GitHub
parent 5af50aff13
commit 35ef77b4f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 338 additions and 3 deletions

View File

@ -33,12 +33,12 @@ final class PhpDocFromTypeDeclarationDecorator
/**
* @var class-string<ReturnTypeWillChange>
*/
private const RETURN_TYPE_WILL_CHANGE_ATTRIBUTE = 'ReturnTypeWillChange';
final public const RETURN_TYPE_WILL_CHANGE_ATTRIBUTE = 'ReturnTypeWillChange';
/**
* @var array<string, array<string, string[]>>
*/
private const ADD_RETURN_TYPE_WILL_CHANGE = [
final public const ADD_RETURN_TYPE_WILL_CHANGE = [
'PHPStan\Type\MixedType' => [
'ArrayAccess' => ['offsetGet'],
],

View File

@ -120,7 +120,9 @@ parameters:
-
message: '#Nested foreach with empty statement is not allowed#'
path: packages-tests/BetterPhpDocParser/PhpDocParser/TagValueNodeReprint/TagValueNodeReprintTest.php
paths:
- packages-tests/BetterPhpDocParser/PhpDocParser/TagValueNodeReprint/TagValueNodeReprintTest.php
- rules/Transform/Rector/ClassMethod/ReturnTypeWillChangeRector.php
-
message: '#Function "dump\(\)" cannot be used/left in the code#'

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\Transform\Rector\ClassMethod\ReturnTypeWillChangeRector;
use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;
final class CustomConfigTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(SmartFileInfo $fileInfo): void
{
$this->doTestFileInfo($fileInfo);
}
/**
* @return Iterator<SmartFileInfo>
*/
public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/FixtureCustomConfig');
}
public function provideConfigFilePath(): string
{
return __DIR__ . '/config/custom_config.php';
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace Rector\Tests\Transform\Rector\ClassMethod\ReturnTypeWillChangeRector\Fixture;
use Rector\Tests\Transform\Rector\ClassMethod\ReturnTypeWillChangeRector\Source\Options;
class SomeClass extends Options
{
public function offsetGet($offset)
{
}
}
?>
-----
<?php
namespace Rector\Tests\Transform\Rector\ClassMethod\ReturnTypeWillChangeRector\Fixture;
use Rector\Tests\Transform\Rector\ClassMethod\ReturnTypeWillChangeRector\Source\Options;
class SomeClass extends Options
{
#[\ReturnTypeWillChange]
public function offsetGet($offset)
{
}
}
?>

View File

@ -0,0 +1,39 @@
<?php
namespace Rector\Tests\Transform\Rector\ClassMethod\ReturnTypeWillChangeRector\FixtureCustomConfig;
use Rector\Tests\Transform\Rector\ClassMethod\ReturnTypeWillChangeRector\Source\Options;
class SomeClass extends Options
{
public function offsetGet($offset)
{
}
public function offsetExists($offset)
{
}
}
?>
-----
<?php
namespace Rector\Tests\Transform\Rector\ClassMethod\ReturnTypeWillChangeRector\FixtureCustomConfig;
use Rector\Tests\Transform\Rector\ClassMethod\ReturnTypeWillChangeRector\Source\Options;
class SomeClass extends Options
{
#[\ReturnTypeWillChange]
public function offsetGet($offset)
{
}
#[\ReturnTypeWillChange]
public function offsetExists($offset)
{
}
}
?>

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\Transform\Rector\ClassMethod\ReturnTypeWillChangeRector;
use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;
final class ReturnTypeWillChangeRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(SmartFileInfo $fileInfo): void
{
$this->doTestFileInfo($fileInfo);
}
/**
* @return Iterator<SmartFileInfo>
*/
public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}
public function provideConfigFilePath(): string
{
return __DIR__ . '/config/configured_rule.php';
}
}

View File

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\Transform\Rector\ClassMethod\ReturnTypeWillChangeRector\Source;
use ArrayAccess;
abstract class Options implements ArrayAccess
{
}

View File

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
use Rector\Transform\Rector\ClassMethod\ReturnTypeWillChangeRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(ReturnTypeWillChangeRector::class);
};

View File

@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
use Rector\Transform\Rector\ClassMethod\ReturnTypeWillChangeRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(ReturnTypeWillChangeRector::class)
->configure([
'ArrayAccess' => ['offsetExists'],
]);
};

View File

@ -0,0 +1,156 @@
<?php
declare(strict_types=1);
namespace Rector\Transform\Rector\ClassMethod;
use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassLike;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Interface_;
use PHPStan\Type\ObjectType;
use Rector\BetterPhpDocParser\PhpDocParser\PhpDocFromTypeDeclarationDecorator;
use Rector\Core\Contract\Rector\AllowEmptyConfigurableRectorInterface;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\ValueObject\PhpVersionFeature;
use Rector\Php80\NodeAnalyzer\PhpAttributeAnalyzer;
use Rector\PhpAttribute\Printer\PhpAttributeGroupFactory;
use Rector\VersionBonding\Contract\MinPhpVersionInterface;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* @see \Rector\Tests\Transform\Rector\ClassMethod\ReturnTypeWillChangeRector\ReturnTypeWillChangeRectorTest
*/
final class ReturnTypeWillChangeRector extends AbstractRector implements AllowEmptyConfigurableRectorInterface, MinPhpVersionInterface
{
/**
* @var array<string, string[]>
*/
private array $classMethodsOfClass = [];
public function __construct(
private readonly PhpAttributeAnalyzer $phpAttributeAnalyzer,
private readonly PhpAttributeGroupFactory $phpAttributeGroupFactory
) {
}
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('Add #[\ReturnTypeWillChange] attribute to configured instanceof class with methods', [
new ConfiguredCodeSample(
<<<'CODE_SAMPLE'
class SomeClass implements ArrayAccess
{
public function offsetGet($offset)
{
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
class SomeClass implements ArrayAccess
{
#[\ReturnTypeWillChange]
public function offsetGet($offset)
{
}
}
CODE_SAMPLE
,
[
'ArrayAccess' => ['offsetGet'],
]
),
]);
}
/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
return [ClassMethod::class];
}
/**
* @param ClassMethod $node
*/
public function refactor(Node $node): ?Node
{
if ($this->phpAttributeAnalyzer->hasPhpAttribute($node, 'ReturnTypeWillChange')) {
return null;
}
if ($node->returnType !== null) {
return null;
}
$classLike = $this->betterNodeFinder->findParentByTypes($node, [Class_::class, Interface_::class]);
if (! $classLike instanceof ClassLike) {
return null;
}
/** @var array<string, string[]> $classMethodsOfClass */
$classMethodsOfClass = array_merge_recursive($this->resolveDefaultConfig(), $this->classMethodsOfClass);
$className = (string) $this->nodeNameResolver->getName($classLike);
$objectType = new ObjectType($className);
$methodName = $this->nodeNameResolver->getName($node);
$hasChanged = false;
foreach ($classMethodsOfClass as $class => $methods) {
$configuredClassObjectType = new ObjectType($class);
if (! $configuredClassObjectType->isSuperTypeOf($objectType)->yes()) {
continue;
}
if (! in_array($methodName, $methods, true)) {
continue;
}
$attributeGroup = $this->phpAttributeGroupFactory->createFromClass(
PhpDocFromTypeDeclarationDecorator::RETURN_TYPE_WILL_CHANGE_ATTRIBUTE
);
$node->attrGroups[] = $attributeGroup;
$hasChanged = true;
break;
}
if (! $hasChanged) {
return null;
}
return $node;
}
/**
* @param mixed[] $configuration
*/
public function configure(array $configuration): void
{
$this->classMethodsOfClass = $configuration;
}
public function provideMinPhpVersion(): int
{
return PhpVersionFeature::RETURN_TYPE_WILL_CHANGE_ATTRIBUTE;
}
/**
* @return array<string, string[]>
*/
private function resolveDefaultConfig(): array
{
$configuration = [];
foreach (PhpDocFromTypeDeclarationDecorator::ADD_RETURN_TYPE_WILL_CHANGE as $classWithMethods) {
foreach ($classWithMethods as $class => $methods) {
$configuration[$class] = array_merge($configuration[$class] ?? [], $methods);
}
}
return $configuration;
}
}

View File

@ -555,6 +555,12 @@ final class PhpVersionFeature
*/
final public const ARRAY_SPREAD_STRING_KEYS = PhpVersion::PHP_81;
/**
* @see https://wiki.php.net/rfc/internal_method_return_types
* @var int
*/
final public const RETURN_TYPE_WILL_CHANGE_ATTRIBUTE = PhpVersion::PHP_81;
/**
* @see https://wiki.php.net/rfc/deprecate_dynamic_properties
* @var int