[TypeDeclaration] Add AddPropertyTypeDeclaration rule (#1317)

This commit is contained in:
Tomas Votruba 2021-11-26 22:52:31 +03:00 committed by GitHub
parent 5a0947d9a2
commit 99a337285a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 353 additions and 39 deletions

View File

@ -1,4 +1,4 @@
# 498 Rules Overview
# 501 Rules Overview
<br>
@ -30,7 +30,7 @@
- [DowngradePhp56](#downgradephp56) (4)
- [DowngradePhp70](#downgradephp70) (12)
- [DowngradePhp70](#downgradephp70) (13)
- [DowngradePhp71](#downgradephp71) (10)
@ -98,9 +98,9 @@
- [Strict](#strict) (5)
- [Transform](#transform) (35)
- [Transform](#transform) (36)
- [TypeDeclaration](#typedeclaration) (21)
- [TypeDeclaration](#typedeclaration) (22)
- [Visibility](#visibility) (2)
@ -4300,6 +4300,19 @@ Remove anonymous class
<br>
### DowngradeClosureCallRector
Replace `Closure::call()` by `Closure::bindTo()`
- class: [`Rector\DowngradePhp70\Rector\MethodCall\DowngradeClosureCallRector`](../rules/DowngradePhp70/Rector/MethodCall/DowngradeClosureCallRector.php)
```diff
-$closure->call($newObj, ...$args);
+call_user_func($closure->bindTo($newObj, $newObj), ...$args);
```
<br>
### DowngradeDefineArrayConstantRector
Change array contant definition via define to const
@ -10167,6 +10180,21 @@ return static function (ContainerConfigurator $containerConfigurator): void {
## Transform
### AddAllowDynamicPropertiesAttributeRector
Add the `AllowDynamicProperties` attribute to all classes
- class: [`Rector\Transform\Rector\Class_\AddAllowDynamicPropertiesAttributeRector`](../rules/Transform/Rector/Class_/AddAllowDynamicPropertiesAttributeRector.php)
```diff
+#[AllowDynamicProperties]
class SomeObject {
public string $someProperty = 'hello world';
}
```
<br>
### AddInterfaceByParentRector
Add interface by parent
@ -11805,6 +11833,44 @@ return static function (ContainerConfigurator $containerConfigurator): void {
<br>
### AddPropertyTypeDeclarationRector
Add type to property by added rules, mostly public/property by parent type
:wrench: **configure it!**
- class: [`Rector\TypeDeclaration\Rector\Property\AddPropertyTypeDeclarationRector`](../rules/TypeDeclaration/Rector/Property/AddPropertyTypeDeclarationRector.php)
```php
use PHPStan\Type\StringType;
use Rector\TypeDeclaration\Rector\Property\AddPropertyTypeDeclarationRector;
use Rector\TypeDeclaration\ValueObject\AddPropertyTypeDeclaration;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symplify\SymfonyPhpConfig\ValueObjectInliner;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(AddPropertyTypeDeclarationRector::class)
->call(
'configure',
[[ValueObjectInliner::inline(new AddPropertyTypeDeclaration('ParentClass', 'name', new StringType()))]]
);
};
```
```diff
class SomeClass extends ParentClass
{
- public $name;
+ public string $name;
}
```
<br>
### AddReturnTypeDeclarationRector
Changes defined return typehint of method and class.

View File

@ -215,4 +215,9 @@ final class AttributeKey
* @var string
*/
public const STATEMENT_DEPTH = 'statementDepth';
/**
* @var string
*/
public const HAS_NEW_INHERITED_TYPE = 'has_new_inherited_type';
}

View File

@ -560,3 +560,6 @@ parameters:
-
path: rules/Php80/NodeManipulator/TokenManipulator.php
message: '#Property set "\$node\-\>expr" is overridden#'
# @todo fix in symplify
- '#Parameter \#3 \$configuration of class Symplify\\RuleDocGenerator\\ValueObject\\CodeSample\\ConfiguredCodeSample constructor expects array<string, mixed\>, array<int, Rector\\TypeDeclaration\\ValueObject\\AddPropertyTypeDeclaration\> given#'

View File

@ -10,26 +10,23 @@ use Rector\Tests\TypeDeclaration\Rector\ClassMethod\AddParamTypeDeclarationRecto
use Rector\TypeDeclaration\Rector\ClassMethod\AddParamTypeDeclarationRector;
use Rector\TypeDeclaration\ValueObject\AddParamTypeDeclaration;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symplify\SymfonyPhpConfig\ValueObjectInliner;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(AddParamTypeDeclarationRector::class)
->call('configure', [[
AddParamTypeDeclarationRector::PARAMETER_TYPEHINTS => ValueObjectInliner::inline([
new AddParamTypeDeclaration(
ParentInterfaceWithChangeTypeInterface::class,
'process',
0,
new StringType()
),
new AddParamTypeDeclaration(ParserInterface::class, 'parse', 0, new StringType()),
new AddParamTypeDeclaration(
ClassMetadataFactory::class,
'setEntityManager',
0,
new ObjectType('Doctrine\ORM\EntityManagerInterface')
),
]),
]]);
->configure([
new AddParamTypeDeclaration(
ParentInterfaceWithChangeTypeInterface::class,
'process',
0,
new StringType()
),
new AddParamTypeDeclaration(ParserInterface::class, 'parse', 0, new StringType()),
new AddParamTypeDeclaration(
ClassMetadataFactory::class,
'setEntityManager',
0,
new ObjectType('Doctrine\ORM\EntityManagerInterface')
),
]);
};

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\TypeDeclaration\Rector\Property\AddPropertyTypeDeclarationRector;
use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;
final class AddPropertyTypeDeclarationRectorTest 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,10 @@
<?php
namespace Rector\Tests\TypeDeclaration\Rector\Property\AddPropertyTypeDeclarationRector\Fixture;
use Rector\Tests\TypeDeclaration\Rector\Property\AddPropertyTypeDeclarationRector\Source\ParentClassWithProperty;
final class SkipAlreadyAddedType extends ParentClassWithProperty
{
public int $name;
}

View File

@ -0,0 +1,10 @@
<?php
namespace Rector\Tests\TypeDeclaration\Rector\Property\AddPropertyTypeDeclarationRector\Fixture;
use Rector\Tests\TypeDeclaration\Rector\Property\AddPropertyTypeDeclarationRector\Source\ParentClassWithProperty;
final class SkipDifferentPropertyName extends ParentClassWithProperty
{
public $surname;
}

View File

@ -0,0 +1,25 @@
<?php
namespace Rector\Tests\TypeDeclaration\Rector\Property\AddPropertyTypeDeclarationRector\Fixture;
use Rector\Tests\TypeDeclaration\Rector\Property\AddPropertyTypeDeclarationRector\Source\ParentClassWithProperty;
final class SomeClass extends ParentClassWithProperty
{
public $name;
}
?>
-----
<?php
namespace Rector\Tests\TypeDeclaration\Rector\Property\AddPropertyTypeDeclarationRector\Fixture;
use Rector\Tests\TypeDeclaration\Rector\Property\AddPropertyTypeDeclarationRector\Source\ParentClassWithProperty;
final class SomeClass extends ParentClassWithProperty
{
public string $name;
}
?>

View File

@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\TypeDeclaration\Rector\Property\AddPropertyTypeDeclarationRector\Source;
abstract class ParentClassWithProperty
{
}

View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
use PHPStan\Type\StringType;
use Rector\Tests\TypeDeclaration\Rector\Property\AddPropertyTypeDeclarationRector\Source\ParentClassWithProperty;
use Rector\TypeDeclaration\Rector\Property\AddPropertyTypeDeclarationRector;
use Rector\TypeDeclaration\ValueObject\AddPropertyTypeDeclaration;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(AddPropertyTypeDeclarationRector::class)
->configure([new AddPropertyTypeDeclaration(ParentClassWithProperty::class, 'name', new StringType())]);
};

View File

@ -109,11 +109,11 @@ CODE_SAMPLE
}
/**
* @param array<string, AddParamTypeDeclaration[]> $configuration
* @param array<string, AddParamTypeDeclaration[]>|AddParamTypeDeclaration[] $configuration
*/
public function configure(array $configuration): void
{
$parameterTypehints = $configuration[self::PARAMETER_TYPEHINTS] ?? [];
$parameterTypehints = $configuration[self::PARAMETER_TYPEHINTS] ?? $configuration ?: [];
Assert::allIsInstanceOf($parameterTypehints, AddParamTypeDeclaration::class);
$this->parameterTypehints = $parameterTypehints;

View File

@ -18,7 +18,6 @@ use Rector\PHPStanStaticTypeMapper\Enum\TypeKind;
use Rector\StaticTypeMapper\ValueObject\Type\NonExistingObjectType;
use Rector\TypeDeclaration\NodeTypeAnalyzer\TraitTypeAnalyzer;
use Rector\TypeDeclaration\TypeInferer\ParamTypeInferer;
use Rector\TypeDeclaration\ValueObject\NewType;
use Rector\VendorLocker\ParentClassMethodTypeOverrideGuard;
use Rector\VendorLocker\VendorLockResolver;
use Rector\VersionBonding\Contract\MinPhpVersionInterface;
@ -208,6 +207,6 @@ CODE_SAMPLE
}
// already set → skip
return ! $param->type->getAttribute(NewType::HAS_NEW_INHERITED_TYPE, false);
return ! $param->type->getAttribute(AttributeKey::HAS_NEW_INHERITED_TYPE, false);
}
}

View File

@ -0,0 +1,121 @@
<?php
declare(strict_types=1);
namespace Rector\TypeDeclaration\Rector\Property;
use PhpParser\Node;
use PhpParser\Node\Stmt\Property;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Type\StringType;
use Rector\Core\Contract\Rector\ConfigurableRectorInterface;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\Rector\AbstractRector;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\PHPStanStaticTypeMapper\Enum\TypeKind;
use Rector\TypeDeclaration\ValueObject\AddPropertyTypeDeclaration;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
use Webmozart\Assert\Assert;
/**
* @see \Rector\Tests\TypeDeclaration\Rector\Property\AddPropertyTypeDeclarationRector\AddPropertyTypeDeclarationRectorTest
*/
final class AddPropertyTypeDeclarationRector extends AbstractRector implements ConfigurableRectorInterface
{
/**
* @var AddPropertyTypeDeclaration[]
*/
private array $addPropertyTypeDeclarations = [];
public function getRuleDefinition(): RuleDefinition
{
$configuration = [new AddPropertyTypeDeclaration('ParentClass', 'name', new StringType())];
return new RuleDefinition('Add type to property by added rules, mostly public/property by parent type', [
new ConfiguredCodeSample(
<<<'CODE_SAMPLE'
class SomeClass extends ParentClass
{
public $name;
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
class SomeClass extends ParentClass
{
public string $name;
}
CODE_SAMPLE
,
$configuration
),
]);
}
/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
return [Property::class];
}
/**
* @param Property $node
*/
public function refactor(Node $node): ?Node
// this would be nice
// public function refactorWithScope(Node $node, Scope $scope): ?Node
{
// type is already known
if ($node->type !== null) {
return null;
}
$scope = $node->getAttribute(AttributeKey::SCOPE);
if (! $scope instanceof Scope) {
throw new ShouldNotHappenException();
}
$classReflection = $scope->getClassReflection();
if (! $classReflection instanceof ClassReflection) {
return null;
}
foreach ($this->addPropertyTypeDeclarations as $addPropertyTypeDeclaration) {
if (! $classReflection->isSubclassOf($addPropertyTypeDeclaration->getClass())) {
continue;
}
if (! $this->isName($node, $addPropertyTypeDeclaration->getPropertyName())) {
continue;
}
$typeNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode(
$addPropertyTypeDeclaration->getType(),
TypeKind::PROPERTY()
);
if ($typeNode === null) {
// invalid configuration
throw new ShouldNotHappenException();
}
$node->type = $typeNode;
return $node;
}
return null;
}
/**
* @param AddPropertyTypeDeclaration[] $configuration
*/
public function configure(array $configuration): void
{
Assert::allIsAOf($configuration, AddPropertyTypeDeclaration::class);
$this->addPropertyTypeDeclarations = $configuration;
}
}

View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Rector\TypeDeclaration\ValueObject;
use PHPStan\Type\Type;
final class AddPropertyTypeDeclaration
{
public function __construct(
private string $class,
private string $propertyName,
private Type $type
) {
}
public function getClass(): string
{
return $this->class;
}
public function getPropertyName(): string
{
return $this->propertyName;
}
public function getType(): Type
{
return $this->type;
}
}

View File

@ -1,13 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\TypeDeclaration\ValueObject;
final class NewType
{
/**
* @var string
*/
public const HAS_NEW_INHERITED_TYPE = 'has_new_inherited_type';
}