From f83d7441580e6175556328ac94dc7f128d2344aa Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sat, 20 Nov 2021 13:28:47 +0300 Subject: [PATCH] [PHP 8.1] Add nested attributes support - part #1 (#1266) --- .../Printer/PhpAttributeGroupFactoryTest.php | 1 - .../PhpDoc/DoctrineAnnotationTagValueNode.php | 2 +- .../PhpDocParser/BetterPhpDocParser.php | 1 + .../DoctrineAnnotationDecorator.php | 91 ++++++++++++++----- .../AbstractValuesAwareNode.php | 1 - .../InvalidNestedAttributeException.php | 11 +++ .../Printer/PhpAttributeGroupFactory.php | 20 ++-- .../PhpAttribute/Value/ValueNormalizer.php | 65 ++++++++++--- ...y_when_entity_attribute_not_exists.php.inc | 39 -------- .../FixturePhp81/nested_attributes.php.inc | 31 +++++++ .../nested_attributes_with_brackets.php.inc | 31 +++++++ .../Php81NestedAttributesRectorTest.php | 36 ++++++++ .../SourcePhp81/All.php | 12 +++ .../SourcePhp81/Length.php | 9 ++ .../SourcePhp81/NotNull.php | 9 ++ .../config/auto_import.php | 2 + .../config/configured_rule.php | 5 + .../config/nested_attributes_php81.php | 27 ++++++ .../DowngradeNewInInitializerRector.php | 2 + rules/Php80/NodeFactory/AttrGroupsFactory.php | 2 +- .../Class_/AnnotationToAttributeRector.php | 40 +++++--- .../DoctrineTagAndAnnotationToAttribute.php | 2 +- .../Doctrine/ORM/EntityManager.php | 14 --- .../Doctrine/ORM/EntityManagerInterface.php | 16 ---- .../Doctrine/ORM/EntityRepository.php | 23 ----- .../Annotations/Doctrine/ORM/QueryBuilder.php | 14 --- 26 files changed, 337 insertions(+), 169 deletions(-) create mode 100644 packages/PhpAttribute/Exception/InvalidNestedAttributeException.php delete mode 100644 rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/FixtureAutoImported/do_not_change_unique_entity_when_entity_attribute_not_exists.php.inc create mode 100644 rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/FixturePhp81/nested_attributes.php.inc create mode 100644 rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/FixturePhp81/nested_attributes_with_brackets.php.inc create mode 100644 rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/Php81NestedAttributesRectorTest.php create mode 100644 rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/SourcePhp81/All.php create mode 100644 rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/SourcePhp81/Length.php create mode 100644 rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/SourcePhp81/NotNull.php create mode 100644 rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/config/nested_attributes_php81.php delete mode 100644 stubs/Annotations/Doctrine/ORM/EntityManager.php delete mode 100644 stubs/Annotations/Doctrine/ORM/EntityManagerInterface.php delete mode 100644 stubs/Annotations/Doctrine/ORM/EntityRepository.php delete mode 100644 stubs/Annotations/Doctrine/ORM/QueryBuilder.php diff --git a/packages-tests/PhpAttribute/Printer/PhpAttributeGroupFactoryTest.php b/packages-tests/PhpAttribute/Printer/PhpAttributeGroupFactoryTest.php index edde527391d5..95f113ba7cea 100644 --- a/packages-tests/PhpAttribute/Printer/PhpAttributeGroupFactoryTest.php +++ b/packages-tests/PhpAttribute/Printer/PhpAttributeGroupFactoryTest.php @@ -16,7 +16,6 @@ final class PhpAttributeGroupFactoryTest extends AbstractTestCase protected function setUp(): void { $this->boot(); - $this->phpAttributeGroupFactory = $this->getService(PhpAttributeGroupFactory::class); } diff --git a/packages/BetterPhpDocParser/PhpDoc/DoctrineAnnotationTagValueNode.php b/packages/BetterPhpDocParser/PhpDoc/DoctrineAnnotationTagValueNode.php index b4772d9cadcd..6a60bbc5c7c9 100644 --- a/packages/BetterPhpDocParser/PhpDoc/DoctrineAnnotationTagValueNode.php +++ b/packages/BetterPhpDocParser/PhpDoc/DoctrineAnnotationTagValueNode.php @@ -69,7 +69,7 @@ public function hasClassName(string $className): bool return true; } - // the name is not fully qualified in the original name, look for resolvd class attirubte + // the name is not fully qualified in the original name, look for resolved class attribute $resolvedClass = $this->identifierTypeNode->getAttribute(PhpDocAttributeKey::RESOLVED_CLASS); return $resolvedClass === $className; } diff --git a/packages/BetterPhpDocParser/PhpDocParser/BetterPhpDocParser.php b/packages/BetterPhpDocParser/PhpDocParser/BetterPhpDocParser.php index 1cc39f673578..7c51cc7bb386 100644 --- a/packages/BetterPhpDocParser/PhpDocParser/BetterPhpDocParser.php +++ b/packages/BetterPhpDocParser/PhpDocParser/BetterPhpDocParser.php @@ -84,6 +84,7 @@ public function parseTagValue(TokenIterator $tokenIterator, string $tag): PhpDoc { $startPosition = $tokenIterator->currentPosition(); $tagValueNode = parent::parseTagValue($tokenIterator, $tag); + $endPosition = $tokenIterator->currentPosition(); $startAndEnd = new StartAndEnd($startPosition, $endPosition); diff --git a/packages/BetterPhpDocParser/PhpDocParser/DoctrineAnnotationDecorator.php b/packages/BetterPhpDocParser/PhpDocParser/DoctrineAnnotationDecorator.php index 657e953679da..52a5dab35a1b 100644 --- a/packages/BetterPhpDocParser/PhpDocParser/DoctrineAnnotationDecorator.php +++ b/packages/BetterPhpDocParser/PhpDocParser/DoctrineAnnotationDecorator.php @@ -7,6 +7,7 @@ use Nette\Utils\Strings; use PhpParser\Node; use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocChildNode; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTextNode; @@ -36,6 +37,12 @@ final class DoctrineAnnotationDecorator */ private const LONG_ANNOTATION_REGEX = '#@\\\\(?.*?)(?\(.*?\))#'; + /** + * @see https://regex101.com/r/xWaLOz/1 + * @var string + */ + private const NESTED_ANNOTATION_END_REGEX = '#(\s+)?\}\)(\s+)?#'; + public function __construct( private CurrentNodeProvider $currentNodeProvider, private ClassAnnotationMatcher $classAnnotationMatcher, @@ -54,7 +61,6 @@ public function decorate(PhpDocNode $phpDocNode): void // merge split doctrine nested tags $this->mergeNestedDoctrineAnnotations($phpDocNode); - $this->transformGenericTagValueNodesToDoctrineAnnotationTagValueNodes($phpDocNode, $currentPhpNode); } @@ -89,6 +95,24 @@ private function mergeNestedDoctrineAnnotations(PhpDocNode $phpDocNode): void } $nextPhpDocChildNode = $phpDocNode->children[$key]; + + if ($nextPhpDocChildNode instanceof PhpDocTextNode && Strings::match( + $nextPhpDocChildNode->text, + self::NESTED_ANNOTATION_END_REGEX + )) { + // @todo how to detect previously opened brackets? + // probably local property with holding count of opened brackets + $composedContent = $genericTagValueNode->value . PHP_EOL . $nextPhpDocChildNode->text; + $genericTagValueNode->value = $composedContent; + + $startAndEnd = $this->combineStartAndEnd($phpDocChildNode, $nextPhpDocChildNode); + $phpDocChildNode->setAttribute(PhpDocAttributeKey::START_AND_END, $startAndEnd); + + $removedKeys[] = $key; + $removedKeys[] = $key + 1; + continue; + } + if (! $nextPhpDocChildNode instanceof PhpDocTagNode) { continue; } @@ -101,16 +125,12 @@ private function mergeNestedDoctrineAnnotations(PhpDocNode $phpDocNode): void break; } - $composedContent = $genericTagValueNode->value . PHP_EOL . $nextPhpDocChildNode->name . $nextPhpDocChildNode->value; - $genericTagValueNode->value = $composedContent; + $composedContent = $genericTagValueNode->value . PHP_EOL . $nextPhpDocChildNode->name . $nextPhpDocChildNode->value->value; - /** @var StartAndEnd $currentStartAndEnd */ - $currentStartAndEnd = $phpDocChildNode->getAttribute(PhpDocAttributeKey::START_AND_END); - - /** @var StartAndEnd $nextStartAndEnd */ - $nextStartAndEnd = $nextPhpDocChildNode->getAttribute(PhpDocAttributeKey::START_AND_END); + // cleanup the next from closing + $genericTagValueNode->value = $composedContent; - $startAndEnd = new StartAndEnd($currentStartAndEnd->getStart(), $nextStartAndEnd->getEnd()); + $startAndEnd = $this->combineStartAndEnd($phpDocChildNode, $nextPhpDocChildNode); $phpDocChildNode->setAttribute(PhpDocAttributeKey::START_AND_END, $startAndEnd); $currentChildValueNode = $phpDocNode->children[$key]; @@ -143,25 +163,11 @@ private function transformGenericTagValueNodesToDoctrineAnnotationTagValueNodes( foreach ($phpDocNode->children as $key => $phpDocChildNode) { // the @\FQN use case if ($phpDocChildNode instanceof PhpDocTextNode) { - $match = Strings::match($phpDocChildNode->text, self::LONG_ANNOTATION_REGEX); - $fullyQualifiedAnnotationClass = $match['class_name'] ?? null; - - if ($fullyQualifiedAnnotationClass === null) { + $spacelessPhpDocTagNode = $this->resolveFqnAnnotationSpacelessPhpDocTagNode($phpDocChildNode); + if (! $spacelessPhpDocTagNode instanceof SpacelessPhpDocTagNode) { continue; } - $annotationContent = $match['annotation_content'] ?? null; - $tagName = '@\\' . $fullyQualifiedAnnotationClass; - - $formerStartEnd = $phpDocChildNode->getAttribute(PhpDocAttributeKey::START_AND_END); - - $spacelessPhpDocTagNode = $this->createDoctrineSpacelessPhpDocTagNode( - $annotationContent, - $tagName, - $fullyQualifiedAnnotationClass, - $formerStartEnd - ); - $phpDocNode->children[$key] = $spacelessPhpDocTagNode; continue; } @@ -280,4 +286,39 @@ private function createDoctrineSpacelessPhpDocTagNode( return new SpacelessPhpDocTagNode($tagName, $doctrineAnnotationTagValueNode); } + + private function combineStartAndEnd( + \PHPStan\PhpDocParser\Ast\Node $startPhpDocChildNode, + PhpDocChildNode $endPhpDocChildNode + ): StartAndEnd { + /** @var StartAndEnd $currentStartAndEnd */ + $currentStartAndEnd = $startPhpDocChildNode->getAttribute(PhpDocAttributeKey::START_AND_END); + + /** @var StartAndEnd $nextStartAndEnd */ + $nextStartAndEnd = $endPhpDocChildNode->getAttribute(PhpDocAttributeKey::START_AND_END); + + return new StartAndEnd($currentStartAndEnd->getStart(), $nextStartAndEnd->getEnd()); + } + + private function resolveFqnAnnotationSpacelessPhpDocTagNode(PhpDocTextNode $phpDocTextNode): ?SpacelessPhpDocTagNode + { + $match = Strings::match($phpDocTextNode->text, self::LONG_ANNOTATION_REGEX); + $fullyQualifiedAnnotationClass = $match['class_name'] ?? null; + + if ($fullyQualifiedAnnotationClass === null) { + return null; + } + + $annotationContent = $match['annotation_content'] ?? null; + $tagName = '@\\' . $fullyQualifiedAnnotationClass; + + $formerStartEnd = $phpDocTextNode->getAttribute(PhpDocAttributeKey::START_AND_END); + + return $this->createDoctrineSpacelessPhpDocTagNode( + $annotationContent, + $tagName, + $fullyQualifiedAnnotationClass, + $formerStartEnd + ); + } } diff --git a/packages/BetterPhpDocParser/ValueObject/PhpDoc/DoctrineAnnotation/AbstractValuesAwareNode.php b/packages/BetterPhpDocParser/ValueObject/PhpDoc/DoctrineAnnotation/AbstractValuesAwareNode.php index 01ca76e7cc90..1f12a311433e 100644 --- a/packages/BetterPhpDocParser/ValueObject/PhpDoc/DoctrineAnnotation/AbstractValuesAwareNode.php +++ b/packages/BetterPhpDocParser/ValueObject/PhpDoc/DoctrineAnnotation/AbstractValuesAwareNode.php @@ -148,7 +148,6 @@ public function getValuesWithExplicitSilentAndWithoutQuotes(): array foreach (array_keys($this->values) as $key) { $valueWithoutQuotes = $this->getValueWithoutQuotes($key); - if (is_int($key) && $this->silentKey !== null) { $explicitKeysValues[$this->silentKey] = $valueWithoutQuotes; } else { diff --git a/packages/PhpAttribute/Exception/InvalidNestedAttributeException.php b/packages/PhpAttribute/Exception/InvalidNestedAttributeException.php new file mode 100644 index 000000000000..d92fe136693e --- /dev/null +++ b/packages/PhpAttribute/Exception/InvalidNestedAttributeException.php @@ -0,0 +1,11 @@ +getAttributeClass()); - $values = $doctrineAnnotationTagValueNode->getValuesWithExplicitSilentAndWithoutQuotes(); $args = $this->createArgsFromItems($values); @@ -80,18 +79,14 @@ public function createArgsFromItems(array $items, ?string $silentKey = null): ar { $args = []; if ($silentKey !== null && isset($items[$silentKey])) { - $silentValue = BuilderHelpers::normalizeValue($items[$silentKey]); - $this->normalizeStringDoubleQuote($silentValue); + $silentValue = $this->mapAnnotationValueToAttribute($items[$silentKey]); $args[] = new Arg($silentValue); unset($items[$silentKey]); } foreach ($items as $key => $value) { - $value = $this->valueNormalizer->normalize($value); - $value = BuilderHelpers::normalizeValue($value); - - $this->normalizeStringDoubleQuote($value); + $value = $this->mapAnnotationValueToAttribute($value); $name = null; if (is_string($key)) { @@ -161,4 +156,13 @@ private function completeNamedArguments(array $args, array $argumentNames): void $arg->name = new Identifier($argumentName); } } + + private function mapAnnotationValueToAttribute(mixed $annotationValue): Expr + { + $value = $this->valueNormalizer->normalize($annotationValue); + $value = BuilderHelpers::normalizeValue($value); + $this->normalizeStringDoubleQuote($value); + + return $value; + } } diff --git a/packages/PhpAttribute/Value/ValueNormalizer.php b/packages/PhpAttribute/Value/ValueNormalizer.php index dd6adcc79401..b2daa05b348d 100644 --- a/packages/PhpAttribute/Value/ValueNormalizer.php +++ b/packages/PhpAttribute/Value/ValueNormalizer.php @@ -6,37 +6,43 @@ use PhpParser\Node\Expr; use PhpParser\Node\Expr\ClassConstFetch; +use PhpParser\Node\Expr\New_; use PhpParser\Node\Name; +use PhpParser\Node\Name\FullyQualified; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprFalseNode; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode; +use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprNode; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprTrueNode; use PHPStan\PhpDocParser\Ast\Node; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantFloatType; +use Rector\BetterPhpDocParser\PhpDoc\DoctrineAnnotationTagValueNode; use Rector\BetterPhpDocParser\ValueObject\PhpDoc\DoctrineAnnotation\CurlyListNode; +use Rector\BetterPhpDocParser\ValueObject\PhpDocAttributeKey; +use Rector\Core\Exception\ShouldNotHappenException; +use Rector\Core\Php\PhpVersionProvider; +use Rector\Core\ValueObject\PhpVersionFeature; +use Rector\PhpAttribute\Exception\InvalidNestedAttributeException; final class ValueNormalizer { + public function __construct( + private PhpVersionProvider $phpVersionProvider + ) { + } + /** * @param mixed $value * @return array */ public function normalize($value): bool | float | int | string | array | Expr { - if ($value instanceof ConstExprIntegerNode) { - return (int) $value->value; + if ($value instanceof DoctrineAnnotationTagValueNode) { + return $this->normalizeDoctrineAnnotationTagValueNode($value); } - if ($value instanceof ConstantFloatType || $value instanceof ConstantBooleanType) { - return $value->getValue(); - } - - if ($value instanceof ConstExprTrueNode) { - return true; - } - - if ($value instanceof ConstExprFalseNode) { - return false; + if ($value instanceof ConstExprNode) { + return $this->normalizeConstrExprNode($value); } if ($value instanceof CurlyListNode) { @@ -62,4 +68,39 @@ public function normalize($value): bool | float | int | string | array | Expr return $value; } + + private function normalizeDoctrineAnnotationTagValueNode( + DoctrineAnnotationTagValueNode $doctrineAnnotationTagValueNode + ): New_ { + // if PHP 8.0- throw exception + if (! $this->phpVersionProvider->isAtLeastPhpVersion(PhpVersionFeature::NEW_INITIALIZERS)) { + throw new InvalidNestedAttributeException(); + } + + $resolveClass = $doctrineAnnotationTagValueNode->identifierTypeNode->getAttribute( + PhpDocAttributeKey::RESOLVED_CLASS + ); + return new New_(new FullyQualified($resolveClass)); + } + + private function normalizeConstrExprNode(ConstExprNode $constExprNode): int|bool|float + { + if ($constExprNode instanceof ConstExprIntegerNode) { + return (int) $constExprNode->value; + } + + if ($constExprNode instanceof ConstantFloatType || $constExprNode instanceof ConstantBooleanType) { + return $constExprNode->getValue(); + } + + if ($constExprNode instanceof ConstExprTrueNode) { + return true; + } + + if ($constExprNode instanceof ConstExprFalseNode) { + return false; + } + + throw new ShouldNotHappenException(); + } } diff --git a/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/FixtureAutoImported/do_not_change_unique_entity_when_entity_attribute_not_exists.php.inc b/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/FixtureAutoImported/do_not_change_unique_entity_when_entity_attribute_not_exists.php.inc deleted file mode 100644 index 0f0237fa8447..000000000000 --- a/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/FixtureAutoImported/do_not_change_unique_entity_when_entity_attribute_not_exists.php.inc +++ /dev/null @@ -1,39 +0,0 @@ - ------ - diff --git a/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/FixturePhp81/nested_attributes.php.inc b/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/FixturePhp81/nested_attributes.php.inc new file mode 100644 index 000000000000..11acc076ed89 --- /dev/null +++ b/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/FixturePhp81/nested_attributes.php.inc @@ -0,0 +1,31 @@ + +----- + diff --git a/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/FixturePhp81/nested_attributes_with_brackets.php.inc b/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/FixturePhp81/nested_attributes_with_brackets.php.inc new file mode 100644 index 000000000000..4034a17e3b21 --- /dev/null +++ b/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/FixturePhp81/nested_attributes_with_brackets.php.inc @@ -0,0 +1,31 @@ + +----- + diff --git a/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/Php81NestedAttributesRectorTest.php b/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/Php81NestedAttributesRectorTest.php new file mode 100644 index 000000000000..75c376aaff0f --- /dev/null +++ b/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/Php81NestedAttributesRectorTest.php @@ -0,0 +1,36 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/FixturePhp81'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/nested_attributes_php81.php'; + } +} diff --git a/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/SourcePhp81/All.php b/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/SourcePhp81/All.php new file mode 100644 index 000000000000..a7035b071f37 --- /dev/null +++ b/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/SourcePhp81/All.php @@ -0,0 +1,12 @@ +parameters(); $parameters->set(Option::AUTO_IMPORT_NAMES, true); + $parameters->set(Option::PHP_VERSION_FEATURES, PhpVersionFeature::NEW_INITIALIZERS - 1); $services = $containerConfigurator->services(); $services->set(AnnotationToAttributeRector::class) diff --git a/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/config/configured_rule.php b/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/config/configured_rule.php index a1fdcea65c34..c4939a504e7a 100644 --- a/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/config/configured_rule.php +++ b/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/config/configured_rule.php @@ -2,6 +2,8 @@ declare(strict_types=1); +use Rector\Core\Configuration\Option; +use Rector\Core\ValueObject\PhpVersionFeature; use Rector\Php80\Rector\Class_\AnnotationToAttributeRector; use Rector\Php80\ValueObject\AnnotationToAttribute; use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\GenericAnnotation; @@ -10,6 +12,9 @@ use Symplify\SymfonyPhpConfig\ValueObjectInliner; return static function (ContainerConfigurator $containerConfigurator): void { + $parameters = $containerConfigurator->parameters(); + $parameters->set(Option::PHP_VERSION_FEATURES, PhpVersionFeature::NEW_INITIALIZERS - 1); + $services = $containerConfigurator->services(); $services->set(AnnotationToAttributeRector::class) diff --git a/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/config/nested_attributes_php81.php b/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/config/nested_attributes_php81.php new file mode 100644 index 000000000000..f19e3473f2bc --- /dev/null +++ b/rules-tests/Php80/Rector/Class_/AnnotationToAttributeRector/config/nested_attributes_php81.php @@ -0,0 +1,27 @@ +parameters(); + $parameters->set(Option::PHP_VERSION_FEATURES, PhpVersionFeature::NEW_INITIALIZERS); + + $services = $containerConfigurator->services(); + $services->set(AnnotationToAttributeRector::class) + ->call('configure', [[ + AnnotationToAttributeRector::ANNOTATION_TO_ATTRIBUTE => ValueObjectInliner::inline([ + new AnnotationToAttribute(All::class), + new AnnotationToAttribute(Length::class), + ]), + ]]); +}; diff --git a/rules/DowngradePhp81/Rector/FunctionLike/DowngradeNewInInitializerRector.php b/rules/DowngradePhp81/Rector/FunctionLike/DowngradeNewInInitializerRector.php index 6430c332ec6c..567626e5affb 100644 --- a/rules/DowngradePhp81/Rector/FunctionLike/DowngradeNewInInitializerRector.php +++ b/rules/DowngradePhp81/Rector/FunctionLike/DowngradeNewInInitializerRector.php @@ -98,9 +98,11 @@ private function shouldSkip(FunctionLike $functionLike): bool if (! $param->default instanceof New_) { continue; } + if ($param->type instanceof IntersectionType) { continue; } + return false; } diff --git a/rules/Php80/NodeFactory/AttrGroupsFactory.php b/rules/Php80/NodeFactory/AttrGroupsFactory.php index 55e9d5688231..a9ea128df149 100644 --- a/rules/Php80/NodeFactory/AttrGroupsFactory.php +++ b/rules/Php80/NodeFactory/AttrGroupsFactory.php @@ -29,7 +29,7 @@ public function create(array $doctrineTagAndAnnotationToAttributes): array // add attributes $attributeGroups[] = $this->phpAttributeGroupFactory->create( $doctrineAnnotationTagValueNode, - $doctrineTagAndAnnotationToAttribute->getAnnotationToAttribute() + $doctrineTagAndAnnotationToAttribute->getAnnotationToAttribute(), ); } diff --git a/rules/Php80/Rector/Class_/AnnotationToAttributeRector.php b/rules/Php80/Rector/Class_/AnnotationToAttributeRector.php index 7af8ac8ca580..d2181751e368 100644 --- a/rules/Php80/Rector/Class_/AnnotationToAttributeRector.php +++ b/rules/Php80/Rector/Class_/AnnotationToAttributeRector.php @@ -219,32 +219,46 @@ private function processDoctrineAnnotationClasses(PhpDocInfo $phpDocInfo): array continue; } + $doctrineTagValueNode = $phpDocChildNode->value; + $annotationToAttribute = $this->matchAnnotationToAttribute($doctrineTagValueNode); + if (! $annotationToAttribute instanceof AnnotationToAttribute) { + continue; + } + $nestedDoctrineAnnotationTagValueNodes = $this->phpDocNodeFinder->findByType( - $phpDocChildNode->value, + $doctrineTagValueNode, DoctrineAnnotationTagValueNode::class ); // depends on PHP 8.1+ - nested values, skip for now - if ($nestedDoctrineAnnotationTagValueNodes !== []) { + if ($nestedDoctrineAnnotationTagValueNodes !== [] && ! $this->phpVersionProvider->isAtLeastPhpVersion( + PhpVersionFeature::NEW_INITIALIZERS + )) { continue; } - $doctrineTagValueNode = $phpDocChildNode->value; + $doctrineTagAndAnnotationToAttributes[] = new DoctrineTagAndAnnotationToAttribute( + $doctrineTagValueNode, + $annotationToAttribute, + ); - foreach ($this->annotationsToAttributes as $annotationToAttribute) { - if (! $doctrineTagValueNode->hasClassName($annotationToAttribute->getTag())) { - continue; - } + $this->phpDocTagRemover->removeTagValueFromNode($phpDocInfo, $doctrineTagValueNode); + } - $doctrineTagAndAnnotationToAttributes[] = new DoctrineTagAndAnnotationToAttribute( - $phpDocChildNode->value, - $annotationToAttribute - ); + return $this->attrGroupsFactory->create($doctrineTagAndAnnotationToAttributes); + } - $this->phpDocTagRemover->removeTagValueFromNode($phpDocInfo, $doctrineTagValueNode); + private function matchAnnotationToAttribute( + DoctrineAnnotationTagValueNode $doctrineAnnotationTagValueNode + ): AnnotationToAttribute|null { + foreach ($this->annotationsToAttributes as $annotationToAttribute) { + if (! $doctrineAnnotationTagValueNode->hasClassName($annotationToAttribute->getTag())) { + continue; } + + return $annotationToAttribute; } - return $this->attrGroupsFactory->create($doctrineTagAndAnnotationToAttributes); + return null; } } diff --git a/rules/Php80/ValueObject/DoctrineTagAndAnnotationToAttribute.php b/rules/Php80/ValueObject/DoctrineTagAndAnnotationToAttribute.php index 56e7a201e142..9f809a6bb37a 100644 --- a/rules/Php80/ValueObject/DoctrineTagAndAnnotationToAttribute.php +++ b/rules/Php80/ValueObject/DoctrineTagAndAnnotationToAttribute.php @@ -10,7 +10,7 @@ final class DoctrineTagAndAnnotationToAttribute { public function __construct( private DoctrineAnnotationTagValueNode $doctrineAnnotationTagValueNode, - private AnnotationToAttribute $annotationToAttribute + private AnnotationToAttribute $annotationToAttribute, ) { } diff --git a/stubs/Annotations/Doctrine/ORM/EntityManager.php b/stubs/Annotations/Doctrine/ORM/EntityManager.php deleted file mode 100644 index 918bb4ee2a2a..000000000000 --- a/stubs/Annotations/Doctrine/ORM/EntityManager.php +++ /dev/null @@ -1,14 +0,0 @@ -