Skip to content

Commit

Permalink
[PHP 8.0] Extend DoctrineAnnotationClassToAttributeRector rule to cov…
Browse files Browse the repository at this point in the history
…er short target annotations (#1260)
  • Loading branch information
TomasVotruba committed Nov 17, 2021
1 parent 660adeb commit d2f7c2e
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 50 deletions.
17 changes: 17 additions & 0 deletions packages/BetterPhpDocParser/PhpDocInfo/PhpDocInfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,23 @@ public function hasByAnnotationClasses(array $annotationsClasses): bool
return $this->getByAnnotationClasses($annotationsClasses) !== null;
}

/**
* @param string[] $desiredClasses
*/
public function findOneByAnnotationClasses(array $desiredClasses): ?DoctrineAnnotationTagValueNode
{
foreach ($desiredClasses as $desiredClass) {
$doctrineAnnotationTagValueNode = $this->findOneByAnnotationClass($desiredClass);
if (! $doctrineAnnotationTagValueNode instanceof DoctrineAnnotationTagValueNode) {
continue;
}

return $doctrineAnnotationTagValueNode;
}

return null;
}

/**
* @param class-string $desiredClass
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@

final class DoctrineAnnotationDecorator
{
/**
* Special short annotations, that are resolved as FQN by Doctrine annotation parser
* @var string[]
*/
private const ALLOWED_SHORT_ANNOTATIONS = ['Target'];

