Skip to content

Commit

Permalink
Add annotation to attribute core rename in AnnotationToAttributeRector (
Browse files Browse the repository at this point in the history
#2384)

* fix fixture name

* [PHP 8.0] Add test case for partial import annotation to attribute rename
  • Loading branch information
TomasVotruba committed May 28, 2022
1 parent 31e60de commit 012e9ad
Show file tree
Hide file tree
Showing 19 changed files with 226 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

use PhpParser\Node\Arg;
use PhpParser\Node\AttributeGroup;
use Rector\PhpAttribute\Printer\PhpAttributeGroupFactory;
use Rector\PhpAttribute\NodeFactory\PhpAttributeGroupFactory;
use Rector\Testing\PHPUnit\AbstractTestCase;
use Rector\Tests\Transform\Rector\FuncCall\ArgumentFuncCallToMethodCallRector\Fixture\Route;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
use Rector\Core\PhpParser\Node\BetterNodeFinder;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\Php80\NodeAnalyzer\PhpAttributeAnalyzer;
use Rector\PhpAttribute\Printer\PhpAttributeGroupFactory;
use Rector\PhpAttribute\NodeFactory\PhpAttributeGroupFactory;
use Rector\PHPStanStaticTypeMapper\Utils\TypeUnwrapper;
use Rector\StaticTypeMapper\StaticTypeMapper;
use ReturnTypeWillChange;
Expand Down
76 changes: 74 additions & 2 deletions packages/PhpAttribute/NodeFactory/AttributeNameFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,96 @@

namespace Rector\PhpAttribute\NodeFactory;

use Nette\Utils\Strings;
use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Stmt\Use_;
use Rector\BetterPhpDocParser\PhpDoc\DoctrineAnnotationTagValueNode;
use Rector\Php80\ValueObject\AnnotationToAttribute;
use Rector\PhpAttribute\ValueObject\UseAliasMetadata;

final class AttributeNameFactory
{
/**
* @param Use_[] $uses
*/
public function create(
AnnotationToAttribute $annotationToAttribute,
DoctrineAnnotationTagValueNode $doctrineAnnotationTagValueNode
DoctrineAnnotationTagValueNode $doctrineAnnotationTagValueNode,
array $uses
): FullyQualified|Name {
// attribute and class name are the same, so we re-use the short form to keep code compatible with previous one
// A. attribute and class name are the same, so we re-use the short form to keep code compatible with previous one
if ($annotationToAttribute->getAttributeClass() === $annotationToAttribute->getTag()) {
$attributeName = $doctrineAnnotationTagValueNode->identifierTypeNode->name;
$attributeName = ltrim($attributeName, '@');

return new Name($attributeName);
}

// B. different name
$useAliasMetadata = $this->matchUseAliasMetadata(
$uses,
$doctrineAnnotationTagValueNode,
$annotationToAttribute
);
if ($useAliasMetadata instanceof UseAliasMetadata) {
$useUse = $useAliasMetadata->getUseUse();

// is same as name?
$useImportName = $useAliasMetadata->getUseImportName();
if ($useUse->name->toString() !== $useImportName) {
// no? rename
$useUse->name = new Name($useImportName);
}

return new Name($useAliasMetadata->getShortAttributeName());
}

// 3. the class is not aliased and is compeltelly new... return the FQN version
return new FullyQualified($annotationToAttribute->getAttributeClass());
}

/**
* @param Use_[] $uses
*/
private function matchUseAliasMetadata(
array $uses,
DoctrineAnnotationTagValueNode $doctrineAnnotationTagValueNode,
AnnotationToAttribute $annotationToAttribute
): ?UseAliasMetadata {
$shortAnnotationName = trim($doctrineAnnotationTagValueNode->identifierTypeNode->name, '@');

foreach ($uses as $use) {
foreach ($use->uses as $useUse) {
if ($useUse->alias === null) {
continue;
}

$alias = $useUse->alias->toString();
if (! str_starts_with($shortAnnotationName, $alias)) {
continue;
}

$importName = $useUse->name->toString();

// previous keyword
$lastImportKeyword = Strings::after($importName, '\\', -1);
if ($lastImportKeyword === null) {
continue;
}

// resolve new short name
$newShortname = Strings::after($annotationToAttribute->getAttributeClass(), $lastImportKeyword);

$beforeImportName = Strings::before(
$annotationToAttribute->getAttributeClass(),
$lastImportKeyword
) . $lastImportKeyword;

return new UseAliasMetadata($alias . $newShortname, $beforeImportName, $useUse);
}
}

return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

declare(strict_types=1);

namespace Rector\PhpAttribute\Printer;
namespace Rector\PhpAttribute\NodeFactory;

use PhpParser\Node\Arg;
use PhpParser\Node\Attribute;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

declare(strict_types=1);

namespace Rector\PhpAttribute\Printer;
namespace Rector\PhpAttribute\NodeFactory;

use PhpParser\Node\Arg;
use PhpParser\Node\Attribute;
Expand All @@ -11,15 +11,14 @@
use PhpParser\Node\Expr\ArrayItem;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Use_;
use Rector\BetterPhpDocParser\PhpDoc\DoctrineAnnotationTagValueNode;
use Rector\Core\Php\PhpVersionProvider;
use Rector\Core\ValueObject\PhpVersionFeature;
use Rector\Php80\ValueObject\AnnotationToAttribute;
use Rector\PhpAttribute\AnnotationToAttributeMapper;
use Rector\PhpAttribute\AttributeArrayNameInliner;
use Rector\PhpAttribute\NodeAnalyzer\ExprParameterReflectionTypeCorrector;
use Rector\PhpAttribute\NodeFactory\AttributeNameFactory;
use Rector\PhpAttribute\NodeFactory\NamedArgsFactory;

/**
* @see \Rector\Tests\PhpAttribute\Printer\PhpAttributeGroupFactoryTest
Expand Down Expand Up @@ -70,20 +69,24 @@ public function createFromClassWithItems(string $attributeClass, array $items):
return new AttributeGroup([$attribute]);
}

/**
* @param Use_[] $uses
*/
public function create(
DoctrineAnnotationTagValueNode $doctrineAnnotationTagValueNode,
AnnotationToAttribute $annotationToAttribute,
array $uses
): AttributeGroup {
$values = $doctrineAnnotationTagValueNode->getValuesWithExplicitSilentAndWithoutQuotes();

$args = $this->createArgsFromItems($values, $annotationToAttribute->getAttributeClass());

// @todo this can be a different class then the unwrapped crated one
//$argumentNames = $this->namedArgumentsResolver->resolveFromClass($annotationToAttribute->getAttributeClass());

$args = $this->attributeArrayNameInliner->inlineArrayToArgs($args);

$attributeName = $this->attributeNameFactory->create($annotationToAttribute, $doctrineAnnotationTagValueNode);
$attributeName = $this->attributeNameFactory->create(
$annotationToAttribute,
$doctrineAnnotationTagValueNode,
$uses
);

$attribute = new Attribute($attributeName, $args);
return new AttributeGroup([$attribute]);
Expand Down
32 changes: 32 additions & 0 deletions packages/PhpAttribute/ValueObject/UseAliasMetadata.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace Rector\PhpAttribute\ValueObject;

use PhpParser\Node\Stmt\UseUse;

final class UseAliasMetadata
{
public function __construct(
private readonly string $shortAttributeName,
private readonly string $useImportName,
private readonly UseUse $useUse
) {
}

public function getShortAttributeName(): string
{
return $this->shortAttributeName;
}

public function getUseImportName(): string
{
return $this->useImportName;
}

public function getUseUse(): UseUse
{
return $this->useUse;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

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

use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\Annotation\OpenApi as OA;

final class AliasUsedWithUsePartialRename
{
/**
* @OA\PastAnnotation
*/
public $value;
}

?>
-----
<?php

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

use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\Attribute\OpenApi as OA;

final class AliasUsedWithUsePartialRename
{
#[OA\FutureAttribute]
public $value;
}

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\Annotation\OpenApi;

final class PastAnnotation
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\Attribute\OpenApi;

final class FutureAttribute
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
use Rector\Core\ValueObject\PhpVersionFeature;
use Rector\Php80\Rector\Class_\AnnotationToAttributeRector;
use Rector\Php80\ValueObject\AnnotationToAttribute;
use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\Annotation\OpenApi\PastAnnotation;
use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\Attribute\OpenApi\FutureAttribute;
use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\GenericAnnotation;
use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\GenericSingleImplicitAnnotation;

Expand All @@ -14,6 +16,8 @@

$rectorConfig
->ruleWithConfiguration(AnnotationToAttributeRector::class, [
new AnnotationToAttribute(PastAnnotation::class, FutureAttribute::class),

// use always this annotation to test inner part of annotation - arguments, arrays, calls...
new AnnotationToAttribute(GenericAnnotation::class),
new AnnotationToAttribute(GenericSingleImplicitAnnotation::class),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

namespace Rector\Tests\Renaming\Rector\Name\RenameClassRector;
namespace Rector\Tests\Renaming\Rector\Name\RenameClassRector\Fixture;

class MyCustomValidatorTest extends \Rector\Tests\Renaming\Rector\Name\RenameClassRector\Source\OldClass
{
Expand All @@ -11,7 +11,7 @@ class MyCustomValidatorTest extends \Rector\Tests\Renaming\Rector\Name\RenameCla
-----
<?php

namespace Rector\Tests\Renaming\Rector\Name\RenameClassRector;
namespace Rector\Tests\Renaming\Rector\Name\RenameClassRector\Fixture;

class MyCustomValidatorTest extends \Rector\Tests\Renaming\Rector\Name\RenameClassRector\Source\NewClass
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
use Rector\Core\Contract\Rector\ConfigurableRectorInterface;
use Rector\Core\Rector\AbstractRector;
use Rector\DowngradePhp80\ValueObject\DowngradeAttributeToAnnotation;
use Rector\PhpAttribute\Printer\DoctrineAnnotationFactory;
use Rector\PhpAttribute\NodeFactory\DoctrineAnnotationFactory;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
use Webmozart\Assert\Assert;
Expand Down
16 changes: 16 additions & 0 deletions rules/Naming/Naming/UseImportsResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,20 @@ public function resolveForNode(Node $node): array
fn (Stmt $stmt): bool => $stmt instanceof Use_ || $stmt instanceof GroupUse
);
}

/**
* @return Use_[]
*/
public function resolveBareUsesForNode(Node $node): array
{
$namespace = $this->betterNodeFinder->findParentByTypes(
$node,
[Namespace_::class, FileWithoutNamespace::class]
);
if (! $namespace instanceof Node) {
return [];
}

return array_filter($namespace->stmts, fn (Stmt $stmt): bool => $stmt instanceof Use_);
}
}
7 changes: 5 additions & 2 deletions rules/Php80/NodeFactory/AttrGroupsFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
namespace Rector\Php80\NodeFactory;

use PhpParser\Node\AttributeGroup;
use PhpParser\Node\Stmt\Use_;
use Rector\Php80\ValueObject\DoctrineTagAndAnnotationToAttribute;
use Rector\PhpAttribute\Printer\PhpAttributeGroupFactory;
use Rector\PhpAttribute\NodeFactory\PhpAttributeGroupFactory;

final class AttrGroupsFactory
{
Expand All @@ -17,9 +18,10 @@ public function __construct(

/**
* @param DoctrineTagAndAnnotationToAttribute[] $doctrineTagAndAnnotationToAttributes
* @param Use_[] $uses
* @return AttributeGroup[]
*/
public function create(array $doctrineTagAndAnnotationToAttributes): array
public function create(array $doctrineTagAndAnnotationToAttributes, array $uses): array
{
$attributeGroups = [];

Expand All @@ -30,6 +32,7 @@ public function create(array $doctrineTagAndAnnotationToAttributes): array
$attributeGroups[] = $this->phpAttributeGroupFactory->create(
$doctrineAnnotationTagValueNode,
$doctrineTagAndAnnotationToAttribute->getAnnotationToAttribute(),
$uses
);
}

Expand Down

0 comments on commit 012e9ad

Please sign in to comment.