Skip to content

Commit

Permalink
chore: convert ApiProperty annotations to attributes through rectorphp
Browse files Browse the repository at this point in the history
  • Loading branch information
vincentchalamon committed Dec 17, 2021
1 parent a31c746 commit 78ccd61
Show file tree
Hide file tree
Showing 7 changed files with 210 additions and 5 deletions.
Expand Up @@ -27,7 +27,7 @@
/**
* @experimental
*/
abstract class AbstractLegacyApiResourceToApiResourceAttribute extends AbstractRector
abstract class AbstractAnnotationToAttributeRector extends AbstractRector
{
use DeprecationMetadataTrait;

Expand Down Expand Up @@ -155,6 +155,16 @@ protected function resolveOperations($items, Node $node): array
}
}

return $this->resolveAttributes($values);
}

/**
* @param mixed $items
*/
protected function resolveAttributes($items): array
{
$values = $items instanceof DoctrineAnnotationTagValueNode ? $items->getValues() : $items;

$camelCaseToSnakeCaseNameConverter = new CamelCaseToSnakeCaseNameConverter();

// Transform "attributes" keys
Expand Down
@@ -0,0 +1,182 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Core\Bridge\Rector\Rules;

use ApiPlatform\Metadata\Resource\DeprecationMetadataTrait;
use PhpParser\Node;
use PhpParser\Node\Stmt\Property;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode;
use Rector\BetterPhpDocParser\PhpDoc\DoctrineAnnotationTagValueNode;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTagRemover;
use Rector\Core\Contract\Rector\ConfigurableRectorInterface;
use Rector\Core\ValueObject\PhpVersionFeature;
use Rector\Php80\ValueObject\AnnotationToAttribute;
use Rector\PhpAttribute\Printer\PhpAttributeGroupFactory;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
use Webmozart\Assert\Assert;

/**
* @experimental
*/
final class ApiPropertyAnnotationToApiPropertyAttributeRector extends AbstractAnnotationToAttributeRector implements ConfigurableRectorInterface
{
use DeprecationMetadataTrait;

/**
* @var string
*/
public const ANNOTATION_TO_ATTRIBUTE = 'api_property_annotation_to_api_property_attribute';
/**
* @var string
*/
public const REMOVE_TAG = 'remove_tag';
/**
* @var AnnotationToAttribute[]
*/
private $annotationsToAttributes = [];
/**
* @var bool
*/
private $removeTag;
/**
* @var PhpDocTagRemover
*/
private $phpDocTagRemover;

public function __construct(PhpAttributeGroupFactory $phpAttributeGroupFactory, PhpDocTagRemover $phpDocTagRemover)
{
$this->phpAttributeGroupFactory = $phpAttributeGroupFactory;
$this->phpDocTagRemover = $phpDocTagRemover;
}

public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('Change annotation to attribute', [new ConfiguredCodeSample(<<<'CODE_SAMPLE'
use ApiPlatform\Core\Annotation\ApiProperty;
/**
* @ApiProperty(iri="https://schema.org/alternateName")
*/
private $alias;
CODE_SAMPLE
, <<<'CODE_SAMPLE'
use ApiPlatform\Metadata\ApiProperty;
#[ApiProperty(types: ['https://schema.org/alternateName'])]
private $alias;
CODE_SAMPLE
, [
self::ANNOTATION_TO_ATTRIBUTE => [new AnnotationToAttribute(\ApiPlatform\Core\Annotation\ApiProperty::class, \ApiPlatform\Metadata\ApiProperty::class)],
self::REMOVE_TAG => true,
]),
]);
}

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

/**
* @param Property $node
*/
public function refactor(Node $node): ?Node
{
if (!$this->phpVersionProvider->isAtLeastPhpVersion(PhpVersionFeature::ATTRIBUTES)) {
return null;
}
$phpDocInfo = $this->phpDocInfoFactory->createFromNode($node);
if (!$phpDocInfo instanceof PhpDocInfo) {
return null;
}
$tags = $phpDocInfo->getPhpDocNode()->getTags();
$hasNewAttrGroups = $this->processApplyAttrGroups($tags, $phpDocInfo, $node);
if ($hasNewAttrGroups) {
return $node;
}

return null;
}

/**
* @param array<string, AnnotationToAttribute[]> $configuration
*/
public function configure(array $configuration): void
{
$annotationsToAttributes = $configuration[self::ANNOTATION_TO_ATTRIBUTE] ?? [];
Assert::allIsInstanceOf($annotationsToAttributes, AnnotationToAttribute::class);
$this->annotationsToAttributes = $annotationsToAttributes;
$this->removeTag = $configuration[self::REMOVE_TAG] ?? true;
}

/**
* @param array<PhpDocTagNode> $tags
* @param Property $node
*/
private function processApplyAttrGroups(array $tags, PhpDocInfo $phpDocInfo, Node $node): bool
{
$hasNewAttrGroups = false;
foreach ($tags as $tag) {
foreach ($this->annotationsToAttributes as $annotationToAttribute) {
$annotationToAttributeTag = $annotationToAttribute->getTag();

if ($phpDocInfo->hasByName($annotationToAttributeTag)) {
if (true === $this->removeTag) {
// 1. remove php-doc tag
$this->phpDocTagRemover->removeByName($phpDocInfo, $annotationToAttributeTag);
}
// 2. add attributes
array_unshift($node->attrGroups, $this->phpAttributeGroupFactory->createFromSimpleTag($annotationToAttribute));
$hasNewAttrGroups = true;
continue 2;
}
if ($this->shouldSkip($tag->value, $phpDocInfo, $annotationToAttributeTag)) {
continue;
}

if (true === $this->removeTag) {
// 1. remove php-doc tag
$this->phpDocTagRemover->removeTagValueFromNode($phpDocInfo, $tag->value);
}
// 2. add attributes
/** @var DoctrineAnnotationTagValueNode $tagValue */
$tagValue = clone $tag->value;
$tagValue->values = $this->resolveAttributes($tagValue);

$resourceAttributeGroup = $this->phpAttributeGroupFactory->create($tagValue, $annotationToAttribute);
array_unshift($node->attrGroups, $resourceAttributeGroup);
$hasNewAttrGroups = true;
continue 2;
}
}

return $hasNewAttrGroups;
}

private function shouldSkip(PhpDocTagValueNode $phpDocTagValueNode, PhpDocInfo $phpDocInfo, string $annotationToAttributeTag): bool
{
$doctrineAnnotationTagValueNode = $phpDocInfo->getByAnnotationClass($annotationToAttributeTag);
if ($phpDocTagValueNode !== $doctrineAnnotationTagValueNode) {
return true;
}

return !$phpDocTagValueNode instanceof DoctrineAnnotationTagValueNode;
}
}
Expand Up @@ -32,7 +32,7 @@
/**
* @experimental
*/
final class ApiResourceAnnotationToApiResourceAttributeRector extends AbstractLegacyApiResourceToApiResourceAttribute implements ConfigurableRectorInterface
final class ApiResourceAnnotationToApiResourceAttributeRector extends AbstractAnnotationToAttributeRector implements ConfigurableRectorInterface
{
use DeprecationMetadataTrait;

Expand Down Expand Up @@ -86,7 +86,7 @@ class Book
class Book
CODE_SAMPLE
, [
self::ANNOTATION_TO_ATTRIBUTE => [new AnnotationToAttribute(\ApiPlatform\Core\Annotation\ApiResource::class, \ApiPlatform\Core\Annotation\ApiResource::class)],
self::ANNOTATION_TO_ATTRIBUTE => [new AnnotationToAttribute(\ApiPlatform\Core\Annotation\ApiResource::class, \ApiPlatform\Metadata\ApiResource::class)],
self::REMOVE_TAG => true,
]),
]);
Expand Down
Expand Up @@ -28,7 +28,7 @@
/**
* @experimental
*/
final class LegacyApiResourceAttributeToApiResourceAttributeRector extends AbstractLegacyApiResourceToApiResourceAttribute implements ConfigurableRectorInterface
final class LegacyApiResourceAttributeToApiResourceAttributeRector extends AbstractAnnotationToAttributeRector implements ConfigurableRectorInterface
{
/**
* @var string
Expand Down
Expand Up @@ -11,6 +11,7 @@

declare(strict_types=1);

use ApiPlatform\Core\Bridge\Rector\Rules\ApiPropertyAnnotationToApiPropertyAttributeRector;
use ApiPlatform\Core\Bridge\Rector\Rules\ApiResourceAnnotationToApiResourceAttributeRector;
use Rector\Core\Configuration\Option;
use Rector\Core\ValueObject\PhpVersion;
Expand Down Expand Up @@ -44,4 +45,15 @@
),
]),
]]);

// ApiProperty annotation to ApiProperty attribute
$services->set(ApiPropertyAnnotationToApiPropertyAttributeRector::class)
->call('configure', [[
ApiPropertyAnnotationToApiPropertyAttributeRector::ANNOTATION_TO_ATTRIBUTE => ValueObjectInliner::inline([
new AnnotationToAttribute(
\ApiPlatform\Core\Annotation\ApiProperty::class,
\ApiPlatform\Metadata\ApiProperty::class
),
]),
]]);
};
Expand Up @@ -28,6 +28,7 @@
->call('configure', [[
RenameNamespaceRector::OLD_TO_NEW_NAMESPACES => [
'ApiPlatform\Core\Annotation\ApiResource' => 'ApiPlatform\Metadata\ApiResource',
'ApiPlatform\Core\Annotation\ApiProperty' => 'ApiPlatform\Metadata\ApiProperty',
'ApiPlatform\Core\Api\UrlGeneratorInterface' => 'ApiPlatform\Api\UrlGeneratorInterface',
],
]]);
Expand Down
Expand Up @@ -25,6 +25,6 @@

$services->set(RemoveAnnotationRector::class)
->call('configure', [[
RemoveAnnotationRector::ANNOTATIONS_TO_REMOVE => ['ApiPlatform\Core\Annotation\ApiSubresource'],
RemoveAnnotationRector::ANNOTATIONS_TO_REMOVE => ['ApiSubresource'],
]]);
};

0 comments on commit 78ccd61

Please sign in to comment.