public function __construct(
private CurrentNodeProvider $currentNodeProvider,
private ClassAnnotationMatcher $classAnnotationMatcher,
Expand Down Expand Up @@ -142,7 +148,11 @@ private function transformGenericTagValueNodesToDoctrineAnnotationTagValueNodes(
);

// not an annotations class
if (! \str_contains($fullyQualifiedAnnotationClass, '\\')) {
if (! \str_contains($fullyQualifiedAnnotationClass, '\\') && ! in_array(
$fullyQualifiedAnnotationClass,
self::ALLOWED_SHORT_ANNOTATIONS,
true
)) {
continue;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace Rector\Tests\Php80\Rector\Class_\DoctrineAnnotationClassToAttributeRector\Fixture;

/**
* @Annotation
* @Target("METHOD")
*/
final class ShortTarget
{
}

?>
-----
<?php

namespace Rector\Tests\Php80\Rector\Class_\DoctrineAnnotationClassToAttributeRector\Fixture;

#[\Attribute(\Attribute::TARGET_METHOD)]
final class ShortTarget
{
}

?>
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
namespace Rector\DowngradePhp55\Rector\Foreach_;

use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\List_;
use PhpParser\Node\Expr\Variable;
Expand All @@ -16,7 +15,6 @@
use Rector\NodeTypeResolver\Node\AttributeKey;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
use Webmozart\Assert\Assert;

/**
* @changelog https://wiki.php.net/rfc/foreachlist
Expand Down Expand Up @@ -71,9 +69,9 @@ public function refactor(Node $node): ?Node
}

$variable = $this->createVariable($node);
$exprAssign = new Expression(new Assign($node->valueVar, $variable));
$expression = new Expression(new Assign($node->valueVar, $variable));
$node->valueVar = $variable;
$node->stmts = array_merge([$exprAssign], $node->stmts);
$node->stmts = array_merge([$expression], $node->stmts);

return $node;
}
Expand Down
49 changes: 49 additions & 0 deletions rules/Php80/NodeAnalyzer/AnnotationTargetResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

declare(strict_types=1);

namespace Rector\Php80\NodeAnalyzer;

use PhpParser\Node\Expr\ClassConstFetch;
use Rector\Core\PhpParser\Node\NodeFactory;

final class AnnotationTargetResolver
{
/**
* @see https://github.com/doctrine/annotations/blob/e6e7b7d5b45a2f2abc5460cc6396480b2b1d321f/lib/Doctrine/Common/Annotations/Annotation/Target.php#L24-L29
* @var array<string, string>
*/
private const TARGET_TO_CONSTANT_MAP = [
'METHOD' => 'TARGET_METHOD',
'PROPERTY' => 'TARGET_PROPERTY',
'CLASS' => 'TARGET_CLASS',
'FUNCTION' => 'TARGET_FUNCTION',
'ALL' => 'TARGET_ALL',
// special case
'ANNOTATION' => 'TARGET_CLASS',
];

public function __construct(
private NodeFactory $nodeFactory,
) {
}

/**
* @param array<int|string, mixed> $targetValues
* @return ClassConstFetch[]
*/
public function resolveFlagClassConstFetches(array $targetValues): array
{
$classConstFetches = [];

foreach (self::TARGET_TO_CONSTANT_MAP as $target => $constant) {
if (! in_array($target, $targetValues, true)) {
continue;
}

$classConstFetches[] = $this->nodeFactory->createClassConstFetch('Attribute', $constant);
}

return $classConstFetches;
}
}
12 changes: 6 additions & 6 deletions rules/Php80/NodeFactory/AttributeFlagFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,18 @@
final class AttributeFlagFactory
{
/**
* @param ClassConstFetch[] $flags
* @param ClassConstFetch[] $classConstFetches
* @return ClassConstFetch|BitwiseOr|null
*/
public function createFlagCollection(array $flags): ?Expr
public function createFlagCollection(array $classConstFetches): ?Expr
{
if ($flags === []) {
if ($classConstFetches === []) {
return null;
}

$flagCollection = array_shift($flags);
foreach ($flags as $flag) {
$flagCollection = new BitwiseOr($flagCollection, $flag);
$flagCollection = array_shift($classConstFetches);
foreach ($classConstFetches as $classConstFetch) {
$flagCollection = new BitwiseOr($flagCollection, $classConstFetch);
}

return $flagCollection;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\AttributeGroup;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Stmt\Class_;
use PHPStan\Type\MixedType;
use Rector\BetterPhpDocParser\PhpDoc\DoctrineAnnotationTagValueNode;
Expand All @@ -17,6 +16,7 @@
use Rector\Core\Contract\Rector\ConfigurableRectorInterface;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\ValueObject\PhpVersionFeature;
use Rector\Php80\NodeAnalyzer\AnnotationTargetResolver;
use Rector\Php80\NodeAnalyzer\PhpAttributeAnalyzer;
use Rector\Php80\NodeFactory\AttributeFlagFactory;
use Rector\PhpAttribute\Printer\PhpAttributeGroupFactory;
Expand All @@ -42,20 +42,6 @@ final class DoctrineAnnotationClassToAttributeRector extends AbstractRector impl
*/
public const REMOVE_ANNOTATIONS = 'remove_annotations';

/**
* @see https://github.com/doctrine/annotations/blob/e6e7b7d5b45a2f2abc5460cc6396480b2b1d321f/lib/Doctrine/Common/Annotations/Annotation/Target.php#L24-L29
* @var array<string, string>
*/
private const TARGET_TO_CONSTANT_MAP = [
'METHOD' => 'TARGET_METHOD',
'PROPERTY' => 'TARGET_PROPERTY',
'CLASS' => 'TARGET_CLASS',
'FUNCTION' => 'TARGET_FUNCTION',
'ALL' => 'TARGET_ALL',
// special case
'ANNOTATION' => 'TARGET_CLASS',
];

/**
* @var string
*/
Expand All @@ -68,7 +54,8 @@ public function __construct(
private AttributeFlagFactory $attributeFlagFactory,
private PhpAttributeGroupFactory $phpAttributeGroupFactory,
private PhpAttributeAnalyzer $phpAttributeAnalyzer,
private PropertyToAddCollector $propertyToAddCollector
private PropertyToAddCollector $propertyToAddCollector,
private AnnotationTargetResolver $annotationTargetResolver
) {
}

Expand Down Expand Up @@ -186,29 +173,12 @@ public function configure(array $configuration): void
$this->shouldRemoveAnnotations = $shouldRemoveAnnotations;
}

/**
* @param array<int|string, mixed> $targetValues
* @return ClassConstFetch[]
*/
private function resolveFlags(array $targetValues): array
{
$flags = [];
foreach (self::TARGET_TO_CONSTANT_MAP as $target => $constant) {
if (! in_array($target, $targetValues, true)) {
continue;
}

$flags[] = $this->nodeFactory->createClassConstFetch(self::ATTRIBUTE, $constant);
}

return $flags;
}

private function decorateTarget(PhpDocInfo $phpDocInfo, AttributeGroup $attributeGroup): void
{
$targetDoctrineAnnotationTagValueNode = $phpDocInfo->findOneByAnnotationClass(
'Doctrine\Common\Annotations\Annotation\Target'
);
$targetDoctrineAnnotationTagValueNode = $phpDocInfo->findOneByAnnotationClasses([
'Doctrine\Common\Annotations\Annotation\Target',
'Target',
]);

if (! $targetDoctrineAnnotationTagValueNode instanceof DoctrineAnnotationTagValueNode) {
return;
Expand All @@ -227,8 +197,9 @@ private function decorateTarget(PhpDocInfo $phpDocInfo, AttributeGroup $attribut
return;
}

$flags = $this->resolveFlags($targetValues);
$flagCollection = $this->attributeFlagFactory->createFlagCollection($flags);
$flagClassConstFetches = $this->annotationTargetResolver->resolveFlagClassConstFetches($targetValues);

$flagCollection = $this->attributeFlagFactory->createFlagCollection($flagClassConstFetches);
if ($flagCollection === null) {
return;
}
Expand Down

0 comments on commit d2f7c2e

Please sign in to comment.