[DoctrineCodeQuality] Add ImproveDoctrineCollectionDocTypeInEntityRector (#4442)

Co-authored-by: rector-bot <tomas@getrector.org>
This commit is contained in:
Tomas Votruba 2020-10-18 18:29:35 +02:00 committed by GitHub
parent 055040f8e8
commit e845d7cea8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 801 additions and 74 deletions

View File

@ -12,11 +12,6 @@ jobs:
fail-fast: false
matrix:
actions:
-
name: Rector CI
install: composer install --no-progress --ansi
run: composer rector-ci
-
name: Rector without Dev Dependencies
install: composer install --no-progress --ansi --no-dev

View File

@ -9,6 +9,7 @@ use Rector\DoctrineCodeQuality\Rector\ClassMethod\MakeEntityDateTimePropertyDate
use Rector\DoctrineCodeQuality\Rector\ClassMethod\MakeEntitySetterNullabilityInSyncWithPropertyRector;
use Rector\DoctrineCodeQuality\Rector\Property\ChangeBigIntEntityPropertyToIntTypeRector;
use Rector\DoctrineCodeQuality\Rector\Property\CorrectDefaultTypesOnEntityPropertyRector;
use Rector\DoctrineCodeQuality\Rector\Property\ImproveDoctrineCollectionDocTypeInEntityRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
@ -20,4 +21,5 @@ return static function (ContainerConfigurator $containerConfigurator): void {
$services->set(MoveCurrentDateTimeDefaultInEntityToConstructorRector::class);
$services->set(CorrectDefaultTypesOnEntityPropertyRector::class);
$services->set(ChangeBigIntEntityPropertyToIntTypeRector::class);
$services->set(ImproveDoctrineCollectionDocTypeInEntityRector::class);
};

View File

@ -1,4 +1,4 @@
# All 593 Rectors Overview
# All 595 Rectors Overview
- [Projects](#projects)
---
@ -10,11 +10,11 @@
- [CakePHP](#cakephp) (6)
- [CodeQuality](#codequality) (59)
- [CodingStyle](#codingstyle) (33)
- [DeadCode](#deadcode) (40)
- [DeadCode](#deadcode) (41)
- [Decouple](#decouple) (1)
- [Defluent](#defluent) (8)
- [Doctrine](#doctrine) (17)
- [DoctrineCodeQuality](#doctrinecodequality) (8)
- [DoctrineCodeQuality](#doctrinecodequality) (9)
- [DoctrineGedmoToKnplabs](#doctrinegedmotoknplabs) (7)
- [DowngradePhp71](#downgradephp71) (3)
- [DowngradePhp72](#downgradephp72) (2)
@ -2983,6 +2983,28 @@ Remove empty method calls not required by parents
<br><br>
### `RemoveEmptyMethodCallRector`
- class: [`Rector\DeadCode\Rector\MethodCall\RemoveEmptyMethodCallRector`](/rules/dead-code/src/Rector/MethodCall/RemoveEmptyMethodCallRector.php)
- [test fixtures](/rules/dead-code/tests/Rector/MethodCall/RemoveEmptyMethodCallRector/Fixture)
Remove empty method call
```diff
class SomeClass
{
public function callThis()
{
}
}
-$some = new SomeClass();
-$some->callThis();
+$some = new SomeClass();
```
<br><br>
### `RemoveNullPropertyInitializationRector`
- class: [`Rector\DeadCode\Rector\PropertyProperty\RemoveNullPropertyInitializationRector`](/rules/dead-code/src/Rector/PropertyProperty/RemoveNullPropertyInitializationRector.php)
@ -4287,6 +4309,33 @@ Change default value types to match Doctrine annotation type
<br><br>
### `ImproveDoctrineCollectionDocTypeInEntityRector`
- class: [`Rector\DoctrineCodeQuality\Rector\Property\ImproveDoctrineCollectionDocTypeInEntityRector`](/rules/doctrine-code-quality/src/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector.php)
- [test fixtures](/rules/doctrine-code-quality/tests/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector/Fixture)
Improve @var, @param and @return types for Doctrine collections to make them useful both for PHPStan and PHPStorm
```diff
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class SomeClass
{
/**
* @ORM\OneToMany(targetEntity=Training::class, mappedBy="trainer")
- * @var Collection|Trainer[]
+ * @var Collection<int, Training>|Trainer[]
*/
private $trainings = [];
}
```
<br><br>
### `InitializeDefaultEntityCollectionRector`
- class: [`Rector\DoctrineCodeQuality\Rector\Class_\InitializeDefaultEntityCollectionRector`](/rules/doctrine-code-quality/src/Rector/Class_/InitializeDefaultEntityCollectionRector.php)
@ -5034,13 +5083,17 @@ Convert the list reference assignment to its equivalent PHP 7.2 code
- $array = [1, 2, 3];
- list($a, &$b) = $array;
+ $array = [1, 2];
+ list($a, $b) = $array;
+ list($a) = $array;
+ $b =& $array[1];
- [&$c, $d, &$e] = $array;
+ [$c, $d, $e] = $array;
+ $c =& $array[0];
+ $e =& $array[2];
- list(&$a, &$b) = $array;
+ $a =& $array[0];
+ $b =& $array[1];
}
}
```
@ -5082,8 +5135,7 @@ Add missing param to `array_merge` and `array_merge_recursive`
```diff
class SomeClass
{
- public function run()
+ public function run()
public function run()
{
- array_merge();
- array_merge_recursive();

View File

@ -13,6 +13,7 @@ use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\MixedType;
use PHPStan\Type\Type;
use Rector\AttributeAwarePhpDoc\Ast\PhpDoc\AttributeAwareParamTagValueNode;
@ -33,6 +34,7 @@ use Rector\PhpAttribute\Contract\PhpAttributableTagNodeInterface;
use Rector\PHPStan\Type\FullyQualifiedObjectType;
use Rector\PHPStan\Type\ShortenedObjectType;
use Rector\StaticTypeMapper\StaticTypeMapper;
use Webmozart\Assert\Assert;
/**
* @see \Rector\BetterPhpDocParser\Tests\PhpDocInfo\PhpDocInfo\PhpDocInfoTest
@ -152,7 +154,7 @@ final class PhpDocInfo
return count($this->tokens);
}
public function getVarTagValue(): ?VarTagValueNode
public function getVarTagValueNode(): ?VarTagValueNode
{
return $this->phpDocNode->getVarTagValues()[0] ?? null;
}
@ -212,7 +214,7 @@ final class PhpDocInfo
public function getVarType(): Type
{
return $this->getTypeOrMixed($this->getVarTagValue());
return $this->getTypeOrMixed($this->getVarTagValueNode());
}
public function getReturnType(): Type
@ -370,8 +372,13 @@ final class PhpDocInfo
return $this->tokens === [];
}
public function changeParamType(Type $type, Param $param, string $paramName): void
/**
* @param Type|TypeNode $type
*/
public function changeParamType($type, Param $param, string $paramName): void
{
Assert::isAnyOf($type, [Type::class, TypeNode::class]);
$this->phpDocTypeChanger->changeParamType($this, $type, $param, $paramName);
}

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Rector\BetterPhpDocParser\PhpDocManipulator;
use PhpParser\Node\Param;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\MixedType;
use PHPStan\Type\NeverType;
@ -18,6 +19,7 @@ use Rector\Core\Exception\ShouldNotHappenException;
use Rector\NodeTypeResolver\PHPStan\TypeComparator;
use Rector\StaticTypeMapper\StaticTypeMapper;
use Rector\TypeDeclaration\PhpDocParser\ParamPhpDocNodeFactory;
use Webmozart\Assert\Assert;
final class PhpDocTypeChanger
{
@ -75,7 +77,7 @@ final class PhpDocTypeChanger
// override existing type
$newPHPStanPhpDocType = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($newType);
$currentVarTagValueNode = $phpDocInfo->getVarTagValue();
$currentVarTagValueNode = $phpDocInfo->getVarTagValueNode();
if ($currentVarTagValueNode !== null) {
// only change type
$currentVarTagValueNode->type = $newPHPStanPhpDocType;
@ -113,17 +115,26 @@ final class PhpDocTypeChanger
$this->notifyChange();
}
public function changeParamType(PhpDocInfo $phpDocInfo, Type $type, Param $param, string $paramName): void
/**
* @param Type|TypeNode $type
*/
public function changeParamType(PhpDocInfo $phpDocInfo, $type, Param $param, string $paramName): void
{
$paramTagValueNode = $phpDocInfo->getParamTagValueByName($paramName);
Assert::isAnyOf($type, [Type::class, TypeNode::class]);
$phpDocType = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($type);
if (! $type instanceof TypeNode) {
$phpDocType = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($type);
} else {
$phpDocType = $type;
}
$paramTagValueNode = $phpDocInfo->getParamTagValueByName($paramName);
// override existing type
if ($paramTagValueNode !== null) {
$paramTagValueNode->type = $phpDocType;
} else {
$paramTagValueNode = $this->paramPhpDocNodeFactory->create($type, $param);
$paramTagValueNode = $this->paramPhpDocNodeFactory->create($phpDocType, $param);
$phpDocInfo->addTagValueNode($paramTagValueNode);
}

View File

@ -35,7 +35,7 @@ final class VarAnnotationManipulator
$phpDocInfo = $this->resolvePhpDocInfo($node);
// already done
if ($phpDocInfo->getVarTagValue() !== null) {
if ($phpDocInfo->getVarTagValueNode() !== null) {
return;
}

View File

@ -71,7 +71,7 @@ final class ReadWritePropertyAnalyzer
return $this->isArrayDimFetchRead($parent);
}
return ! $this->assignManipulator->isNodeLeftPartOfAssign($node);
return ! $this->assignManipulator->isLeftPartOfAssign($node);
}
private function unwrapPostPreIncDec(Node $node): Node
@ -93,7 +93,7 @@ final class ReadWritePropertyAnalyzer
throw new MissingParentNodeException();
}
if (! $this->assignManipulator->isNodeLeftPartOfAssign($arrayDimFetch)) {
if (! $this->assignManipulator->isLeftPartOfAssign($arrayDimFetch)) {
return false;
}
@ -102,6 +102,6 @@ final class ReadWritePropertyAnalyzer
return true;
}
return ! $this->assignManipulator->isNodeLeftPartOfAssign($arrayDimFetch);
return ! $this->assignManipulator->isLeftPartOfAssign($arrayDimFetch);
}
}

View File

@ -209,6 +209,15 @@ final class RectorRecipe
*/
public function setPackage(string $package): void
{
if (is_file($package)) {
$message = sprintf(
'The "%s()" method only accepts package name, file path "%s" given',
__METHOD__,
$package
);
throw new ShouldNotHappenException($message);
}
$this->package = $package;
}

View File

@ -7,9 +7,10 @@ namespace Rector\DoctrineCodeQuality\NodeAnalyzer;
use PhpParser\Node\Stmt\Property;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\BetterPhpDocParser\PhpDocNode\Doctrine\Property_\ColumnTagValueNode;
use Rector\BetterPhpDocParser\PhpDocNode\Doctrine\Property_\OneToManyTagValueNode;
use Rector\NodeTypeResolver\Node\AttributeKey;
final class ColumnPropertyAnalyzer
final class DoctrinePropertyAnalyzer
{
public function matchDoctrineColumnTagValueNode(Property $property): ?ColumnTagValueNode
{
@ -21,4 +22,15 @@ final class ColumnPropertyAnalyzer
return $phpDocInfo->getByType(ColumnTagValueNode::class);
}
public function matchDoctrineOneToManyTagValueNode(Property $property): ?OneToManyTagValueNode
{
/** @var PhpDocInfo|null $phpDocInfo */
$phpDocInfo = $property->getAttribute(AttributeKey::PHP_DOC_INFO);
if ($phpDocInfo === null) {
return null;
}
return $phpDocInfo->getByType(OneToManyTagValueNode::class);
}
}

View File

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace Rector\DoctrineCodeQuality\PhpDoc;
use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use Rector\AttributeAwarePhpDoc\Ast\Type\AttributeAwareArrayTypeNode;
use Rector\AttributeAwarePhpDoc\Ast\Type\AttributeAwareFullyQualifiedIdentifierTypeNode;
use Rector\AttributeAwarePhpDoc\Ast\Type\AttributeAwareIdentifierTypeNode;
use Rector\AttributeAwarePhpDoc\Ast\Type\AttributeAwareUnionTypeNode;
final class CollectionTypeFactory
{
public function createFromIdentifierType(IdentifierTypeNode $identifierTypeNode): AttributeAwareUnionTypeNode
{
$genericTypeNode = $this->createGenericTypeNode($identifierTypeNode);
return new AttributeAwareUnionTypeNode([
$genericTypeNode,
new AttributeAwareArrayTypeNode($identifierTypeNode),
]);
}
private function createGenericTypeNode(IdentifierTypeNode $identifierTypeNode): GenericTypeNode
{
$genericTypesNodes = [new AttributeAwareIdentifierTypeNode('int'), $identifierTypeNode];
return new GenericTypeNode(new AttributeAwareFullyQualifiedIdentifierTypeNode(
'Doctrine\Common\Collections\Collection'
), $genericTypesNodes);
}
}

View File

@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace Rector\DoctrineCodeQuality\PhpDoc;
use PhpParser\Node\Stmt\Property;
use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
use Rector\AttributeAwarePhpDoc\Ast\Type\AttributeAwareIdentifierTypeNode;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\BetterPhpDocParser\PhpDocNode\Doctrine\Property_\OneToManyTagValueNode;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\NodeTypeResolver\Node\AttributeKey;
final class CollectionTypeResolver
{
public function resolveFromType(TypeNode $typeNode): ?IdentifierTypeNode
{
if ($typeNode instanceof UnionTypeNode) {
foreach ($typeNode->types as $unionedTypeNode) {
if ($this->resolveFromType($unionedTypeNode) !== null) {
return $this->resolveFromType($unionedTypeNode);
}
}
}
if ($typeNode instanceof ArrayTypeNode && $typeNode->type instanceof IdentifierTypeNode) {
return $typeNode->type;
}
return null;
}
public function resolveFromOneToManyProperty(Property $property): ?IdentifierTypeNode
{
$phpDocInfo = $property->getAttribute(AttributeKey::PHP_DOC_INFO);
if (! $phpDocInfo instanceof PhpDocInfo) {
return null;
}
/** @var OneToManyTagValueNode|null $oneToManyTagValueNode */
$oneToManyTagValueNode = $phpDocInfo->getByType(OneToManyTagValueNode::class);
if ($oneToManyTagValueNode === null) {
return null;
}
$fullyQualifiedTargetEntity = $oneToManyTagValueNode->getFullyQualifiedTargetEntity();
if ($fullyQualifiedTargetEntity === null) {
throw new ShouldNotHappenException();
}
return new AttributeAwareIdentifierTypeNode($fullyQualifiedTargetEntity);
}
}

View File

@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Rector\DoctrineCodeQuality\PhpDoc;
use PhpParser\Node\Stmt\Property;
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\DoctrineCodeQuality\NodeAnalyzer\DoctrinePropertyAnalyzer;
use Rector\NodeTypeResolver\Node\AttributeKey;
final class CollectionVarTagValueNodeResolver
{
/**
* @var DoctrinePropertyAnalyzer
*/
private $doctrinePropertyAnalyzer;
public function __construct(DoctrinePropertyAnalyzer $doctrinePropertyAnalyzer)
{
$this->doctrinePropertyAnalyzer = $doctrinePropertyAnalyzer;
}
public function resolve(Property $property): ?VarTagValueNode
{
$doctrineOneToManyTagValueNode = $this->doctrinePropertyAnalyzer->matchDoctrineOneToManyTagValueNode($property);
if ($doctrineOneToManyTagValueNode === null) {
return null;
}
$phpDocInfo = $property->getAttribute(AttributeKey::PHP_DOC_INFO);
if (! $phpDocInfo instanceof PhpDocInfo) {
return null;
}
return $phpDocInfo->getVarTagValueNode();
}
}

View File

@ -14,7 +14,7 @@ use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\RectorDefinition\CodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
use Rector\DoctrineCodeQuality\NodeAnalyzer\ColumnPropertyAnalyzer;
use Rector\DoctrineCodeQuality\NodeAnalyzer\DoctrinePropertyAnalyzer;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockClassRenamer;
@ -28,9 +28,9 @@ use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockClassRenamer;
final class ChangeBigIntEntityPropertyToIntTypeRector extends AbstractRector
{
/**
* @var ColumnPropertyAnalyzer
* @var DoctrinePropertyAnalyzer
*/
private $columnPropertyAnalyzer;
private $doctrinePropertyAnalyzer;
/**
* @var DocBlockClassRenamer
@ -38,10 +38,10 @@ final class ChangeBigIntEntityPropertyToIntTypeRector extends AbstractRector
private $docBlockClassRenamer;
public function __construct(
ColumnPropertyAnalyzer $columnPropertyAnalyzer,
DoctrinePropertyAnalyzer $doctrinePropertyAnalyzer,
DocBlockClassRenamer $docBlockClassRenamer
) {
$this->columnPropertyAnalyzer = $columnPropertyAnalyzer;
$this->doctrinePropertyAnalyzer = $doctrinePropertyAnalyzer;
$this->docBlockClassRenamer = $docBlockClassRenamer;
}
@ -97,7 +97,7 @@ CODE_SAMPLE
*/
public function refactor(Node $node): ?Node
{
$columnTagValueNode = $this->columnPropertyAnalyzer->matchDoctrineColumnTagValueNode($node);
$columnTagValueNode = $this->doctrinePropertyAnalyzer->matchDoctrineColumnTagValueNode($node);
if ($columnTagValueNode === null) {
return null;
}
@ -112,7 +112,7 @@ CODE_SAMPLE
return null;
}
$attributeAwareVarTagValueNode = $phpDocInfo->getVarTagValue();
$attributeAwareVarTagValueNode = $phpDocInfo->getVarTagValueNode();
if ($attributeAwareVarTagValueNode === null) {
return null;
}

View File

@ -14,7 +14,7 @@ use Rector\Core\Exception\NotImplementedYetException;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\RectorDefinition\CodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
use Rector\DoctrineCodeQuality\NodeAnalyzer\ColumnPropertyAnalyzer;
use Rector\DoctrineCodeQuality\NodeAnalyzer\DoctrinePropertyAnalyzer;
/**
* @sponsor Thanks https://www.luzanky.cz/ for sponsoring this rule
@ -24,13 +24,13 @@ use Rector\DoctrineCodeQuality\NodeAnalyzer\ColumnPropertyAnalyzer;
final class CorrectDefaultTypesOnEntityPropertyRector extends AbstractRector
{
/**
* @var ColumnPropertyAnalyzer
* @var DoctrinePropertyAnalyzer
*/
private $columnPropertyAnalyzer;
private $doctrinePropertyAnalyzer;
public function __construct(ColumnPropertyAnalyzer $columnPropertyAnalyzer)
public function __construct(DoctrinePropertyAnalyzer $doctrinePropertyAnalyzer)
{
$this->columnPropertyAnalyzer = $columnPropertyAnalyzer;
$this->doctrinePropertyAnalyzer = $doctrinePropertyAnalyzer;
}
public function getDefinition(): RectorDefinition
@ -84,7 +84,7 @@ CODE_SAMPLE
*/
public function refactor(Node $node): ?Node
{
$columnTagValueNode = $this->columnPropertyAnalyzer->matchDoctrineColumnTagValueNode($node);
$columnTagValueNode = $this->doctrinePropertyAnalyzer->matchDoctrineColumnTagValueNode($node);
if ($columnTagValueNode === null) {
return null;
}

View File

@ -0,0 +1,241 @@
<?php
declare(strict_types=1);
namespace Rector\DoctrineCodeQuality\Rector\Property;
use PhpParser\Node;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Property;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use Rector\AttributeAwarePhpDoc\Ast\PhpDoc\AttributeAwareVarTagValueNode;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\BetterPhpDocParser\PhpDocNode\Doctrine\Property_\OneToManyTagValueNode;
use Rector\Core\PhpParser\Node\Manipulator\AssignManipulator;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\RectorDefinition\CodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
use Rector\DoctrineCodeQuality\PhpDoc\CollectionTypeFactory;
use Rector\DoctrineCodeQuality\PhpDoc\CollectionTypeResolver;
use Rector\DoctrineCodeQuality\PhpDoc\CollectionVarTagValueNodeResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
/**
* @see \Rector\DoctrineCodeQuality\Tests\Rector\Property\ImproveDoctrineCollectionDocTypeInEntityRector\ImproveDoctrineCollectionDocTypeInEntityRectorTest
*/
final class ImproveDoctrineCollectionDocTypeInEntityRector extends AbstractRector
{
/**
* @var CollectionTypeFactory
*/
private $collectionTypeFactory;
/**
* @var AssignManipulator
*/
private $assignManipulator;
/**
* @var CollectionTypeResolver
*/
private $collectionTypeResolver;
/**
* @var CollectionVarTagValueNodeResolver
*/
private $collectionVarTagValueNodeResolver;
public function __construct(
CollectionTypeFactory $collectionTypeFactory,
AssignManipulator $assignManipulator,
CollectionTypeResolver $collectionTypeResolver,
CollectionVarTagValueNodeResolver $collectionVarTagValueNodeResolver
) {
$this->collectionTypeFactory = $collectionTypeFactory;
$this->assignManipulator = $assignManipulator;
$this->collectionTypeResolver = $collectionTypeResolver;
$this->collectionVarTagValueNodeResolver = $collectionVarTagValueNodeResolver;
}
public function getDefinition(): RectorDefinition
{
return new RectorDefinition(
'Improve @var, @param and @return types for Doctrine collections to make them useful both for PHPStan and PHPStorm',
[
new CodeSample(
<<<'CODE_SAMPLE'
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class SomeClass
{
/**
* @ORM\OneToMany(targetEntity=Training::class, mappedBy="trainer")
* @var Collection|Trainer[]
*/
private $trainings = [];
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class SomeClass
{
/**
* @ORM\OneToMany(targetEntity=Training::class, mappedBy="trainer")
* @var Collection<int, Training>|Trainer[]
*/
private $trainings = [];
}
CODE_SAMPLE
),
]
);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [Property::class, ClassMethod::class];
}
/**
* @param Property|ClassMethod $node
*/
public function refactor(Node $node): ?Node
{
if ($node instanceof Property) {
return $this->refactorProperty($node);
}
if ($node instanceof ClassMethod) {
return $this->refactorClassMethod($node);
}
return null;
}
private function refactorProperty(Property $property): ?Property
{
if (! $this->hasNodeTagValueNode($property, OneToManyTagValueNode::class)) {
return null;
}
// @todo make an own local property on enter node?
/** @var PhpDocInfo $phpDocInfo */
$phpDocInfo = $property->getAttribute(AttributeKey::PHP_DOC_INFO);
$attributeAwareVarTagValueNode = $this->collectionVarTagValueNodeResolver->resolve($property);
if ($attributeAwareVarTagValueNode !== null) {
$collectionObjectType = $this->collectionTypeResolver->resolveFromType(
$attributeAwareVarTagValueNode->type
);
if ($collectionObjectType === null) {
return null;
}
$attributeAwareVarTagValueNode->type = $this->collectionTypeFactory->createFromIdentifierType(
$collectionObjectType
);
} else {
$collectionObjectType = $this->collectionTypeResolver->resolveFromOneToManyProperty($property);
if ($collectionObjectType === null) {
return null;
}
$attributeAwareUnionTypeNode = $this->collectionTypeFactory->createFromIdentifierType(
$collectionObjectType
);
$attributeAwareVarTagValueNode = new AttributeAwareVarTagValueNode($attributeAwareUnionTypeNode, '', '');
$phpDocInfo->addTagValueNode($attributeAwareVarTagValueNode);
}
return $property;
}
private function refactorClassMethod(ClassMethod $classMethod): ?ClassMethod
{
if (! $this->isInDoctrineEntityClass($classMethod)) {
return null;
}
if (! $classMethod->isPublic()) {
return null;
}
$collectionObjectType = $this->resolveCollectionSetterAssignType($classMethod);
if ($collectionObjectType === null) {
return null;
}
if (count($classMethod->params) !== 1) {
return null;
}
/** @var PhpDocInfo $phpDocInfo */
$phpDocInfo = $classMethod->getAttribute(AttributeKey::PHP_DOC_INFO);
$param = $classMethod->params[0];
$parameterName = $this->getName($param);
$phpDocInfo->changeParamType($collectionObjectType, $param, $parameterName);
return $classMethod;
}
private function hasNodeTagValueNode(Node $node, string $tagValueNodeClass): bool
{
$phpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO);
if (! $phpDocInfo instanceof PhpDocInfo) {
return false;
}
return $phpDocInfo->hasByType($tagValueNodeClass);
}
private function resolveCollectionSetterAssignType(ClassMethod $classMethod): ?TypeNode
{
$propertyFetches = $this->assignManipulator->resolveAssignsToLocalPropertyFetches($classMethod);
if (count($propertyFetches) !== 1) {
return null;
}
$property = $this->matchPropertyFetchToClassProperty($propertyFetches[0]);
if ($property === null) {
return null;
}
$varTagValueNode = $this->collectionVarTagValueNodeResolver->resolve($property);
if ($varTagValueNode === null) {
return null;
}
return $varTagValueNode->type;
}
private function matchPropertyFetchToClassProperty(PropertyFetch $propertyFetch): ?Property
{
$propertyName = $this->getName($propertyFetch);
if ($propertyName === null) {
return null;
}
$classLike = $propertyFetch->getAttribute(AttributeKey::CLASS_NODE);
if (! $classLike instanceof Class_) {
return null;
}
return $classLike->getProperty($propertyName);
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace Rector\DoctrineCodeQuality\Tests\Rector\Property\ImproveDoctrineCollectionDocTypeInEntityRector\Fixture;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class NoVar
{
/**
* @ORM\OneToMany(targetEntity=Training::class, mappedBy="trainer")
*/
private $trainings = [];
}
?>
-----
<?php
namespace Rector\DoctrineCodeQuality\Tests\Rector\Property\ImproveDoctrineCollectionDocTypeInEntityRector\Fixture;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class NoVar
{
/**
* @ORM\OneToMany(targetEntity=Training::class, mappedBy="trainer")
* @var \Doctrine\Common\Collections\Collection<int, Training>|Training[]
*/
private $trainings = [];
}
?>

View File

@ -0,0 +1,56 @@
<?php
namespace Rector\DoctrineCodeQuality\Tests\Rector\Property\ImproveDoctrineCollectionDocTypeInEntityRector\Fixture;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class ParamWithoutArray
{
/**
* @ORM\OneToMany(targetEntity=Training::class, mappedBy="trainer")
*/
private $trainings = [];
/**
* @param Collection $collection
*/
public function setTrainings($collection): void
{
$this->trainings = $collection;
}
}
?>
-----
<?php
namespace Rector\DoctrineCodeQuality\Tests\Rector\Property\ImproveDoctrineCollectionDocTypeInEntityRector\Fixture;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class ParamWithoutArray
{
/**
* @ORM\OneToMany(targetEntity=Training::class, mappedBy="trainer")
* @var \Doctrine\Common\Collections\Collection<int, Training>|Training[]
*/
private $trainings = [];
/**
* @param \Doctrine\Common\Collections\Collection<int, Training>|Training[] $collection
*/
public function setTrainings($collection): void
{
$this->trainings = $collection;
}
}
?>

View File

@ -0,0 +1,41 @@
<?php
namespace Rector\DoctrineCodeQuality\Tests\Rector\Property\ImproveDoctrineCollectionDocTypeInEntityRector\Fixture;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class VarSomeClass
{
/**
* @ORM\OneToMany(targetEntity=Training::class, mappedBy="trainer")
* @var Collection<int, string>|Training[]
*/
private $trainings = [];
}
?>
-----
<?php
namespace Rector\DoctrineCodeQuality\Tests\Rector\Property\ImproveDoctrineCollectionDocTypeInEntityRector\Fixture;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class VarSomeClass
{
/**
* @ORM\OneToMany(targetEntity=Training::class, mappedBy="trainer")
* @var \Doctrine\Common\Collections\Collection<int, Training>|Training[]
*/
private $trainings = [];
}
?>

View File

@ -0,0 +1,41 @@
<?php
namespace Rector\DoctrineCodeQuality\Tests\Rector\Property\ImproveDoctrineCollectionDocTypeInEntityRector\Fixture;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class VarWithoutArray
{
/**
* @ORM\OneToMany(targetEntity=Training::class, mappedBy="trainer")
* @var Training[]
*/
private $trainings = [];
}
?>
-----
<?php
namespace Rector\DoctrineCodeQuality\Tests\Rector\Property\ImproveDoctrineCollectionDocTypeInEntityRector\Fixture;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class VarWithoutArray
{
/**
* @ORM\OneToMany(targetEntity=Training::class, mappedBy="trainer")
* @var \Doctrine\Common\Collections\Collection<int, Training>|Training[]
*/
private $trainings = [];
}
?>

View File

@ -0,0 +1,39 @@
<?php
namespace Rector\DoctrineCodeQuality\Tests\Rector\Property\ImproveDoctrineCollectionDocTypeInEntityRector\Fixture;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class VarWithoutCollection
{
/**
* @ORM\OneToMany(targetEntity=Training::class, mappedBy="trainer")
* @var Training[]
*/
private $trainings = [];
}
?>
-----
<?php
namespace Rector\DoctrineCodeQuality\Tests\Rector\Property\ImproveDoctrineCollectionDocTypeInEntityRector\Fixture;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class VarWithoutCollection
{
/**
* @ORM\OneToMany(targetEntity=Training::class, mappedBy="trainer")
* @var \Doctrine\Common\Collections\Collection<int, Training>|Training[]
*/
private $trainings = [];
}
?>

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Rector\DoctrineCodeQuality\Tests\Rector\Property\ImproveDoctrineCollectionDocTypeInEntityRector;
use Iterator;
use Rector\Core\Testing\PHPUnit\AbstractRectorTestCase;
use Rector\DoctrineCodeQuality\Rector\Property\ImproveDoctrineCollectionDocTypeInEntityRector;
use Symplify\SmartFileSystem\SmartFileInfo;
final class ImproveDoctrineCollectionDocTypeInEntityRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(SmartFileInfo $fileInfo): void
{
$this->doTestFileInfo($fileInfo);
}
public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}
protected function getRectorClass(): string
{
return ImproveDoctrineCollectionDocTypeInEntityRector::class;
}
}

View File

@ -143,7 +143,7 @@ CODE_SAMPLE
}
// it needs @var tag as well, to get the type
if ($phpDocInfo->getVarTagValue() !== null) {
if ($phpDocInfo->getVarTagValueNode() !== null) {
return false;
}

View File

@ -98,7 +98,7 @@ CODE_SAMPLE
return null;
}
$attributeAwareVarTagValueNode = $phpDocInfo->getVarTagValue();
$attributeAwareVarTagValueNode = $phpDocInfo->getVarTagValueNode();
if ($attributeAwareVarTagValueNode === null) {
return null;
}

View File

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Rector\PHPUnit\Composer;
use Nette\Utils\Arrays;
use Nette\Utils\Json;
use Rector\Core\Testing\PHPUnit\StaticPHPUnitEnvironment;
use Symplify\SmartFileSystem\SmartFileSystem;
@ -32,7 +33,7 @@ final class ComposerAutoloadedDirectoryProvider
}
/**
* @return string[]
* @return string[]|mixed[]
*/
public function provide(): array
{
@ -49,10 +50,10 @@ final class ComposerAutoloadedDirectoryProvider
}
$sectionDirectories = $this->collectDirectoriesFromAutoload($composerJson[$autoloadSection]);
$autoloadDirectories = array_merge($autoloadDirectories, $sectionDirectories);
$autoloadDirectories[] = $sectionDirectories;
}
return $autoloadDirectories;
return Arrays::flatten($autoloadDirectories);
}
/**

View File

@ -49,9 +49,7 @@ final class PHPUnitTestCaseClassesProvider
$robotLoader->setTempDirectory(sys_get_temp_dir() . '/tests_add_see_rector_tests');
$directories = $this->composerAutoloadedDirectoryProvider->provide();
foreach ($directories as $directory) {
$robotLoader->addDirectory($directory);
}
$robotLoader->addDirectory(...$directories);
$robotLoader->acceptFiles = ['*Test.php'];
$robotLoader->ignoreDirs[] = '*Expected*';

View File

@ -110,7 +110,7 @@ CODE_SAMPLE
private function getVarDocVariableName(PhpDocInfo $phpDocInfo): ?string
{
$attributeAwareVarTagValueNode = $phpDocInfo->getVarTagValue();
$attributeAwareVarTagValueNode = $phpDocInfo->getVarTagValueNode();
if ($attributeAwareVarTagValueNode === null) {
return null;
}
@ -166,7 +166,7 @@ CODE_SAMPLE
private function refactorAlreadyCreatedNode(Node $node, PhpDocInfo $phpDocInfo, Variable $variable): ?Node
{
$varTagValue = $phpDocInfo->getVarTagValue();
$varTagValue = $phpDocInfo->getVarTagValueNode();
if ($varTagValue === null) {
throw new ShouldNotHappenException();
}

View File

@ -5,10 +5,9 @@ declare(strict_types=1);
namespace Rector\TypeDeclaration\PhpDocParser;
use PhpParser\Node\Param;
use PHPStan\Type\Type;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use Rector\AttributeAwarePhpDoc\Ast\PhpDoc\AttributeAwareParamTagValueNode;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\StaticTypeMapper\StaticTypeMapper;
final class ParamPhpDocNodeFactory
{
@ -17,21 +16,13 @@ final class ParamPhpDocNodeFactory
*/
private $nodeNameResolver;
/**
* @var StaticTypeMapper
*/
private $staticTypeMapper;
public function __construct(NodeNameResolver $nodeNameResolver, StaticTypeMapper $staticTypeMapper)
public function __construct(NodeNameResolver $nodeNameResolver)
{
$this->nodeNameResolver = $nodeNameResolver;
$this->staticTypeMapper = $staticTypeMapper;
}
public function create(Type $type, Param $param): AttributeAwareParamTagValueNode
public function create(TypeNode $typeNode, Param $param): AttributeAwareParamTagValueNode
{
$typeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($type);
return new AttributeAwareParamTagValueNode(
$typeNode,
$param->variadic,

View File

@ -100,10 +100,7 @@ final class AdditionalAutoloader
}
// last argument is workaround: https://github.com/nette/robot-loader/issues/12
$robotLoader->setTempDirectory(sys_get_temp_dir() . '/_rector_robot_loader');
foreach ($directories as $autoloadDirectory) {
$robotLoader->addDirectory($autoloadDirectory);
}
$robotLoader->addDirectory(...$directories);
$robotLoader->register();
}

View File

@ -14,7 +14,11 @@ use PhpParser\Node\Expr\PostDec;
use PhpParser\Node\Expr\PostInc;
use PhpParser\Node\Expr\PreDec;
use PhpParser\Node\Expr\PreInc;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Stmt\Expression;
use Rector\Core\NodeAnalyzer\PropertyFetchAnalyzer;
use Rector\Core\PhpParser\Node\BetterNodeFinder;
use Rector\Core\PhpParser\Printer\BetterStandardPrinter;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
@ -42,10 +46,26 @@ final class AssignManipulator
*/
private $betterStandardPrinter;
public function __construct(BetterStandardPrinter $betterStandardPrinter, NodeNameResolver $nodeNameResolver)
{
/**
* @var BetterNodeFinder
*/
private $betterNodeFinder;
/**
* @var PropertyFetchAnalyzer
*/
private $propertyFetchAnalyzer;
public function __construct(
BetterStandardPrinter $betterStandardPrinter,
NodeNameResolver $nodeNameResolver,
BetterNodeFinder $betterNodeFinder,
PropertyFetchAnalyzer $propertyFetchAnalyzer
) {
$this->nodeNameResolver = $nodeNameResolver;
$this->betterStandardPrinter = $betterStandardPrinter;
$this->betterNodeFinder = $betterNodeFinder;
$this->propertyFetchAnalyzer = $propertyFetchAnalyzer;
}
/**
@ -65,7 +85,7 @@ final class AssignManipulator
return $this->nodeNameResolver->isName($assign->expr, 'each');
}
public function isNodeLeftPartOfAssign(Node $node): bool
public function isLeftPartOfAssign(Node $node): bool
{
$parentNode = $node->getAttribute(AttributeKey::PARENT_NODE);
if ($parentNode instanceof Assign && $parentNode->var === $node) {
@ -116,6 +136,20 @@ final class AssignManipulator
return false;
}
/**
* @return PropertyFetch[]
*/
public function resolveAssignsToLocalPropertyFetches(FunctionLike $functionLike): array
{
return $this->betterNodeFinder->find($functionLike->getStmts(), function (Node $node): bool {
if (! $this->propertyFetchAnalyzer->isLocalPropertyFetch($node)) {
return false;
}
return $this->isLeftPartOfAssign($node);
});
}
private function isValueModifyingNode(Node $node): bool
{
foreach (self::MODIFYING_NODES as $modifyingNode) {

View File

@ -193,6 +193,6 @@ final class PropertyManipulator
}
}
return $this->assignManipulator->isNodeLeftPartOfAssign($node);
return $this->assignManipulator->isLeftPartOfAssign($node);
}
}

View File

@ -154,7 +154,7 @@ final class VariableManipulator
return false;
}
if (! $this->assignManipulator->isNodeLeftPartOfAssign($variableUsage)) {
if (! $this->assignManipulator->isLeftPartOfAssign($variableUsage)) {
continue;
}

View File

@ -83,13 +83,13 @@ final class RectorsFinder
private function findClassesInDirectoriesByName(array $directories, string $name): array
{
$robotLoader = new RobotLoader();
foreach ($directories as $directory) {
$robotLoader->addDirectory($directory);
}
$robotLoader->addDirectory(...$directories);
$robotLoader->setTempDirectory(sys_get_temp_dir() . '/_rector_finder');
$robotLoader->acceptFiles = [$name];
$robotLoader->excludeDirectory(__DIR__ . '/../../../packages/rector-generator/tests');
$robotLoader->rebuild();
$classNames = [];

View File

@ -34,12 +34,12 @@ final class NodeClassFinder
private function findClassesByNamePatternInDirectories(string $namePattern, array $directories): array
{
$robotLoader = new RobotLoader();
foreach ($directories as $directory) {
$robotLoader->addDirectory($directory);
}
$robotLoader->addDirectory(...$directories);
$robotLoader->setTempDirectory(sys_get_temp_dir() . '/_phpdoc_parser_ast');
$robotLoader->acceptFiles = [$namePattern];
$robotLoader->rebuild();
$indexedClasses = $robotLoader->getIndexedClasses();