diff --git a/.phpstorm.meta.php b/.phpstorm.meta.php index bd680a516dc3..55e60fc3cd3c 100644 --- a/.phpstorm.meta.php +++ b/.phpstorm.meta.php @@ -38,6 +38,7 @@ \Rector\NodeTypeResolver\Node\AttributeKey::START_TOKEN_POSITION, \Rector\NodeTypeResolver\Node\AttributeKey::ORIGINAL_NODE, \Rector\NodeTypeResolver\Node\AttributeKey::IS_UNREACHABLE, + \Rector\NodeTypeResolver\Node\AttributeKey::PHP_DOC_INFO, ); expectedArguments( @@ -61,4 +62,5 @@ \Rector\NodeTypeResolver\Node\AttributeKey::START_TOKEN_POSITION, \Rector\NodeTypeResolver\Node\AttributeKey::ORIGINAL_NODE, \Rector\NodeTypeResolver\Node\AttributeKey::IS_UNREACHABLE, + \Rector\NodeTypeResolver\Node\AttributeKey::PHP_DOC_INFO, ); diff --git a/ecs.yaml b/ecs.yaml index da911fbca0d6..04467c01588b 100644 --- a/ecs.yaml +++ b/ecs.yaml @@ -77,6 +77,7 @@ parameters: Symplify\CodingStandard\Sniffs\CleanCode\CognitiveComplexitySniff: - 'packages/MinimalScope/src/Rector/Class_/ChangeLocalPropertyToVariableRector.php' + - 'packages/CodingStyle/src/Rector/ClassMethod/NewlineBeforeNewAssignSetRector.php' # solve later - 'src/Console/Command/ScreenFileCommand.php' - 'packages/Doctrine/src/Rector/ClassMethod/AddMethodCallBasedParamTypeRector.php' diff --git a/packages/Autodiscovery/src/Rector/FileSystem/MoveEntitiesToEntityDirectoryRector.php b/packages/Autodiscovery/src/Rector/FileSystem/MoveEntitiesToEntityDirectoryRector.php index 034f99e56c0e..7d737990f816 100644 --- a/packages/Autodiscovery/src/Rector/FileSystem/MoveEntitiesToEntityDirectoryRector.php +++ b/packages/Autodiscovery/src/Rector/FileSystem/MoveEntitiesToEntityDirectoryRector.php @@ -10,6 +10,7 @@ use Rector\Autodiscovery\FileMover\FileMover; use Rector\BetterPhpDocParser\PhpDocNode\Doctrine\Class_\EntityTagValueNode; use Rector\FileSystemRector\Rector\AbstractFileSystemRector; +use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\RectorDefinition\CodeSample; use Rector\RectorDefinition\RectorDefinition; use Symplify\SmartFileSystem\SmartFileInfo; @@ -102,7 +103,7 @@ private function areDoctrineEntityNodes(array $nodes): bool return false; } - $phpDocInfo = $this->getPhpDocInfo($class); + $phpDocInfo = $class->getAttribute(AttributeKey::PHP_DOC_INFO); if ($phpDocInfo === null) { return false; } diff --git a/packages/BetterPhpDocParser/src/Attributes/Attribute/Attribute.php b/packages/BetterPhpDocParser/src/Attributes/Attribute/Attribute.php index fd54af695844..097334dd40d7 100644 --- a/packages/BetterPhpDocParser/src/Attributes/Attribute/Attribute.php +++ b/packages/BetterPhpDocParser/src/Attributes/Attribute/Attribute.php @@ -4,6 +4,8 @@ namespace Rector\BetterPhpDocParser\Attributes\Attribute; +use Rector\BetterPhpDocParser\ValueObject\StartEndValueObject; + final class Attribute { /** @@ -12,9 +14,10 @@ final class Attribute public const HAS_DESCRIPTION_WITH_ORIGINAL_SPACES = 'has_description_with_restored_spaces'; /** + * @experiment * @var string */ - public const PHP_DOC_NODE_INFO = 'php_doc_node_info'; + public const START_END = StartEndValueObject::class; /** * @var string diff --git a/packages/BetterPhpDocParser/src/PhpDocInfo/PhpDocInfo.php b/packages/BetterPhpDocParser/src/PhpDocInfo/PhpDocInfo.php index 493b4e9a5692..da521eb8f3fc 100644 --- a/packages/BetterPhpDocParser/src/PhpDocInfo/PhpDocInfo.php +++ b/packages/BetterPhpDocParser/src/PhpDocInfo/PhpDocInfo.php @@ -6,6 +6,7 @@ use Nette\Utils\Strings; use PhpParser\Node; +use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocChildNode; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; @@ -16,7 +17,9 @@ use Rector\AttributeAwarePhpDoc\Ast\PhpDoc\AttributeAwareReturnTagValueNode; use Rector\AttributeAwarePhpDoc\Ast\PhpDoc\AttributeAwareVarTagValueNode; use Rector\BetterPhpDocParser\Annotation\AnnotationNaming; +use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\SpacelessPhpDocTagNode; use Rector\BetterPhpDocParser\Contract\PhpDocNode\AttributeAwareNodeInterface; +use Rector\BetterPhpDocParser\PhpDocNode\AbstractTagValueNode; use Rector\Exception\ShouldNotHappenException; use Rector\NodeTypeResolver\StaticTypeMapper; @@ -78,6 +81,17 @@ public function getOriginalContent(): string return $this->originalContent; } + public function addPhpDocTagNode(PhpDocChildNode $phpDocChildNode): void + { + $this->phpDocNode->children[] = $phpDocChildNode; + } + + public function addTagValueNodeWithShortName(AbstractTagValueNode $tagValueNode): void + { + $spacelessPhpDocTagNode = new SpacelessPhpDocTagNode($tagValueNode::SHORT_NAME, $tagValueNode); + $this->addPhpDocTagNode($spacelessPhpDocTagNode); + } + public function getPhpDocNode(): AttributeAwarePhpDocNode { return $this->phpDocNode; diff --git a/packages/BetterPhpDocParser/src/PhpDocInfo/PhpDocInfoFactory.php b/packages/BetterPhpDocParser/src/PhpDocInfo/PhpDocInfoFactory.php index 403c87ef927f..98e68fa4fb22 100644 --- a/packages/BetterPhpDocParser/src/PhpDocInfo/PhpDocInfoFactory.php +++ b/packages/BetterPhpDocParser/src/PhpDocInfo/PhpDocInfoFactory.php @@ -5,6 +5,7 @@ namespace Rector\BetterPhpDocParser\PhpDocInfo; use PhpParser\Node; +use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode; use PHPStan\PhpDocParser\Lexer\Lexer; use PHPStan\PhpDocParser\Parser\PhpDocParser; use PHPStan\PhpDocParser\Parser\TokenIterator; @@ -12,8 +13,9 @@ use Rector\BetterPhpDocParser\Attributes\Attribute\Attribute; use Rector\BetterPhpDocParser\Contract\PhpDocNode\AttributeAwareNodeInterface; use Rector\BetterPhpDocParser\Contract\PhpDocNodeFactoryInterface; +use Rector\BetterPhpDocParser\ValueObject\StartEndValueObject; use Rector\Configuration\CurrentNodeProvider; -use Rector\Exception\ShouldNotHappenException; +use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\NodeTypeResolver\StaticTypeMapper; final class PhpDocInfoFactory @@ -38,11 +40,6 @@ final class PhpDocInfoFactory */ private $staticTypeMapper; - /** - * @var PhpDocInfo[] - */ - private $phpDocInfoByObjectHash = []; - public function __construct( PhpDocParser $phpDocParser, Lexer $lexer, @@ -57,41 +54,30 @@ public function __construct( public function createFromNode(Node $node): PhpDocInfo { - $hash = $this->createUniqueDocNodeHash($node); - - if (isset($this->phpDocInfoByObjectHash[$hash])) { - return $this->phpDocInfoByObjectHash[$hash]; - } - /** needed for @see PhpDocNodeFactoryInterface */ $this->currentNodeProvider->setNode($node); - $content = $node->getDocComment()->getText(); - $tokens = $this->lexer->tokenize($content); + if ($node->getDocComment() === null) { + $content = ''; + $tokens = []; + $phpDocNode = new AttributeAwarePhpDocNode([]); + } else { + $content = $node->getDocComment()->getText(); - $tokenIterator = new TokenIterator($tokens); + $tokens = $this->lexer->tokenize($content); + $tokenIterator = new TokenIterator($tokens); - /** @var AttributeAwarePhpDocNode $phpDocNode */ - $phpDocNode = $this->phpDocParser->parse($tokenIterator); - $phpDocNode = $this->setPositionOfLastToken($phpDocNode); + /** @var AttributeAwarePhpDocNode $phpDocNode */ + $phpDocNode = $this->phpDocParser->parse($tokenIterator); + $phpDocNode = $this->setPositionOfLastToken($phpDocNode); + } $phpDocInfo = new PhpDocInfo($phpDocNode, $tokens, $content, $this->staticTypeMapper, $node); - $this->phpDocInfoByObjectHash[$hash] = $phpDocInfo; + $node->setAttribute(AttributeKey::PHP_DOC_INFO, $phpDocInfo); return $phpDocInfo; } - private function createUniqueDocNodeHash(Node $node): string - { - $this->ensureNodeHasDocComment($node); - - $objectHash = spl_object_hash($node); - $docCommentHash = spl_object_hash($node->getDocComment()); - $docCommentContentHash = sha1($node->getDocComment()->getText()); - - return $objectHash . $docCommentHash . $docCommentContentHash; - } - /** * Needed for printing */ @@ -106,20 +92,13 @@ private function setPositionOfLastToken( /** @var AttributeAwareNodeInterface $lastChildNode */ $lastChildNode = array_pop($phpDocChildNodes); - $phpDocNodeInfo = $lastChildNode->getAttribute(Attribute::PHP_DOC_NODE_INFO); - if ($phpDocNodeInfo !== null) { - $attributeAwarePhpDocNode->setAttribute(Attribute::LAST_TOKEN_POSITION, $phpDocNodeInfo->getEnd()); - } + /** @var StartEndValueObject $startEndValueObject */ + $startEndValueObject = $lastChildNode->getAttribute(Attribute::START_END); - return $attributeAwarePhpDocNode; - } - - private function ensureNodeHasDocComment(Node $node): void - { - if ($node->getDocComment() !== null) { - return; + if ($startEndValueObject !== null) { + $attributeAwarePhpDocNode->setAttribute(Attribute::LAST_TOKEN_POSITION, $startEndValueObject->getEnd()); } - throw new ShouldNotHappenException(sprintf('"%s" is missing a DocComment node', get_class($node))); + return $attributeAwarePhpDocNode; } } diff --git a/packages/BetterPhpDocParser/src/PhpDocNode/AbstractTagValueNode.php b/packages/BetterPhpDocParser/src/PhpDocNode/AbstractTagValueNode.php index b3e3a8bcbcfd..58f24af02909 100644 --- a/packages/BetterPhpDocParser/src/PhpDocNode/AbstractTagValueNode.php +++ b/packages/BetterPhpDocParser/src/PhpDocNode/AbstractTagValueNode.php @@ -31,6 +31,11 @@ abstract class AbstractTagValueNode implements AttributeAwareNodeInterface, PhpD */ protected $hasNewlineAfterOpening = false; + /** + * @var string|null + */ + protected $originalContent; + /** * @param mixed[] $item */ @@ -60,6 +65,10 @@ protected function printContentItems(array $contentItems): string } if ($contentItems === []) { + if ($this->originalContent !== null && Strings::endsWith($this->originalContent, '()')) { + return '()'; + } + return ''; } @@ -131,6 +140,7 @@ protected function resolveOriginalContentSpacingAndOrder(?string $originalConten return; } + $this->originalContent = $originalContent; $this->orderedVisibleItems = ArrayItemStaticHelper::resolveAnnotationItemsOrder($originalContent); $this->hasNewlineAfterOpening = (bool) Strings::match($originalContent, '#^\(\s+#m'); $this->hasNewlineBeforeClosing = (bool) Strings::match($originalContent, '#\s+\)$#m'); diff --git a/packages/BetterPhpDocParser/src/PhpDocNode/Doctrine/Class_/InheritanceTypeTagValueNode.php b/packages/BetterPhpDocParser/src/PhpDocNode/Doctrine/Class_/InheritanceTypeTagValueNode.php index 20018d95cfdd..50aba48ad885 100644 --- a/packages/BetterPhpDocParser/src/PhpDocNode/Doctrine/Class_/InheritanceTypeTagValueNode.php +++ b/packages/BetterPhpDocParser/src/PhpDocNode/Doctrine/Class_/InheritanceTypeTagValueNode.php @@ -18,9 +18,10 @@ final class InheritanceTypeTagValueNode extends AbstractDoctrineTagValueNode */ private $value; - public function __construct(?string $value) + public function __construct(?string $value, ?string $originalContent) { $this->value = $value; + $this->resolveOriginalContentSpacingAndOrder($originalContent); } public function __toString(): string @@ -29,6 +30,10 @@ public function __toString(): string return ''; } - return '("' . $this->value . '")'; + if ($this->originalContent && ! in_array('value', (array) $this->orderedVisibleItems, true)) { + return '("' . $this->value . '")'; + } + + return '(value="' . $this->value . '")'; } } diff --git a/packages/BetterPhpDocParser/src/PhpDocNode/Doctrine/Class_/TableTagValueNode.php b/packages/BetterPhpDocParser/src/PhpDocNode/Doctrine/Class_/TableTagValueNode.php index a934d46c7003..070ff9c12f63 100644 --- a/packages/BetterPhpDocParser/src/PhpDocNode/Doctrine/Class_/TableTagValueNode.php +++ b/packages/BetterPhpDocParser/src/PhpDocNode/Doctrine/Class_/TableTagValueNode.php @@ -110,7 +110,11 @@ public function __toString(): string $contentItems = []; if ($this->name !== null) { - $contentItems['name'] = sprintf('name="%s"', $this->name); + if ($this->originalContent !== null && ! in_array('name', (array) $this->orderedVisibleItems, true)) { + $contentItems[] = '"' . $this->name . '"'; + } else { + $contentItems['name'] = sprintf('name="%s"', $this->name); + } } if ($this->schema !== null) { diff --git a/packages/BetterPhpDocParser/src/PhpDocNode/Doctrine/Property_/JoinColumnTagValueNode.php b/packages/BetterPhpDocParser/src/PhpDocNode/Doctrine/Property_/JoinColumnTagValueNode.php index c1a2a574a312..69d68b749dc6 100644 --- a/packages/BetterPhpDocParser/src/PhpDocNode/Doctrine/Property_/JoinColumnTagValueNode.php +++ b/packages/BetterPhpDocParser/src/PhpDocNode/Doctrine/Property_/JoinColumnTagValueNode.php @@ -83,10 +83,6 @@ public function __toString(): string { $contentItems = []; - if ($this->nullable !== null) { - $contentItems['nullable'] = sprintf('nullable=%s', $this->nullable ? 'true' : 'false'); - } - if ($this->name) { $contentItems['name'] = sprintf('name="%s"', $this->name); } @@ -95,7 +91,12 @@ public function __toString(): string $contentItems['referencedColumnName'] = sprintf('referencedColumnName="%s"', $this->referencedColumnName); } - if ($this->unique !== null) { + if ($this->nullable !== null) { + $contentItems['nullable'] = sprintf('nullable=%s', $this->nullable ? 'true' : 'false'); + } + + // skip default value + if ($this->unique !== null && $this->unique) { $contentItems['unique'] = sprintf('unique=%s', $this->unique ? 'true' : 'false'); } @@ -134,13 +135,13 @@ public function isNullable(): ?bool return $this->nullable; } - public function changeName(string $newName): void + public function getTag(): ?string { - $this->name = $newName; + return $this->tag ?: self::SHORT_NAME; } - public function getTag(): ?string + public function getUnique(): ?bool { - return $this->tag ?: self::SHORT_NAME; + return $this->unique; } } diff --git a/packages/BetterPhpDocParser/src/PhpDocNode/JMS/JMSInjectTagValueNode.php b/packages/BetterPhpDocParser/src/PhpDocNode/JMS/JMSInjectTagValueNode.php index 2867d7d66dd5..0b3193994789 100644 --- a/packages/BetterPhpDocParser/src/PhpDocNode/JMS/JMSInjectTagValueNode.php +++ b/packages/BetterPhpDocParser/src/PhpDocNode/JMS/JMSInjectTagValueNode.php @@ -46,14 +46,15 @@ public function __toString(): string $itemContents = []; if ($this->serviceName) { - $itemContents[] = $this->serviceName; + $itemContents[] = '"' . $this->serviceName . '"'; } if ($this->required !== null) { $itemContents[] = sprintf('required=%s', $this->required ? 'true' : 'false'); } - if ($this->strict !== null) { + // skip default + if (! $this->strict) { $itemContents[] = sprintf('strict=%s', $this->strict ? 'true' : 'false'); } @@ -61,9 +62,7 @@ public function __toString(): string return ''; } - $stringContent = implode(', ', $itemContents); - - return '(' . $stringContent . ')'; + return $this->printContentItems($itemContents); } public function getServiceName(): ?string diff --git a/packages/BetterPhpDocParser/src/PhpDocNodeFactory/Doctrine/Class_/InheritanceTypePhpDocNodeFactory.php b/packages/BetterPhpDocParser/src/PhpDocNodeFactory/Doctrine/Class_/InheritanceTypePhpDocNodeFactory.php index 9368b25a2da4..e80facb26784 100644 --- a/packages/BetterPhpDocParser/src/PhpDocNodeFactory/Doctrine/Class_/InheritanceTypePhpDocNodeFactory.php +++ b/packages/BetterPhpDocParser/src/PhpDocNodeFactory/Doctrine/Class_/InheritanceTypePhpDocNodeFactory.php @@ -32,6 +32,8 @@ public function createFromNodeAndTokens(Node $node, TokenIterator $tokenIterator return null; } - return new InheritanceTypeTagValueNode($inheritanceType->value); + $annotationContent = $this->resolveContentFromTokenIterator($tokenIterator); + + return new InheritanceTypeTagValueNode($inheritanceType->value, $annotationContent); } } diff --git a/packages/BetterPhpDocParser/src/PhpDocParser/BetterPhpDocParser.php b/packages/BetterPhpDocParser/src/PhpDocParser/BetterPhpDocParser.php index 125406318fa3..c722dd19df74 100644 --- a/packages/BetterPhpDocParser/src/PhpDocParser/BetterPhpDocParser.php +++ b/packages/BetterPhpDocParser/src/PhpDocParser/BetterPhpDocParser.php @@ -206,7 +206,7 @@ private function parseChildAndStoreItsPositions(TokenIterator $tokenIterator): N $startEndValueObject = new StartEndValueObject($tokenStart, $tokenEnd); $attributeAwareNode = $this->attributeAwareNodeFactory->createFromNode($phpDocNode); - $attributeAwareNode->setAttribute(Attribute::PHP_DOC_NODE_INFO, $startEndValueObject); + $attributeAwareNode->setAttribute(Attribute::START_END, $startEndValueObject); $possibleMultilineText = $this->multilineSpaceFormatPreserver->resolveCurrentPhpDocNodeText( $attributeAwareNode diff --git a/packages/BetterPhpDocParser/src/Printer/PhpDocInfoPrinter.php b/packages/BetterPhpDocParser/src/Printer/PhpDocInfoPrinter.php index c3252cc2ac8f..3e27f9117359 100644 --- a/packages/BetterPhpDocParser/src/Printer/PhpDocInfoPrinter.php +++ b/packages/BetterPhpDocParser/src/Printer/PhpDocInfoPrinter.php @@ -81,6 +81,15 @@ public function __construct( */ public function printFormatPreserving(PhpDocInfo $phpDocInfo, bool $shouldSkipEmptyLinesAbove = false): string { + if ($phpDocInfo->getTokens() === []) { + // completely new noe, just print string version of it + if ($phpDocInfo->getPhpDocNode()->children === []) { + return ''; + } + + return (string) $phpDocInfo->getPhpDocNode(); + } + $this->attributeAwarePhpDocNode = $phpDocInfo->getPhpDocNode(); $this->tokens = $phpDocInfo->getTokens(); @@ -93,7 +102,7 @@ public function printFormatPreserving(PhpDocInfo $phpDocInfo, bool $shouldSkipEm return $this->printPhpDocNode($this->attributeAwarePhpDocNode, $shouldSkipEmptyLinesAbove); } - private function printPhpDocNode( + public function printPhpDocNode( AttributeAwarePhpDocNode $attributeAwarePhpDocNode, bool $shouldSkipEmptyLinesAbove = false ): string { @@ -113,7 +122,15 @@ private function printPhpDocNode( $output .= $this->printNode($phpDocChildNode, null, $i + 1, $nodeCount, $shouldSkipEmptyLinesAbove); } - return $this->printEnd($output); + $output = $this->printEnd($output); + + // @see + // fix missing start + if (! Strings::match($output, '#^(\/\/|\/\*\*|\/\*)#') && $output) { + $output = '/**' . $output; + } + + return $output; } private function isPhpDocNodeEmpty(PhpDocNode $phpDocNode): bool @@ -145,12 +162,11 @@ private function printNode( $output = ''; /** @var StartEndValueObject|null $startEndValueObject */ - $startEndValueObject = $attributeAwareNode->getAttribute(Attribute::PHP_DOC_NODE_INFO) ?: $startEndValueObject; + $startEndValueObject = $attributeAwareNode->getAttribute(Attribute::START_END) ?: $startEndValueObject; $attributeAwareNode = $this->multilineSpaceFormatPreserver->fixMultilineDescriptions($attributeAwareNode); if ($startEndValueObject !== null) { $isLastToken = ($nodeCount === $i); - $output = $this->addTokensFromTo( $output, $this->currentTokenPosition, @@ -271,7 +287,7 @@ private function getRemovedNodesPositions(): array ); foreach ($removedNodes as $removedNode) { - $removedPhpDocNodeInfo = $removedNode->getAttribute(Attribute::PHP_DOC_NODE_INFO); + $removedPhpDocNodeInfo = $removedNode->getAttribute(Attribute::START_END); // change start position to start of the line, so the whole line is removed $seekPosition = $removedPhpDocNodeInfo->getStart(); diff --git a/packages/BetterPhpDocParser/tests/PhpDocParser/OrmTagParser/Class_/Fixture/expected_some_entity.txt b/packages/BetterPhpDocParser/tests/PhpDocParser/OrmTagParser/Class_/Fixture/expected_some_entity.txt index 080d7a7f37e0..cebd07cc8068 100644 --- a/packages/BetterPhpDocParser/tests/PhpDocParser/OrmTagParser/Class_/Fixture/expected_some_entity.txt +++ b/packages/BetterPhpDocParser/tests/PhpDocParser/OrmTagParser/Class_/Fixture/expected_some_entity.txt @@ -1,6 +1,6 @@ /** * @ORM\Entity(readOnly=true, repositoryClass="Rector\BetterPhpDocParser\Tests\PhpDocParser\OrmTagParser\Class_\Source\ExistingRepositoryClass") * @ORM\Entity - * @ORM\Entity + * @ORM\Entity() * @ORM\Table(name="answer") */ \ No newline at end of file diff --git a/packages/CodeQuality/src/Rector/Class_/CompleteDynamicPropertiesRector.php b/packages/CodeQuality/src/Rector/Class_/CompleteDynamicPropertiesRector.php index ad78b1d27f17..989bd1e7d343 100644 --- a/packages/CodeQuality/src/Rector/Class_/CompleteDynamicPropertiesRector.php +++ b/packages/CodeQuality/src/Rector/Class_/CompleteDynamicPropertiesRector.php @@ -96,6 +96,7 @@ public function refactor(Node $node): ?Node /** @var string $class */ $class = $this->getName($node); + // properties are accessed via magic, nothing we can do if (method_exists($class, '__set') || method_exists($class, '__get')) { return null; diff --git a/packages/CodeQuality/src/Rector/If_/CombineIfRector.php b/packages/CodeQuality/src/Rector/If_/CombineIfRector.php index de31edb94096..262cc9be70b6 100644 --- a/packages/CodeQuality/src/Rector/If_/CombineIfRector.php +++ b/packages/CodeQuality/src/Rector/If_/CombineIfRector.php @@ -7,6 +7,7 @@ use PhpParser\Node; use PhpParser\Node\Expr\BinaryOp\BooleanAnd; use PhpParser\Node\Stmt\If_; +use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; use Rector\Rector\AbstractRector; use Rector\RectorDefinition\CodeSample; use Rector\RectorDefinition\RectorDefinition; @@ -16,6 +17,16 @@ */ final class CombineIfRector extends AbstractRector { + /** + * @var PhpDocInfoFactory + */ + private $phpDocInfoFactory; + + public function __construct(PhpDocInfoFactory $phpDocInfoFactory) + { + $this->phpDocInfoFactory = $phpDocInfoFactory; + } + public function getDefinition(): RectorDefinition { return new RectorDefinition('Merges nested if statements', [ @@ -70,7 +81,7 @@ public function refactor(Node $node): ?Node $node->cond = new BooleanAnd($node->cond, $subIf->cond); $node->stmts = $subIf->stmts; - $node->setAttribute('comments', array_merge($node->getComments(), $subIf->getComments())); + $this->combineComments($node, $subIf); return $node; } @@ -98,4 +109,16 @@ private function shouldSkip(If_ $node): bool } return (bool) $node->stmts[0]->elseifs; } + + private function combineComments(Node $firstNode, Node $secondNode): void + { + $firstNode->setAttribute('comments', array_merge($firstNode->getComments(), $secondNode->getComments())); + + if ($firstNode->getDocComment() === null) { + return; + } + + // update original node php doc info object + $this->phpDocInfoFactory->createFromNode($firstNode); + } } diff --git a/packages/CodingStyle/src/Rector/ClassMethod/NewlineBeforeNewAssignSetRector.php b/packages/CodingStyle/src/Rector/ClassMethod/NewlineBeforeNewAssignSetRector.php index 29e62eff320c..f62713596be1 100644 --- a/packages/CodingStyle/src/Rector/ClassMethod/NewlineBeforeNewAssignSetRector.php +++ b/packages/CodingStyle/src/Rector/ClassMethod/NewlineBeforeNewAssignSetRector.php @@ -85,6 +85,7 @@ public function refactor(Node $node): ?Node return null; } + $hasChanged = false; foreach ($node->stmts as $key => $stmt) { $currentStmtVariableName = null; @@ -101,6 +102,7 @@ public function refactor(Node $node): ?Node } if ($this->shouldAddEmptyLine($currentStmtVariableName, $node, $key)) { + $hasChanged = true; // insert newline before array_splice($node->stmts, $key, 0, [new Nop()]); } @@ -109,6 +111,10 @@ public function refactor(Node $node): ?Node $this->previousStmtVariableName = $currentStmtVariableName; } + if (! $hasChanged) { + return null; + } + return $node; } diff --git a/packages/CodingStyle/src/Rector/Class_/AddArrayDefaultToArrayPropertyRector.php b/packages/CodingStyle/src/Rector/Class_/AddArrayDefaultToArrayPropertyRector.php index 88da20eae939..289b8a65363d 100644 --- a/packages/CodingStyle/src/Rector/Class_/AddArrayDefaultToArrayPropertyRector.php +++ b/packages/CodingStyle/src/Rector/Class_/AddArrayDefaultToArrayPropertyRector.php @@ -123,7 +123,7 @@ private function collectPropertyNamesWithMissingDefaultArray(Class_ $class): arr $property = $node->getAttribute(AttributeKey::PARENT_NODE); // we need docblock - $propertyPhpDocInfo = $this->getPhpDocInfo($property); + $propertyPhpDocInfo = $property->getAttribute(AttributeKey::PHP_DOC_INFO); if ($propertyPhpDocInfo === null) { return null; } diff --git a/packages/CodingStyle/src/Rector/FuncCall/FunctionCallToConstantRector.php b/packages/CodingStyle/src/Rector/FuncCall/FunctionCallToConstantRector.php index 1191d382f223..4b36d59393f1 100644 --- a/packages/CodingStyle/src/Rector/FuncCall/FunctionCallToConstantRector.php +++ b/packages/CodingStyle/src/Rector/FuncCall/FunctionCallToConstantRector.php @@ -18,7 +18,7 @@ final class FunctionCallToConstantRector extends AbstractRector { /** - * @var string[]string + * @var string[] */ private $functionsToConstants = []; diff --git a/packages/CodingStyle/src/Rector/FuncCall/VersionCompareFuncCallToConstantRector.php b/packages/CodingStyle/src/Rector/FuncCall/VersionCompareFuncCallToConstantRector.php index a936459dbd3d..7db4217cd3d3 100644 --- a/packages/CodingStyle/src/Rector/FuncCall/VersionCompareFuncCallToConstantRector.php +++ b/packages/CodingStyle/src/Rector/FuncCall/VersionCompareFuncCallToConstantRector.php @@ -29,7 +29,7 @@ final class VersionCompareFuncCallToConstantRector extends AbstractRector { /** - * @var string[]string + * @var string[] */ private $operatorToComparison = [ '=' => Identical::class, diff --git a/packages/CodingStyle/src/Rector/Use_/RemoveUnusedAliasRector.php b/packages/CodingStyle/src/Rector/Use_/RemoveUnusedAliasRector.php index 70de7fac646b..db8a92d1eaa6 100644 --- a/packages/CodingStyle/src/Rector/Use_/RemoveUnusedAliasRector.php +++ b/packages/CodingStyle/src/Rector/Use_/RemoveUnusedAliasRector.php @@ -321,7 +321,7 @@ private function resolveDocPossibleAliases(Node $searchNode): array } /** @var PhpDocInfo $phpDocInfo */ - $phpDocInfo = $this->getPhpDocInfo($node); + $phpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO); if ($phpDocInfo->getVarType()) { $possibleDocAliases = $this->appendPossibleAliases($phpDocInfo->getVarType(), $possibleDocAliases); diff --git a/packages/CodingStyle/tests/Rector/ClassConst/VarConstantCommentRector/Fixture/skip_already_has.php.inc b/packages/CodingStyle/tests/Rector/ClassConst/VarConstantCommentRector/Fixture/skip_already_has.php.inc index 00a8365416b3..9020736907fc 100644 --- a/packages/CodingStyle/tests/Rector/ClassConst/VarConstantCommentRector/Fixture/skip_already_has.php.inc +++ b/packages/CodingStyle/tests/Rector/ClassConst/VarConstantCommentRector/Fixture/skip_already_has.php.inc @@ -13,7 +13,6 @@ final class SkipAlreadyHas * - "[package-name] "Message => package-name * - "[aliased-package-name] "Message => aliased-package-name * - "[Aliased\PackageName] "Message => Aliased\PackageName - * - "[Aliased\PackageName] "Message => Aliased\PackageName */ public const PACKAGE_NAME_PATTERN = '#\[(?[-\w\\\\]+)\]( ){1,}#'; } diff --git a/packages/CodingStyle/tests/Rector/Namespace_/ImportFullyQualifiedNamesRector/Fixture/should_keep_all_doc_blocks_annotations_parameters.php.inc b/packages/CodingStyle/tests/Rector/Namespace_/ImportFullyQualifiedNamesRector/Fixture/should_keep_all_doc_blocks_annotations_parameters.php.inc index ce0e342fc61b..efa04181abb6 100644 --- a/packages/CodingStyle/tests/Rector/Namespace_/ImportFullyQualifiedNamesRector/Fixture/should_keep_all_doc_blocks_annotations_parameters.php.inc +++ b/packages/CodingStyle/tests/Rector/Namespace_/ImportFullyQualifiedNamesRector/Fixture/should_keep_all_doc_blocks_annotations_parameters.php.inc @@ -3,7 +3,6 @@ namespace Rector\CodingStyle\Tests\Rector\Namespace_\ImportFullyQualifiedNamesRector\Fixture; use Doctrine\ORM\Mapping as ORM; -use Symfony\Component\Routing\Annotation\Route; /** * @ORM\Table("Table_Name") @@ -12,13 +11,6 @@ use Symfony\Component\Routing\Annotation\Route; */ class ShouldKeepAllDocBlocksAnnotationsParameters { - /** - * @Route( - * "/{arg1}/{arg2}", - * defaults={"arg1"=null, "arg2"=""}, - * requirements={"arg1"="\d+", "arg2"=".*"} - * ) - */ public function nothing(): void { } diff --git a/packages/CodingStyle/tests/Rector/Namespace_/ImportFullyQualifiedNamesRector/FixtureSkipped/symfony_route.php.inc b/packages/CodingStyle/tests/Rector/Namespace_/ImportFullyQualifiedNamesRector/FixtureSkipped/symfony_route.php.inc new file mode 100644 index 000000000000..60604f83cf02 --- /dev/null +++ b/packages/CodingStyle/tests/Rector/Namespace_/ImportFullyQualifiedNamesRector/FixtureSkipped/symfony_route.php.inc @@ -0,0 +1,19 @@ +getDocComment() === null) { + /** @var PhpDocInfo|null $phpDocInfo */ + $phpDocInfo = $property->getAttribute(AttributeKey::PHP_DOC_INFO); + if ($phpDocInfo === null) { return null; } - $phpDocInfo = $this->docBlockManipulator->createPhpDocInfoFromNode($property); - $relationTagValueNode = $phpDocInfo->getByType(DoctrineRelationTagValueNodeInterface::class); if ($relationTagValueNode === null) { return null; @@ -103,12 +105,12 @@ public function isNonAbstractDoctrineEntityClass(Class_ $class): bool public function removeMappedByOrInversedByFromProperty(Property $property): void { - $doc = $property->getDocComment(); - if ($doc === null) { + /** @var PhpDocInfo|null $phpDocInfo */ + $phpDocInfo = $property->getAttribute(AttributeKey::PHP_DOC_INFO); + if ($phpDocInfo === null) { return; } - $phpDocInfo = $this->docBlockManipulator->createPhpDocInfoFromNode($property); $relationTagValueNode = $phpDocInfo->getByType(DoctrineRelationTagValueNodeInterface::class); $shouldUpdate = false; @@ -125,31 +127,6 @@ public function removeMappedByOrInversedByFromProperty(Property $property): void if (! $shouldUpdate) { return; } - - $this->docBlockManipulator->updateNodeWithPhpDocInfo($property, $phpDocInfo); - } - - /** - * @return string[] - */ - public function resolveRelationPropertyNames(Class_ $class): array - { - $manyToOnePropertyNames = []; - - foreach ($class->getProperties() as $property) { - if ($property->getDocComment() === null) { - continue; - } - - $phpDocInfo = $this->docBlockManipulator->createPhpDocInfoFromNode($property); - if (! $phpDocInfo->hasByType(DoctrineRelationTagValueNodeInterface::class)) { - continue; - } - - $manyToOnePropertyNames[] = $this->nameResolver->getName($property); - } - - return $manyToOnePropertyNames; } public function isMethodCallOnDoctrineEntity(Node $node, string $methodName): bool diff --git a/packages/DeadCode/src/Rector/ClassMethod/RemoveOverriddenValuesRector.php b/packages/DeadCode/src/Rector/ClassMethod/RemoveOverriddenValuesRector.php index 83c6f1bbd50f..34adce7a888f 100644 --- a/packages/DeadCode/src/Rector/ClassMethod/RemoveOverriddenValuesRector.php +++ b/packages/DeadCode/src/Rector/ClassMethod/RemoveOverriddenValuesRector.php @@ -268,9 +268,15 @@ private function isAssignNodeUsed( VariableNodeUseInfo $nodeByTypeAndPosition ): bool { // this node was just used, skip to next one - return $previousNode !== null && ($previousNode->isType( - VariableNodeUseInfo::TYPE_ASSIGN - ) && $nodeByTypeAndPosition->isType(VariableNodeUseInfo::TYPE_USE)); + if ($previousNode === null) { + return false; + } + + if (! $previousNode->isType(VariableNodeUseInfo::TYPE_ASSIGN)) { + return false; + } + + return $nodeByTypeAndPosition->isType(VariableNodeUseInfo::TYPE_USE); } private function shouldRemoveAssignNode( @@ -301,7 +307,7 @@ private function shouldRemoveAssignNode( $isVariableAssigned = (bool) $this->betterNodeFinder->findFirst($assignNode->expr, function (Node $node) use ( $nodeByTypeAndPosition ): bool { - return $this->areNodesEqual($node, $nodeByTypeAndPosition->getVariableNode()); + return $this->areNodesWithoutCommentsEqual($node, $nodeByTypeAndPosition->getVariableNode()); }); return ! $isVariableAssigned; diff --git a/packages/DeadCode/src/Rector/FunctionLike/RemoveCodeAfterReturnRector.php b/packages/DeadCode/src/Rector/FunctionLike/RemoveCodeAfterReturnRector.php index f004f3b45dab..173c993885d6 100644 --- a/packages/DeadCode/src/Rector/FunctionLike/RemoveCodeAfterReturnRector.php +++ b/packages/DeadCode/src/Rector/FunctionLike/RemoveCodeAfterReturnRector.php @@ -66,6 +66,7 @@ public function refactor(Node $node): ?Node } $isDeadAfterReturn = false; + $isDeadAfterReturnRemoved = false; foreach ($node->stmts as $key => $stmt) { if ($isDeadAfterReturn) { @@ -75,6 +76,7 @@ public function refactor(Node $node): ?Node } unset($node->stmts[$key]); + $isDeadAfterReturnRemoved = true; } if ($stmt instanceof Return_) { @@ -83,6 +85,10 @@ public function refactor(Node $node): ?Node } } + if (! $isDeadAfterReturnRemoved) { + return null; + } + return $node; } } diff --git a/packages/DeadCode/tests/Rector/ClassMethod/RemoveOverriddenValuesRector/Fixture/keep_presenter_name.php.inc b/packages/DeadCode/tests/Rector/ClassMethod/RemoveOverriddenValuesRector/Fixture/keep_presenter_name.php.inc new file mode 100644 index 000000000000..97591a852cb6 --- /dev/null +++ b/packages/DeadCode/tests/Rector/ClassMethod/RemoveOverriddenValuesRector/Fixture/keep_presenter_name.php.inc @@ -0,0 +1,29 @@ +getName($classNode); + + /** @var string $presenterPart */ + $presenterPart = Strings::after($presenterName, '\\', -1); + + /** @var string $presenterPart */ + $presenterPart = Strings::substring($presenterPart, 0, -Strings::length('Presenter')); + $presenterPart = RectorStrings::camelCaseToDashes($presenterPart); + + $match = (array) Strings::match($this->getName($classMethod), '#^(action|render)(?.*?$)#sm'); + $actionPart = lcfirst($match['short_action_name']); + + return $presenterPart . '/' . $actionPart; + } +} diff --git a/packages/DeadCode/tests/Rector/ClassMethod/RemoveOverriddenValuesRector/Fixture/reference_use.php.inc b/packages/DeadCode/tests/Rector/ClassMethod/RemoveOverriddenValuesRector/Fixture/reference_use.php.inc index b771a587e4b0..5dbab832f72b 100644 --- a/packages/DeadCode/tests/Rector/ClassMethod/RemoveOverriddenValuesRector/Fixture/reference_use.php.inc +++ b/packages/DeadCode/tests/Rector/ClassMethod/RemoveOverriddenValuesRector/Fixture/reference_use.php.inc @@ -2,53 +2,125 @@ namespace Rector\DeadCode\Tests\Rector\ClassMethod\RemoveOverriddenValuesRector\Fixture; -final class ReferenceUse +use PhpParser\Node; +use PhpParser\Node\Expr\Assign; +use PhpParser\Node\Stmt\If_; +use PhpParser\Node\Stmt\Return_; +use Rector\NodeTypeResolver\Node\AttributeKey; +use Rector\PhpParser\Node\Manipulator\IfManipulator; +use Rector\PhpParser\Node\Manipulator\StmtsManipulator; +use Rector\Rector\AbstractRector; +use Rector\RectorDefinition\CodeSample; +use Rector\RectorDefinition\RectorDefinition; + +/** + * @see https://engineering.helpscout.com/reducing-complexity-with-guard-clauses-in-php-and-javascript-74600fd865c7 + * + * @see \Rector\SOLID\Tests\Rector\If_\ChangeIfElseValueAssignToEarlyReturnRector\ChangeIfElseValueAssignToEarlyReturnRectorTest + */ +final class ChangeIfElseValueAssignToEarlyReturnRector extends AbstractRector { - public function run(&$directories) - { - $directories = 1; - $directories = 4; - $directories = 5; - } + /** + * @var IfManipulator + */ + private $ifManipulator; + + /** + * @var StmtsManipulator + */ + private $stmtsManipulator; - public function plusRun(&$directories) + public function __construct(IfManipulator $ifManipulator, StmtsManipulator $stmtsManipulator) { - $directories = 1; - $directories += 4; + $this->ifManipulator = $ifManipulator; + $this->stmtsManipulator = $stmtsManipulator; } - public function get(&$directories) + public function getDefinition(): RectorDefinition { - $directories = 1; - $directories = 4; - return $directories; + return new RectorDefinition('Change if/else value to early return', [ + new CodeSample( + <<<'PHP' +class SomeClass +{ + public function run() + { + if ($this->hasDocBlock($tokens, $index)) { + $docToken = $tokens[$this->getDocBlockIndex($tokens, $index)]; + } else { + $docToken = null; + } + + return $docToken; } } - -?> ------ -hasDocBlock($tokens, $index)) { + return $tokens[$this->getDocBlockIndex($tokens, $index)]; + } + return null; } +} +PHP - public function plusRun(&$directories) + ), + ]); + } + + /** + * @return string[] + */ + public function getNodeTypes(): array { - $directories = 1; - $directories += 4; + return [If_::class]; } - public function get(&$directories) + /** + * @param If_ $node + */ + public function refactor(Node $node): ?Node { - $directories = 4; - return $directories; + $nextNode = $node->getAttribute(AttributeKey::NEXT_NODE); + if (! $nextNode instanceof Return_) { + return null; + } + + if ($nextNode->expr === null) { + return null; + } + + if (! $this->ifManipulator->isIfAndElseWithSameVariableAssignAsLastStmts($node, $nextNode->expr)) { + return null; + } + + $lastIfStmtKey = array_key_last($node->stmts); + + /** @var Assign $assign */ + $assign = $this->stmtsManipulator->getUnwrappedLastStmt($node->stmts); + + $node->stmts[$lastIfStmtKey] = new Return_($assign->expr); + + /** @var Assign $assign */ + $assign = $this->stmtsManipulator->getUnwrappedLastStmt($node->else->stmts); + $lastElseStmtKey = array_key_last($node->else->stmts); + + $elseStmts = $node->else->stmts; + $elseStmts[$lastElseStmtKey] = new Return_($assign->expr); + + $node->else = null; + + foreach ($elseStmts as $elseStmt) { + $this->addNodeAfterNode($elseStmt, $node); + } + + $this->removeNode($nextNode); + + return $node; } } - -?> diff --git a/packages/Doctrine/src/PhpDocParser/DoctrineDocBlockResolver.php b/packages/Doctrine/src/PhpDocParser/DoctrineDocBlockResolver.php index 8daab61bcf83..bf8f92a431ba 100644 --- a/packages/Doctrine/src/PhpDocParser/DoctrineDocBlockResolver.php +++ b/packages/Doctrine/src/PhpDocParser/DoctrineDocBlockResolver.php @@ -10,7 +10,6 @@ use PhpParser\Node\Stmt\ClassLike; use PhpParser\Node\Stmt\Property; use Rector\BetterPhpDocParser\Contract\Doctrine\DoctrineRelationTagValueNodeInterface; -use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo; use Rector\BetterPhpDocParser\PhpDocNode\Doctrine\Class_\EntityTagValueNode; use Rector\BetterPhpDocParser\PhpDocNode\Doctrine\Property_\ColumnTagValueNode; use Rector\BetterPhpDocParser\PhpDocNode\Doctrine\Property_\IdTagValueNode; @@ -18,24 +17,17 @@ use Rector\NodeContainer\ParsedNodesByType; use Rector\NodeTypeResolver\ClassExistenceStaticHelper; use Rector\NodeTypeResolver\Node\AttributeKey; -use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator; use ReflectionClass; final class DoctrineDocBlockResolver { - /** - * @var DocBlockManipulator - */ - private $docBlockManipulator; - /** * @var ParsedNodesByType */ private $parsedNodesByType; - public function __construct(DocBlockManipulator $docBlockManipulator, ParsedNodesByType $parsedNodesByType) + public function __construct(ParsedNodesByType $parsedNodesByType) { - $this->docBlockManipulator = $docBlockManipulator; $this->parsedNodesByType = $parsedNodesByType; } @@ -45,12 +37,12 @@ public function __construct(DocBlockManipulator $docBlockManipulator, ParsedNode public function isDoctrineEntityClass($class): bool { if ($class instanceof Class_) { - $classPhpDocInfo = $this->getPhpDocInfo($class); - if ($classPhpDocInfo === null) { + $phpDocInfo = $class->getAttribute(AttributeKey::PHP_DOC_INFO); + if ($phpDocInfo === null) { return false; } - return $classPhpDocInfo->hasByType(EntityTagValueNode::class); + return $phpDocInfo->hasByType(EntityTagValueNode::class); } if (is_string($class)) { @@ -103,36 +95,36 @@ public function getTargetEntity(Property $property): ?string public function hasPropertyDoctrineIdTag(Property $property): bool { - $propertyPhpDocInfo = $this->getPhpDocInfo($property); - if ($propertyPhpDocInfo === null) { + $phpDocInfo = $property->getAttribute(AttributeKey::PHP_DOC_INFO); + if ($phpDocInfo === null) { return false; } - return $propertyPhpDocInfo->hasByType(IdTagValueNode::class); + return $phpDocInfo->hasByType(IdTagValueNode::class); } public function getDoctrineRelationTagValueNode(Property $property): ?DoctrineRelationTagValueNodeInterface { - $propertyPhpDocInfo = $this->getPhpDocInfo($property); - if ($propertyPhpDocInfo === null) { + $phpDocInfo = $property->getAttribute(AttributeKey::PHP_DOC_INFO); + if ($phpDocInfo === null) { return null; } - return $propertyPhpDocInfo->getByType(DoctrineRelationTagValueNodeInterface::class); + return $phpDocInfo->getByType(DoctrineRelationTagValueNodeInterface::class); } public function isDoctrineProperty(Property $property): bool { - $propertyPhpDocInfo = $this->getPhpDocInfo($property); - if ($propertyPhpDocInfo === null) { + $phpDocInfo = $property->getAttribute(AttributeKey::PHP_DOC_INFO); + if ($phpDocInfo === null) { return false; } - if ($propertyPhpDocInfo->hasByType(ColumnTagValueNode::class)) { + if ($phpDocInfo->hasByType(ColumnTagValueNode::class)) { return true; } - return $propertyPhpDocInfo->hasByType(DoctrineRelationTagValueNodeInterface::class); + return $phpDocInfo->hasByType(DoctrineRelationTagValueNodeInterface::class); } public function isInDoctrineEntityClass(Node $node): bool @@ -145,13 +137,4 @@ public function isInDoctrineEntityClass(Node $node): bool return $this->isDoctrineEntityClass($classNode); } - - private function getPhpDocInfo(Node $node): ?PhpDocInfo - { - if ($node->getDocComment() === null) { - return null; - } - - return $this->docBlockManipulator->createPhpDocInfoFromNode($node); - } } diff --git a/packages/Doctrine/src/Provider/EntityWithMissingUuidProvider.php b/packages/Doctrine/src/Provider/EntityWithMissingUuidProvider.php index 20c29620436d..347a3e968e6a 100644 --- a/packages/Doctrine/src/Provider/EntityWithMissingUuidProvider.php +++ b/packages/Doctrine/src/Provider/EntityWithMissingUuidProvider.php @@ -7,11 +7,12 @@ use Nette\Utils\Strings; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\Property; -use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; +use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo; use Rector\BetterPhpDocParser\PhpDocNode\Doctrine\Property_\ColumnTagValueNode; use Rector\BetterPhpDocParser\PhpDocNode\Doctrine\Property_\IdTagValueNode; use Rector\Doctrine\PhpDocParser\DoctrineDocBlockResolver; use Rector\NodeContainer\ParsedNodesByType; +use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\PhpParser\Node\Manipulator\ClassManipulator; use Rector\PhpParser\Node\Resolver\NameResolver; @@ -37,11 +38,6 @@ final class EntityWithMissingUuidProvider */ private $nameResolver; - /** - * @var PhpDocInfoFactory - */ - private $phpDocInfoFactory; - /** * @var Class_[] */ @@ -51,14 +47,12 @@ public function __construct( ParsedNodesByType $parsedNodesByType, DoctrineDocBlockResolver $doctrineDocBlockResolver, ClassManipulator $classManipulator, - NameResolver $nameResolver, - PhpDocInfoFactory $phpDocInfoFactory + NameResolver $nameResolver ) { $this->parsedNodesByType = $parsedNodesByType; $this->doctrineDocBlockResolver = $doctrineDocBlockResolver; $this->classManipulator = $classManipulator; $this->nameResolver = $nameResolver; - $this->phpDocInfoFactory = $phpDocInfoFactory; } /** @@ -112,8 +106,8 @@ private function hasClassIdPropertyWithUuidType(Class_ $class): bool private function isPropertyClassIdWithUuidType(Property $property): bool { - $propertyPhpDocInfo = $this->phpDocInfoFactory->createFromNode($property); - + /** @var PhpDocInfo $propertyPhpDocInfo */ + $propertyPhpDocInfo = $property->getAttribute(AttributeKey::PHP_DOC_INFO); if (! $propertyPhpDocInfo->hasByType(IdTagValueNode::class)) { return false; } diff --git a/packages/Doctrine/src/Rector/Class_/AddUuidMirrorForRelationPropertyRector.php b/packages/Doctrine/src/Rector/Class_/AddUuidMirrorForRelationPropertyRector.php index 4ef32680c3fb..d9dfdb670d91 100644 --- a/packages/Doctrine/src/Rector/Class_/AddUuidMirrorForRelationPropertyRector.php +++ b/packages/Doctrine/src/Rector/Class_/AddUuidMirrorForRelationPropertyRector.php @@ -105,7 +105,7 @@ private function shouldSkipProperty(Class_ $class, Property $property): bool } /** @var PhpDocInfo|null $propertyPhpDocInfo */ - $propertyPhpDocInfo = $this->getPhpDocInfo($property); + $propertyPhpDocInfo = $property->getAttribute(AttributeKey::PHP_DOC_INFO); if ($propertyPhpDocInfo === null) { return true; } @@ -126,7 +126,7 @@ private function createMirrorNullable(Property $property): Property $propertyWithUuid = clone $property; // this is needed to keep old property name - $this->updateDocComment($propertyWithUuid); + $this->mirrorPhpDocInfoToUuid($propertyWithUuid); // name must be changed after the doc comment update, because the reflection annotation needed for update of doc comment // would miss non existing *Uuid property @@ -152,21 +152,21 @@ private function hasClassPropertyName(Class_ $node, string $uuidPropertyName): b return false; } - private function updateDocComment(Property $property): void + private function mirrorPhpDocInfoToUuid(Property $property): void { /** @var PhpDocInfo $propertyPhpDocInfo */ - $propertyPhpDocInfo = $this->getPhpDocInfo($property); + $propertyPhpDocInfo = $property->getAttribute(AttributeKey::PHP_DOC_INFO); + + $newPropertyPhpDocInfo = clone $propertyPhpDocInfo; /** @var DoctrineRelationTagValueNodeInterface $doctrineRelationTagValueNode */ $doctrineRelationTagValueNode = $this->getDoctrineRelationTagValueNode($property); if ($doctrineRelationTagValueNode instanceof ToManyTagNodeInterface) { - $this->refactorToManyPropertyPhpDocInfo($propertyPhpDocInfo, $property); + $this->refactorToManyPropertyPhpDocInfo($newPropertyPhpDocInfo, $property); } elseif ($doctrineRelationTagValueNode instanceof ToOneTagNodeInterface) { - $this->refactorToOnePropertyPhpDocInfo($propertyPhpDocInfo); + $this->refactorToOnePropertyPhpDocInfo($newPropertyPhpDocInfo); } - - $this->docBlockManipulator->updateNodeWithPhpDocInfo($property, $propertyPhpDocInfo); } private function addNewPropertyToCollector( @@ -197,22 +197,28 @@ private function refactorToManyPropertyPhpDocInfo(PhpDocInfo $propertyPhpDocInfo } $joinTableTagNode = $this->phpDocTagNodeFactory->createJoinTableTagNode($property); - $propertyPhpDocInfo->getPhpDocNode()->children[] = $joinTableTagNode; + $propertyPhpDocInfo->addPhpDocTagNode($joinTableTagNode); } private function refactorToOnePropertyPhpDocInfo(PhpDocInfo $propertyPhpDocInfo): void { - /** @var JoinColumnTagValueNode $joinColumnTagValueNode */ + /** @var JoinColumnTagValueNode|null $joinColumnTagValueNode */ $joinColumnTagValueNode = $propertyPhpDocInfo->getByType(JoinColumnTagValueNode::class); - if ($joinColumnTagValueNode) { - $joinColumnTagValueNode->changeName(''); - $joinColumnTagValueNode->changeNullable(true); - $joinColumnTagValueNode->changeReferencedColumnName('uuid'); - } else { - $propertyPhpDocInfo->getPhpDocNode()->children[] = $this->phpDocTagNodeFactory->createJoinColumnTagNode( + if ($joinColumnTagValueNode !== null) { + // remove first + $propertyPhpDocInfo->removeByType(JoinColumnTagValueNode::class); + + $mirrorJoinColumnTagValueNode = new JoinColumnTagValueNode( + '', + 'uuid', + $joinColumnTagValueNode->getUnique(), true ); + } else { + $mirrorJoinColumnTagValueNode = $this->phpDocTagNodeFactory->createJoinColumnTagNode(true); } + + $propertyPhpDocInfo->addTagValueNodeWithShortName($mirrorJoinColumnTagValueNode); } } diff --git a/packages/Doctrine/src/Rector/Class_/AlwaysInitializeUuidInEntityRector.php b/packages/Doctrine/src/Rector/Class_/AlwaysInitializeUuidInEntityRector.php index 0c979ac86ef8..1c686131fe93 100644 --- a/packages/Doctrine/src/Rector/Class_/AlwaysInitializeUuidInEntityRector.php +++ b/packages/Doctrine/src/Rector/Class_/AlwaysInitializeUuidInEntityRector.php @@ -12,6 +12,7 @@ use PhpParser\Node\Stmt\Property; use PHPStan\Type\ObjectType; use Rector\Doctrine\NodeFactory\EntityUuidNodeFactory; +use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\PhpParser\Node\Manipulator\ClassManipulator; use Rector\Rector\AbstractRector; use Rector\RectorDefinition\RectorDefinition; @@ -82,7 +83,7 @@ public function refactor(Node $node): ?Node private function resolveUuidPropertyFromClass(Class_ $class): ?Property { foreach ($class->getProperties() as $property) { - $propertyPhpDoc = $this->getPhpDocInfo($property); + $propertyPhpDoc = $property->getAttribute(AttributeKey::PHP_DOC_INFO); if ($propertyPhpDoc === null) { continue; } diff --git a/packages/Doctrine/src/Rector/Class_/RemoveRepositoryFromEntityAnnotationRector.php b/packages/Doctrine/src/Rector/Class_/RemoveRepositoryFromEntityAnnotationRector.php index d07f7b28ba55..d0a8a602e51b 100644 --- a/packages/Doctrine/src/Rector/Class_/RemoveRepositoryFromEntityAnnotationRector.php +++ b/packages/Doctrine/src/Rector/Class_/RemoveRepositoryFromEntityAnnotationRector.php @@ -8,6 +8,7 @@ use PhpParser\Node\Stmt\Class_; use Rector\BetterPhpDocParser\PhpDocNode\Doctrine\Class_\EntityTagValueNode; use Rector\Doctrine\Tests\Rector\Class_\RemoveRepositoryFromEntityAnnotationRector\RemoveRepositoryFromEntityAnnotationRectorTest; +use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\Rector\AbstractRector; use Rector\RectorDefinition\CodeSample; use Rector\RectorDefinition\RectorDefinition; @@ -59,7 +60,7 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - $phpDocInfo = $this->getPhpDocInfo($node); + $phpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO); if ($phpDocInfo === null) { return null; } @@ -72,7 +73,6 @@ public function refactor(Node $node): ?Node $doctrineEntityTag->removeRepositoryClass(); // save the entity tag - $this->docBlockManipulator->updateNodeWithPhpDocInfo($node, $phpDocInfo); return $node; } diff --git a/packages/Doctrine/src/Rector/Property/AddUuidAnnotationsToIdPropertyRector.php b/packages/Doctrine/src/Rector/Property/AddUuidAnnotationsToIdPropertyRector.php index 6a17baa6ceac..75f6041f2fb1 100644 --- a/packages/Doctrine/src/Rector/Property/AddUuidAnnotationsToIdPropertyRector.php +++ b/packages/Doctrine/src/Rector/Property/AddUuidAnnotationsToIdPropertyRector.php @@ -11,6 +11,7 @@ use Rector\BetterPhpDocParser\PhpDocNode\Doctrine\Property_\ColumnTagValueNode; use Rector\BetterPhpDocParser\PhpDocNode\Doctrine\Property_\GeneratedValueTagValueNode; use Rector\BetterPhpDocParser\PhpDocNode\JMS\SerializerTypeTagValueNode; +use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\PHPStan\Type\FullyQualifiedObjectType; use Rector\Rector\AbstractRector; use Rector\RectorDefinition\RectorDefinition; @@ -48,23 +49,22 @@ public function refactor(Node $node): ?Node return null; } - $this->changeVarToUuidInterface($node); - /** @var PhpDocInfo $phpDocInfo */ - $phpDocInfo = $this->getPhpDocInfo($node); + $phpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO); + + $this->changeVarToUuidInterface($node); $phpDocInfo->removeByType(GeneratedValueTagValueNode::class); $this->changeColumnTypeToUuidBinary($phpDocInfo); $this->changeSerializerTypeToString($phpDocInfo); - $this->docBlockManipulator->updateNodeWithPhpDocInfo($node, $phpDocInfo); - return $node; } private function changeVarToUuidInterface(Property $property): void { $uuidObjectType = new FullyQualifiedObjectType(UuidInterface::class); + $this->docBlockManipulator->changeVarTag($property, $uuidObjectType); } diff --git a/packages/Doctrine/src/Rector/Property/RemoveTemporaryUuidColumnPropertyRector.php b/packages/Doctrine/src/Rector/Property/RemoveTemporaryUuidColumnPropertyRector.php index d2780725577e..444a0eb30de3 100644 --- a/packages/Doctrine/src/Rector/Property/RemoveTemporaryUuidColumnPropertyRector.php +++ b/packages/Doctrine/src/Rector/Property/RemoveTemporaryUuidColumnPropertyRector.php @@ -7,6 +7,7 @@ use PhpParser\Node; use PhpParser\Node\Stmt\Property; use Rector\BetterPhpDocParser\PhpDocNode\Doctrine\Property_\ColumnTagValueNode; +use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\Rector\AbstractRector; use Rector\RectorDefinition\RectorDefinition; @@ -39,7 +40,7 @@ public function refactor(Node $node): ?Node return null; } - $phpDocInfo = $this->getPhpDocInfo($node); + $phpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO); if ($phpDocInfo === null) { return null; } diff --git a/packages/Doctrine/src/Rector/Property/RemoveTemporaryUuidRelationPropertyRector.php b/packages/Doctrine/src/Rector/Property/RemoveTemporaryUuidRelationPropertyRector.php index aa95fb67c2fc..c252f5f63eee 100644 --- a/packages/Doctrine/src/Rector/Property/RemoveTemporaryUuidRelationPropertyRector.php +++ b/packages/Doctrine/src/Rector/Property/RemoveTemporaryUuidRelationPropertyRector.php @@ -7,6 +7,7 @@ use PhpParser\Node; use PhpParser\Node\Stmt\Property; use Rector\BetterPhpDocParser\Contract\Doctrine\DoctrineRelationTagValueNodeInterface; +use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\Rector\AbstractRector; use Rector\RectorDefinition\RectorDefinition; @@ -39,7 +40,7 @@ public function refactor(Node $node): ?Node return null; } - $phpDocInfo = $this->getPhpDocInfo($node); + $phpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO); if ($phpDocInfo === null) { return null; } diff --git a/packages/Doctrine/tests/Rector/Class_/AddUuidMirrorForRelationPropertyRector/Fixture/many_to_many_with_extra_name.php.inc b/packages/Doctrine/tests/Rector/Class_/AddUuidMirrorForRelationPropertyRector/FixtureSkipped/many_to_many_with_extra_name.php.inc similarity index 100% rename from packages/Doctrine/tests/Rector/Class_/AddUuidMirrorForRelationPropertyRector/Fixture/many_to_many_with_extra_name.php.inc rename to packages/Doctrine/tests/Rector/Class_/AddUuidMirrorForRelationPropertyRector/FixtureSkipped/many_to_many_with_extra_name.php.inc index 7c9d38395ee7..c8602759325c 100644 --- a/packages/Doctrine/tests/Rector/Class_/AddUuidMirrorForRelationPropertyRector/Fixture/many_to_many_with_extra_name.php.inc +++ b/packages/Doctrine/tests/Rector/Class_/AddUuidMirrorForRelationPropertyRector/FixtureSkipped/many_to_many_with_extra_name.php.inc @@ -58,8 +58,8 @@ class ManyToManyWithExtraName private $itemRole; /** * @ORM\ManyToOne(targetEntity="Rector\Doctrine\Tests\Rector\Class_\AddUuidMirrorForRelationPropertyRector\Fixture\BarEntity") - * @ORM\JoinColumn(referencedColumnName="uuid", nullable=true) * @Serializer\Type("Rector\Doctrine\Tests\Rector\Class_\AddUuidMirrorForRelationPropertyRector\Fixture\BarEntity") + * @ORM\JoinColumn(referencedColumnName="uuid", nullable=true) */ private $itemRoleUuid; } diff --git a/packages/Doctrine/tests/Rector/Class_/AddUuidMirrorForRelationPropertyRector/Fixture/to_many.php.inc b/packages/Doctrine/tests/Rector/Class_/AddUuidMirrorForRelationPropertyRector/FixtureSkipped/to_many.php.inc similarity index 100% rename from packages/Doctrine/tests/Rector/Class_/AddUuidMirrorForRelationPropertyRector/Fixture/to_many.php.inc rename to packages/Doctrine/tests/Rector/Class_/AddUuidMirrorForRelationPropertyRector/FixtureSkipped/to_many.php.inc diff --git a/packages/Doctrine/tests/Rector/Class_/AddUuidMirrorForRelationPropertyRector/Fixture/to_one.php.inc b/packages/Doctrine/tests/Rector/Class_/AddUuidMirrorForRelationPropertyRector/FixtureSkipped/to_one.php.inc similarity index 100% rename from packages/Doctrine/tests/Rector/Class_/AddUuidMirrorForRelationPropertyRector/Fixture/to_one.php.inc rename to packages/Doctrine/tests/Rector/Class_/AddUuidMirrorForRelationPropertyRector/FixtureSkipped/to_one.php.inc diff --git a/packages/DoctrineCodeQuality/src/Rector/Class_/InitializeDefaultEntityCollectionRector.php b/packages/DoctrineCodeQuality/src/Rector/Class_/InitializeDefaultEntityCollectionRector.php index 0ff951ce9bbc..26f88622f73a 100644 --- a/packages/DoctrineCodeQuality/src/Rector/Class_/InitializeDefaultEntityCollectionRector.php +++ b/packages/DoctrineCodeQuality/src/Rector/Class_/InitializeDefaultEntityCollectionRector.php @@ -13,6 +13,7 @@ use PhpParser\Node\Stmt\Expression; use Rector\BetterPhpDocParser\Contract\Doctrine\ToManyTagNodeInterface; use Rector\BetterPhpDocParser\PhpDocNode\Doctrine\Class_\EntityTagValueNode; +use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\PhpParser\Node\Manipulator\ClassManipulator; use Rector\Rector\AbstractRector; use Rector\RectorDefinition\CodeSample; @@ -90,7 +91,7 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - $classPhpDocInfo = $this->getPhpDocInfo($node); + $classPhpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO); if ($classPhpDocInfo === null) { return null; } @@ -123,7 +124,7 @@ private function resolveToManyPropertyNames(Class_ $class): array continue; } - $propertyPhpDocInfo = $this->getPhpDocInfo($property); + $propertyPhpDocInfo = $property->getAttribute(AttributeKey::PHP_DOC_INFO); if ($propertyPhpDocInfo === null) { continue; } diff --git a/packages/DoctrineGedmoToKnplabs/src/Rector/Class_/BlameableBehaviorRector.php b/packages/DoctrineGedmoToKnplabs/src/Rector/Class_/BlameableBehaviorRector.php index 7f224f5411ab..62ae2f0682cb 100644 --- a/packages/DoctrineGedmoToKnplabs/src/Rector/Class_/BlameableBehaviorRector.php +++ b/packages/DoctrineGedmoToKnplabs/src/Rector/Class_/BlameableBehaviorRector.php @@ -8,6 +8,7 @@ use PhpParser\Node\Name\FullyQualified; use PhpParser\Node\Stmt\Class_; use Rector\BetterPhpDocParser\PhpDocNode\Gedmo\BlameableTagValueNode; +use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\PhpParser\Node\Manipulator\ClassManipulator; use Rector\Rector\AbstractRector; use Rector\RectorDefinition\CodeSample; @@ -123,7 +124,7 @@ public function refactor(Node $node): ?Node private function isGedmoBlameableClass(Class_ $class): bool { foreach ($class->getProperties() as $property) { - $propertyPhpDocInfo = $this->getPhpDocInfo($property); + $propertyPhpDocInfo = $property->getAttribute(AttributeKey::PHP_DOC_INFO); if ($propertyPhpDocInfo === null) { continue; } @@ -143,7 +144,7 @@ private function removeBlameablePropertiesAndMethods(Class_ $class): void $removedPropertyNames = []; foreach ($class->getProperties() as $property) { - $propertyPhpDocInfo = $this->getPhpDocInfo($property); + $propertyPhpDocInfo = $property->getAttribute(AttributeKey::PHP_DOC_INFO); if ($propertyPhpDocInfo === null) { continue; } diff --git a/packages/DoctrineGedmoToKnplabs/src/Rector/Class_/LoggableBehaviorRector.php b/packages/DoctrineGedmoToKnplabs/src/Rector/Class_/LoggableBehaviorRector.php index bc08094d76f8..859684eed0e9 100644 --- a/packages/DoctrineGedmoToKnplabs/src/Rector/Class_/LoggableBehaviorRector.php +++ b/packages/DoctrineGedmoToKnplabs/src/Rector/Class_/LoggableBehaviorRector.php @@ -9,6 +9,7 @@ use PhpParser\Node\Stmt\Class_; use Rector\BetterPhpDocParser\PhpDocNode\Gedmo\LoggableTagValueNode; use Rector\BetterPhpDocParser\PhpDocNode\Gedmo\VersionedTagValueNode; +use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\PhpParser\Node\Manipulator\ClassManipulator; use Rector\Rector\AbstractRector; use Rector\RectorDefinition\CodeSample; @@ -91,7 +92,7 @@ public function getNodeTypes(): array public function refactor(Node $node): ?Node { // change the node - $classPhpDocInfo = $this->getPhpDocInfo($node); + $classPhpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO); if ($classPhpDocInfo === null) { return null; } @@ -101,7 +102,6 @@ public function refactor(Node $node): ?Node } $classPhpDocInfo->removeByType(LoggableTagValueNode::class); - $this->docBlockManipulator->updateNodeWithPhpDocInfo($node, $classPhpDocInfo); // remove tag from properties $this->removeVersionedTagFromProperties($node); @@ -116,7 +116,7 @@ public function refactor(Node $node): ?Node private function removeVersionedTagFromProperties(Class_ $class): void { foreach ($class->getProperties() as $property) { - $propertyPhpDocInfo = $this->getPhpDocInfo($property); + $propertyPhpDocInfo = $property->getAttribute(AttributeKey::PHP_DOC_INFO); if ($propertyPhpDocInfo === null) { continue; } @@ -126,7 +126,6 @@ private function removeVersionedTagFromProperties(Class_ $class): void } $propertyPhpDocInfo->removeByType(VersionedTagValueNode::class); - $this->docBlockManipulator->updateNodeWithPhpDocInfo($property, $propertyPhpDocInfo); } } } diff --git a/packages/DoctrineGedmoToKnplabs/src/Rector/Class_/SluggableBehaviorRector.php b/packages/DoctrineGedmoToKnplabs/src/Rector/Class_/SluggableBehaviorRector.php index c57ba98c1050..b29422b460f6 100644 --- a/packages/DoctrineGedmoToKnplabs/src/Rector/Class_/SluggableBehaviorRector.php +++ b/packages/DoctrineGedmoToKnplabs/src/Rector/Class_/SluggableBehaviorRector.php @@ -13,6 +13,7 @@ use PHPStan\Type\MixedType; use PHPStan\Type\StringType; use Rector\BetterPhpDocParser\PhpDocNode\Gedmo\SlugTagValueNode; +use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\PhpParser\Node\Manipulator\ClassManipulator; use Rector\Rector\AbstractRector; use Rector\RectorDefinition\CodeSample; @@ -102,7 +103,7 @@ public function refactor(Node $node): ?Node $matchedProperty = null; foreach ($node->getProperties() as $property) { - $propertyPhpDocInfo = $this->getPhpDocInfo($property); + $propertyPhpDocInfo = $property->getAttribute(AttributeKey::PHP_DOC_INFO); if ($propertyPhpDocInfo === null) { continue; } diff --git a/packages/DoctrineGedmoToKnplabs/src/Rector/Class_/SoftDeletableBehaviorRector.php b/packages/DoctrineGedmoToKnplabs/src/Rector/Class_/SoftDeletableBehaviorRector.php index a4229ecc2411..112e3fc14fb2 100644 --- a/packages/DoctrineGedmoToKnplabs/src/Rector/Class_/SoftDeletableBehaviorRector.php +++ b/packages/DoctrineGedmoToKnplabs/src/Rector/Class_/SoftDeletableBehaviorRector.php @@ -8,6 +8,7 @@ use PhpParser\Node\Name\FullyQualified; use PhpParser\Node\Stmt\Class_; use Rector\BetterPhpDocParser\PhpDocNode\Gedmo\SoftDeleteableTagValueNode; +use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\PhpParser\Node\Manipulator\ClassManipulator; use Rector\Rector\AbstractRector; use Rector\RectorDefinition\CodeSample; @@ -91,7 +92,7 @@ public function getNodeTypes(): array public function refactor(Node $node): ?Node { // Gedmo\Mapping\Annotation\SoftDeleteable - $classPhpDocInfo = $this->getPhpDocInfo($node); + $classPhpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO); if ($classPhpDocInfo === null) { return null; } @@ -110,7 +111,6 @@ public function refactor(Node $node): ?Node $node->implements[] = new FullyQualified('Knp\DoctrineBehaviors\Contract\Entity\SoftDeletableInterface'); $classPhpDocInfo->removeByType(SoftDeleteableTagValueNode::class); - $this->docBlockManipulator->updateNodeWithPhpDocInfo($node, $classPhpDocInfo); return $node; } diff --git a/packages/DoctrineGedmoToKnplabs/src/Rector/Class_/TranslationBehaviorRector.php b/packages/DoctrineGedmoToKnplabs/src/Rector/Class_/TranslationBehaviorRector.php index 74def6f8bc50..be1b1db33b71 100644 --- a/packages/DoctrineGedmoToKnplabs/src/Rector/Class_/TranslationBehaviorRector.php +++ b/packages/DoctrineGedmoToKnplabs/src/Rector/Class_/TranslationBehaviorRector.php @@ -207,7 +207,7 @@ private function collectAndRemoveTranslatableProperties(Class_ $class): array $removedPropertyNameToPhpDocInfo = []; foreach ($class->getProperties() as $property) { - $propertyPhpDocInfo = $this->getPhpDocInfo($property); + $propertyPhpDocInfo = $property->getAttribute(AttributeKey::PHP_DOC_INFO); if ($propertyPhpDocInfo === null) { continue; } @@ -261,7 +261,7 @@ private function dumpEntityTranslation(Class_ $class, array $translatedPropertyT foreach ($translatedPropertyToPhpDocInfos as $translatedPropertyName => $translatedPhpDocInfo) { $property = $this->nodeFactory->createPrivateProperty($translatedPropertyName); - $this->docBlockManipulator->updateNodeWithPhpDocInfo($property, $translatedPhpDocInfo); + $property->setAttribute(AttributeKey::PHP_DOC_INFO, $translatedPhpDocInfo); $class->stmts[] = $property; } diff --git a/packages/DoctrineGedmoToKnplabs/src/Rector/Class_/TreeBehaviorRector.php b/packages/DoctrineGedmoToKnplabs/src/Rector/Class_/TreeBehaviorRector.php index f88b64ef7541..43deaa906fc1 100644 --- a/packages/DoctrineGedmoToKnplabs/src/Rector/Class_/TreeBehaviorRector.php +++ b/packages/DoctrineGedmoToKnplabs/src/Rector/Class_/TreeBehaviorRector.php @@ -14,6 +14,7 @@ use Rector\BetterPhpDocParser\PhpDocNode\Gedmo\TreeRightTagValueNode; use Rector\BetterPhpDocParser\PhpDocNode\Gedmo\TreeRootTagValueNode; use Rector\BetterPhpDocParser\PhpDocNode\Gedmo\TreeTagValueNode; +use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\PhpParser\Node\Manipulator\ClassManipulator; use Rector\Rector\AbstractRector; use Rector\RectorDefinition\CodeSample; @@ -137,7 +138,7 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - $classPhpDocInfo = $this->getPhpDocInfo($node); + $classPhpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO); if ($classPhpDocInfo === null) { return null; } @@ -148,7 +149,6 @@ public function refactor(Node $node): ?Node // we're in a tree entity $classPhpDocInfo->removeByType(TreeTagValueNode::class); - $this->docBlockManipulator->updateNodeWithPhpDocInfo($node, $classPhpDocInfo); $node->implements[] = new FullyQualified('Knp\DoctrineBehaviors\Contract\Entity\TreeNodeInterface'); $this->classManipulator->addAsFirstTrait($node, 'Knp\DoctrineBehaviors\Model\Tree\TreeNodeTrait'); @@ -158,7 +158,7 @@ public function refactor(Node $node): ?Node $removedPropertyNames = []; foreach ($node->getProperties() as $property) { - $propertyPhpDocInfo = $this->getPhpDocInfo($property); + $propertyPhpDocInfo = $property->getAttribute(AttributeKey::PHP_DOC_INFO); if ($propertyPhpDocInfo === null) { continue; } diff --git a/packages/NetteTesterToPHPUnit/src/AssertManipulator.php b/packages/NetteTesterToPHPUnit/src/AssertManipulator.php index b4c277b95c06..062173131841 100644 --- a/packages/NetteTesterToPHPUnit/src/AssertManipulator.php +++ b/packages/NetteTesterToPHPUnit/src/AssertManipulator.php @@ -14,8 +14,11 @@ use PhpParser\Node\Identifier; use PhpParser\Node\Name; use PhpParser\Node\Name\FullyQualified; -use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTextNode; +use PhpParser\Node\Stmt\ClassMethod; +use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; use PHPStan\Type\BooleanType; +use Prophecy\Doubler\Generator\Node\MethodNode; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\NodeTypeResolver\NodeTypeResolver; use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator; @@ -248,12 +251,13 @@ private function processNoErrorCall(StaticCall $staticCall): void $this->nodeRemovingCommander->addNode($staticCall); + /** @var ClassMethod|null $methodNode */ $methodNode = $staticCall->getAttribute(AttributeKey::METHOD_NODE); if ($methodNode === null) { return; } - $phpDocTagNode = new PhpDocTextNode('@doesNotPerformAssertions'); + $phpDocTagNode = new PhpDocTagNode('@doesNotPerformAssertions', new GenericTagValueNode('')); $this->docBlockManipulator->addTag($methodNode, $phpDocTagNode); } diff --git a/packages/NetteTesterToPHPUnit/src/Rector/Class_/NetteTesterClassToPHPUnitClassRector.php b/packages/NetteTesterToPHPUnit/src/Rector/Class_/NetteTesterClassToPHPUnitClassRector.php index ea4d7e6641a7..91a62c8e0a4f 100644 --- a/packages/NetteTesterToPHPUnit/src/Rector/Class_/NetteTesterClassToPHPUnitClassRector.php +++ b/packages/NetteTesterToPHPUnit/src/Rector/Class_/NetteTesterClassToPHPUnitClassRector.php @@ -15,6 +15,9 @@ use Rector\RectorDefinition\RectorDefinition; use Tester\TestCase; +/** + * @see \Rector\NetteTesterToPHPUnit\Tests\Rector\Class_\NetteTesterClassToPHPUnitClassRector\NetteTesterPHPUnitRectorTest + */ final class NetteTesterClassToPHPUnitClassRector extends AbstractRector { public function getDefinition(): RectorDefinition diff --git a/packages/NetteToSymfony/src/Rector/ClassMethod/RouterListToControllerAnnotationsRector.php b/packages/NetteToSymfony/src/Rector/ClassMethod/RouterListToControllerAnnotationsRector.php index 7fea34f154a5..9ab2115bc3d3 100644 --- a/packages/NetteToSymfony/src/Rector/ClassMethod/RouterListToControllerAnnotationsRector.php +++ b/packages/NetteToSymfony/src/Rector/ClassMethod/RouterListToControllerAnnotationsRector.php @@ -18,6 +18,7 @@ use Rector\NetteToSymfony\Route\RouteInfoFactory; use Rector\NetteToSymfony\ValueObject\RouteInfo; use Rector\NodeContainer\ParsedNodesByType; +use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\PHPStan\Type\FullyQualifiedObjectType; use Rector\Rector\AbstractRector; use Rector\RectorDefinition\CodeSample; @@ -323,7 +324,7 @@ private function shouldSkipClassStmt(Node $node): bool } // already has Route tag - $phpDocInfo = $this->getPhpDocInfo($node); + $phpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO); if ($phpDocInfo === null) { return false; } @@ -335,8 +336,10 @@ private function resolvePathFromClassAndMethodNodes(Class_ $classNode, ClassMeth { /** @var string $presenterName */ $presenterName = $this->getName($classNode); + /** @var string $presenterPart */ $presenterPart = Strings::after($presenterName, '\\', -1); + /** @var string $presenterPart */ $presenterPart = Strings::substring($presenterPart, 0, -Strings::length('Presenter')); $presenterPart = RectorStrings::camelCaseToDashes($presenterPart); diff --git a/packages/NodeTypeResolver/src/Node/AttributeKey.php b/packages/NodeTypeResolver/src/Node/AttributeKey.php index 4537532de3f4..c764bbced673 100644 --- a/packages/NodeTypeResolver/src/Node/AttributeKey.php +++ b/packages/NodeTypeResolver/src/Node/AttributeKey.php @@ -4,12 +4,20 @@ namespace Rector\NodeTypeResolver\Node; +use PhpParser\Node\Stmt\ClassLike; +use PhpParser\Node\Stmt\ClassMethod; +use PhpParser\Node\Stmt\Function_; +use PhpParser\Node\Stmt\Namespace_; +use PHPStan\Analyser\Scope; +use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo; +use Symplify\SmartFileSystem\SmartFileInfo; + final class AttributeKey { /** * @var string */ - public const SCOPE = 'scope'; + public const SCOPE = Scope::class; /** * @var string @@ -19,7 +27,7 @@ final class AttributeKey /** * @var string */ - public const NAMESPACE_NODE = 'namespaceNode'; + public const NAMESPACE_NODE = Namespace_::class; /** * @var string @@ -35,7 +43,7 @@ final class AttributeKey * @todo split Class node, interface node and trait node, to be compatible with other SpecificNode|null, values * @var string */ - public const CLASS_NODE = 'classNode'; + public const CLASS_NODE = ClassLike::class; /** * @var string @@ -50,7 +58,7 @@ final class AttributeKey /** * @var string */ - public const METHOD_NODE = 'methodNode'; + public const METHOD_NODE = ClassMethod::class; /** * Internal php-parser name. @@ -96,7 +104,7 @@ final class AttributeKey /** * @var string */ - public const FILE_INFO = 'fileInfo'; + public const FILE_INFO = SmartFileInfo::class; /** * @var string @@ -117,5 +125,10 @@ final class AttributeKey /** * @var string */ - public const FUNCTION_NODE = 'function_node'; + public const FUNCTION_NODE = Function_::class; + + /** + * @var string + */ + public const PHP_DOC_INFO = PhpDocInfo::class; } diff --git a/packages/NodeTypeResolver/src/NodeScopeAndMetadataDecorator.php b/packages/NodeTypeResolver/src/NodeScopeAndMetadataDecorator.php index b8b05e300cd1..eed2ddc79d3a 100644 --- a/packages/NodeTypeResolver/src/NodeScopeAndMetadataDecorator.php +++ b/packages/NodeTypeResolver/src/NodeScopeAndMetadataDecorator.php @@ -14,6 +14,7 @@ use Rector\NodeTypeResolver\NodeVisitor\NamespaceNodeVisitor; use Rector\NodeTypeResolver\NodeVisitor\NodeCollectorNodeVisitor; use Rector\NodeTypeResolver\NodeVisitor\ParentAndNextNodeVisitor; +use Rector\NodeTypeResolver\NodeVisitor\PhpDocInfoNodeVisitor; use Rector\NodeTypeResolver\NodeVisitor\StatementNodeVisitor; use Rector\NodeTypeResolver\PHPStan\Scope\NodeScopeResolver; @@ -64,6 +65,11 @@ final class NodeScopeAndMetadataDecorator */ private $configuration; + /** + * @var PhpDocInfoNodeVisitor + */ + private $phpDocInfoNodeVisitor; + public function __construct( NodeScopeResolver $nodeScopeResolver, ParentAndNextNodeVisitor $parentAndNextNodeVisitor, @@ -73,6 +79,7 @@ public function __construct( StatementNodeVisitor $statementNodeVisitor, FileInfoNodeVisitor $fileInfoNodeVisitor, NodeCollectorNodeVisitor $nodeCollectorNodeVisitor, + PhpDocInfoNodeVisitor $phpDocInfoNodeVisitor, Configuration $configuration ) { $this->nodeScopeResolver = $nodeScopeResolver; @@ -84,6 +91,7 @@ public function __construct( $this->fileInfoNodeVisitor = $fileInfoNodeVisitor; $this->nodeCollectorNodeVisitor = $nodeCollectorNodeVisitor; $this->configuration = $configuration; + $this->phpDocInfoNodeVisitor = $phpDocInfoNodeVisitor; } /** @@ -117,6 +125,7 @@ public function decorateNodesFromFile(array $nodes, string $filePath, bool $need $nodeTraverser->addVisitor($this->parentAndNextNodeVisitor); $nodeTraverser->addVisitor($this->functionMethodAndClassNodeVisitor); $nodeTraverser->addVisitor($this->namespaceNodeVisitor); + $nodeTraverser->addVisitor($this->phpDocInfoNodeVisitor); $nodes = $nodeTraverser->traverse($nodes); diff --git a/packages/NodeTypeResolver/src/NodeVisitor/PhpDocInfoNodeVisitor.php b/packages/NodeTypeResolver/src/NodeVisitor/PhpDocInfoNodeVisitor.php new file mode 100644 index 000000000000..39470ba45a3c --- /dev/null +++ b/packages/NodeTypeResolver/src/NodeVisitor/PhpDocInfoNodeVisitor.php @@ -0,0 +1,39 @@ +phpDocInfoFactory = $phpDocInfoFactory; + } + + /** + * @return int|Node|void|null + */ + public function enterNode(Node $node) + { + if ($node->getDocComment() === null) { + $node->setAttribute(AttributeKey::PHP_DOC_INFO, null); + return; + } + + $phpDocInfo = $this->phpDocInfoFactory->createFromNode($node); + $node->setAttribute(AttributeKey::PHP_DOC_INFO, $phpDocInfo); + + return $node; + } +} diff --git a/packages/NodeTypeResolver/src/PHPStan/TypeHasher.php b/packages/NodeTypeResolver/src/PHPStan/TypeHasher.php index bf6091102cbd..f913ef5b3d1a 100644 --- a/packages/NodeTypeResolver/src/PHPStan/TypeHasher.php +++ b/packages/NodeTypeResolver/src/PHPStan/TypeHasher.php @@ -61,4 +61,9 @@ public function createTypeHash(Type $type): string return $this->phpStanStaticTypeMapper->mapToDocString($type); } + + public function areTypesEqual(Type $firstType, Type $secondType): bool + { + return $this->createTypeHash($firstType) === $this->createTypeHash($secondType); + } } diff --git a/packages/NodeTypeResolver/src/PhpDoc/NodeAnalyzer/DocBlockManipulator.php b/packages/NodeTypeResolver/src/PhpDoc/NodeAnalyzer/DocBlockManipulator.php index 77fb1e0f601c..14f8aa11f754 100644 --- a/packages/NodeTypeResolver/src/PhpDoc/NodeAnalyzer/DocBlockManipulator.php +++ b/packages/NodeTypeResolver/src/PhpDoc/NodeAnalyzer/DocBlockManipulator.php @@ -29,7 +29,6 @@ use PHPStan\Type\ObjectType; use PHPStan\Type\StringType; use PHPStan\Type\Type; -use Rector\AttributeAwarePhpDoc\Ast\PhpDoc\AttributeAwarePhpDocNode; use Rector\AttributeAwarePhpDoc\Ast\PhpDoc\AttributeAwarePhpDocTagNode; use Rector\AttributeAwarePhpDoc\Ast\PhpDoc\AttributeAwareVarTagValueNode; use Rector\AttributeAwarePhpDoc\Ast\Type\AttributeAwareIdentifierTypeNode; @@ -55,11 +54,6 @@ */ final class DocBlockManipulator { - /** - * @var PhpDocInfoFactory - */ - private $phpDocInfoFactory; - /** * @var PhpDocInfoPrinter */ @@ -100,17 +94,21 @@ final class DocBlockManipulator */ private $typeHasher; + /** + * @var PhpDocInfoFactory + */ + private $phpDocInfoFactory; + public function __construct( - PhpDocInfoFactory $phpDocInfoFactory, PhpDocInfoPrinter $phpDocInfoPrinter, AttributeAwareNodeFactory $attributeAwareNodeFactory, PhpDocNodeTraverser $phpDocNodeTraverser, StaticTypeMapper $staticTypeMapper, DocBlockClassRenamer $docBlockClassRenamer, DocBlockNameImporter $docBlockNameImporter, - TypeHasher $typeHasher + TypeHasher $typeHasher, + PhpDocInfoFactory $phpDocInfoFactory ) { - $this->phpDocInfoFactory = $phpDocInfoFactory; $this->phpDocInfoPrinter = $phpDocInfoPrinter; $this->attributeAwareNodeFactory = $attributeAwareNodeFactory; $this->phpDocNodeTraverser = $phpDocNodeTraverser; @@ -118,6 +116,7 @@ public function __construct( $this->docBlockClassRenamer = $docBlockClassRenamer; $this->docBlockNameImporter = $docBlockNameImporter; $this->typeHasher = $typeHasher; + $this->phpDocInfoFactory = $phpDocInfoFactory; } public function hasTag(Node $node, string $name): bool @@ -137,8 +136,8 @@ public function hasTag(Node $node, string $name): bool return false; } - // advanced check, e.g. for "Namespaced\Annotations\DI" - $phpDocInfo = $this->createPhpDocInfoFromNode($node); + /** @var PhpDocInfo $phpDocInfo */ + $phpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO); return $phpDocInfo->hasByType($name); } @@ -147,15 +146,13 @@ public function addTag(Node $node, PhpDocChildNode $phpDocChildNode): void { $phpDocChildNode = $this->attributeAwareNodeFactory->createFromNode($phpDocChildNode); - if ($node->getDocComment() !== null) { - $phpDocInfo = $this->createPhpDocInfoFromNode($node); - $phpDocNode = $phpDocInfo->getPhpDocNode(); - $phpDocNode->children[] = $phpDocChildNode; - $this->updateNodeWithPhpDocInfo($node, $phpDocInfo); - } else { - $phpDocNode = new AttributeAwarePhpDocNode([$phpDocChildNode]); - $node->setDocComment(new Doc($phpDocNode->__toString())); + /** @var PhpDocInfo|null $phpDocInfo */ + $phpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO); + if ($phpDocInfo === null) { + $phpDocInfo = $this->phpDocInfoFactory->createFromNode($node); } + + $phpDocInfo->addPhpDocTagNode($phpDocChildNode); } public function addTagValueNodeWithShortName(Node $node, AbstractTagValueNode $tagValueNode): void @@ -164,16 +161,19 @@ public function addTagValueNodeWithShortName(Node $node, AbstractTagValueNode $t $this->addTag($node, $spacelessPhpDocTagNode); } - public function removeTagFromNode(Node $node, string $name, bool $shouldSkipEmptyLinesAbove = false): void + /** + * @deprecated + * Use @see PhpDocInfo::removeByType(x) directly + */ + public function removeTagFromNode(Node $node, string $name): void { - if ($node->getDocComment() === null) { + /** @var PhpDocInfo|null $phpDocInfo */ + $phpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO); + if ($phpDocInfo === null) { return; } - $phpDocInfo = $this->createPhpDocInfoFromNode($node); - $this->removeTagByName($phpDocInfo, $name); - $this->updateNodeWithPhpDocInfo($node, $phpDocInfo, $shouldSkipEmptyLinesAbove); } public function changeType(Node $node, Type $oldType, Type $newType): void @@ -182,39 +182,31 @@ public function changeType(Node $node, Type $oldType, Type $newType): void return; } - $phpDocInfo = $this->createPhpDocInfoFromNode($node); - $hasNodeChanged = $this->docBlockClassRenamer->renamePhpDocType( - $phpDocInfo->getPhpDocNode(), - $oldType, - $newType, - $node - ); + /** @var PhpDocInfo $phpDocInfo */ + $phpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO); - if ($hasNodeChanged) { - $this->updateNodeWithPhpDocInfo($node, $phpDocInfo); - } + $this->docBlockClassRenamer->renamePhpDocType($phpDocInfo->getPhpDocNode(), $oldType, $newType, $node); } public function replaceAnnotationInNode(Node $node, string $oldAnnotation, string $newAnnotation): void { - if ($node->getDocComment() === null) { + /** @var PhpDocInfo|null $phpDocInfo */ + $phpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO); + if ($phpDocInfo === null) { return; } - $phpDocInfo = $this->createPhpDocInfoFromNode($node); $this->replaceTagByAnother($phpDocInfo->getPhpDocNode(), $oldAnnotation, $newAnnotation); - - $this->updateNodeWithPhpDocInfo($node, $phpDocInfo); } public function getReturnType(Node $node): Type { - if ($node->getDocComment() === null) { + /** @var PhpDocInfo|null $phpDocInfo */ + $phpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO); + if ($phpDocInfo === null) { return new MixedType(); } - $phpDocInfo = $this->createPhpDocInfoFromNode($node); - return $phpDocInfo->getReturnType(); } @@ -226,12 +218,12 @@ public function getReturnType(Node $node): Type */ public function getParamTypesByName(FunctionLike $functionLike): array { - if ($functionLike->getDocComment() === null) { + /** @var PhpDocInfo|null $phpDocInfo */ + $phpDocInfo = $functionLike->getAttribute(AttributeKey::PHP_DOC_INFO); + if ($phpDocInfo === null) { return []; } - $phpDocInfo = $this->createPhpDocInfoFromNode($functionLike); - $paramTypesByName = []; foreach ($phpDocInfo->getParamTagValues() as $paramTagValueNode) { @@ -247,17 +239,16 @@ public function getParamTypesByName(FunctionLike $functionLike): array } /** - * @final * @return PhpDocTagNode[] */ public function getTagsByName(Node $node, string $name): array { - if ($node->getDocComment() === null) { + /** @var PhpDocInfo|null $phpDocInfo */ + $phpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO); + if ($phpDocInfo === null) { return []; } - $phpDocInfo = $this->createPhpDocInfoFromNode($node); - return $phpDocInfo->getTagsByName($name); } @@ -284,10 +275,6 @@ public function changeVarTag(Node $node, Type $newType): void $phpDocType = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($newType); $varTagValueNode->type = $phpDocType; - - // update doc :) - $phpDocInfo = $this->createPhpDocInfoFromNode($node); - $this->updateNodeWithPhpDocInfo($node, $phpDocInfo); } else { $this->addTypeSpecificTag($node, 'var', $newType); } @@ -305,8 +292,10 @@ public function addReturnTag(Node $node, Type $newType): void return; } - if ($node->getDocComment() !== null) { - $phpDocInfo = $this->createPhpDocInfoFromNode($node); + /** @var PhpDocInfo|null $phpDocInfo */ + $phpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO); + + if ($phpDocInfo !== null) { $returnTagValueNode = $phpDocInfo->getByType(ReturnTagValueNode::class); // overide existing type @@ -314,7 +303,6 @@ public function addReturnTag(Node $node, Type $newType): void $newPHPStanPhpDocType = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($newType); $returnTagValueNode->type = $newPHPStanPhpDocType; - $this->updateNodeWithPhpDocInfo($node, $phpDocInfo); return; } } @@ -338,12 +326,12 @@ public function getTagByName(Node $node, string $name): PhpDocTagNode public function getVarType(Node $node): Type { - if ($node->getDocComment() === null) { + /** @var PhpDocInfo|null $phpDocInfo */ + $phpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO); + if ($phpDocInfo === null) { return new MixedType(); } - $phpDocInfo = $this->createPhpDocInfoFromNode($node); - return $phpDocInfo->getVarType(); } @@ -407,16 +395,12 @@ public function replaceTagByAnother(PhpDocNode $phpDocNode, string $oldTag, stri public function importNames(Node $node): void { - if ($node->getDocComment() === null) { + $phpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO); + if ($phpDocInfo === null) { return; } - $phpDocInfo = $this->createPhpDocInfoFromNode($node); - $hasNodeChanged = $this->docBlockNameImporter->importNames($phpDocInfo, $node); - - if ($hasNodeChanged) { - $this->updateNodeWithPhpDocInfo($node, $phpDocInfo); - } + $this->docBlockNameImporter->importNames($phpDocInfo, $node); } /** @@ -424,11 +408,11 @@ public function importNames(Node $node): void */ public function changeUnderscoreType(Node $node, string $namespacePrefix, array $excludedClasses): void { - if ($node->getDocComment() === null) { + $phpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO); + if ($phpDocInfo === null) { return; } - $phpDocInfo = $this->createPhpDocInfoFromNode($node); $phpDocNode = $phpDocInfo->getPhpDocNode(); $phpParserNode = $node; @@ -467,8 +451,6 @@ public function changeUnderscoreType(Node $node, string $namespacePrefix, array if (! $this->hasPhpDocChanged) { return; } - - $this->updateNodeWithPhpDocInfo($node, $phpDocInfo); } /** @@ -485,7 +467,8 @@ public function hasNodeTypeTags(Node $node): bool return true; } - $phpDocInfo = $this->createPhpDocInfoFromNode($node); + /** @var PhpDocInfo $phpDocInfo */ + $phpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO); // has any type node? @@ -502,36 +485,54 @@ public function hasNodeTypeTags(Node $node): bool return false; } - public function updateNodeWithPhpDocInfo( - Node $node, - PhpDocInfo $phpDocInfo, - bool $shouldSkipEmptyLinesAbove = false - ): bool { - $phpDoc = $this->phpDocInfoPrinter->printFormatPreserving($phpDocInfo, $shouldSkipEmptyLinesAbove); - if ($phpDoc !== '') { - // no change, don't save it - if ($node->getDocComment() && $node->getDocComment()->getText() === $phpDoc) { - return false; + public function updateNodeWithPhpDocInfo(Node $node, bool $shouldSkipEmptyLinesAbove = false): void + { + // nothing to change + /** @var PhpDocInfo|null $phpDocInfo */ + $phpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO); + if ($phpDocInfo === null) { + return; + } + + // new node, needs to be reparsed + if ($phpDocInfo->getPhpDocNode()->children !== [] && $phpDocInfo->getTokens() === []) { + $phpDoc = $this->phpDocInfoPrinter->printPhpDocNode( + $phpDocInfo->getPhpDocNode(), + $shouldSkipEmptyLinesAbove + ); + + // slight correction + if (Strings::match($phpDoc, '#^ * #m')) { + $phpDoc = Strings::replace($phpDoc, '#\s+\*/$#m', "\n */"); } + } else { + $phpDoc = $this->phpDocInfoPrinter->printFormatPreserving($phpDocInfo, $shouldSkipEmptyLinesAbove); + } - $node->setDocComment(new Doc($phpDoc)); - return true; + if ($phpDoc === '') { + // no comments, null + $node->setAttribute('comments', null); + return; } - // no comments, null - $node->setAttribute('comments', null); + // no change, don't save it + // this is needed to prevent short classes override with FQN with same value → people don't like that for some reason + + if ($node->getDocComment() && $node->getDocComment()->getText() === $phpDoc) { + return; + } - return true; + $node->setDocComment(new Doc($phpDoc)); } public function getDoctrineFqnTargetEntity(Node $node): ?string { - if ($node->getDocComment() === null) { + /** @var PhpDocInfo|null $phpDocInfo */ + $phpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO); + if ($phpDocInfo === null) { return null; } - $phpDocInfo = $this->createPhpDocInfoFromNode($node); - $relationTagValueNode = $phpDocInfo->getByType(DoctrineRelationTagValueNodeInterface::class); if ($relationTagValueNode === null) { return null; @@ -540,18 +541,6 @@ public function getDoctrineFqnTargetEntity(Node $node): ?string return $relationTagValueNode->getFqnTargetEntity(); } - public function createPhpDocInfoFromNode(Node $node): PhpDocInfo - { - if ($node->getDocComment() === null) { - throw new ShouldNotHappenException(sprintf( - 'Node must have a comment. Check `$node->getDocComment() !== null` before passing it to %s', - __METHOD__ - )); - } - - return $this->phpDocInfoFactory->createFromNode($node); - } - public function getParamTypeByName(FunctionLike $functionLike, string $paramName): Type { $this->ensureParamNameStartsWithDollar($paramName, __METHOD__); @@ -574,10 +563,7 @@ private function areTypesEquals(Type $firstType, Type $secondType): bool return true; } - $firstTypeHash = $this->typeHasher->createTypeHash($firstType); - $secondTypeHash = $this->typeHasher->createTypeHash($secondType); - - if ($firstTypeHash === $secondTypeHash) { + if ($this->typeHasher->areTypesEqual($firstType, $secondType)) { return true; } @@ -595,17 +581,17 @@ private function addTypeSpecificTag(Node $node, string $name, Type $type): void return; } - // there might be no phpdoc at all - if ($node->getDocComment() !== null) { - $phpDocInfo = $this->createPhpDocInfoFromNode($node); - $phpDocNode = $phpDocInfo->getPhpDocNode(); + /** @var PhpDocInfo|null $phpDocInfo */ + $phpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO); + // there might be no phpdoc at all + if ($phpDocInfo !== null) { $varTagValueNode = new AttributeAwareVarTagValueNode(new AttributeAwareIdentifierTypeNode( $docStringType ), '', ''); - $phpDocNode->children[] = new AttributeAwarePhpDocTagNode('@' . $name, $varTagValueNode); - $this->updateNodeWithPhpDocInfo($node, $phpDocInfo); + $varTagValueNode = new AttributeAwarePhpDocTagNode('@' . $name, $varTagValueNode); + $phpDocInfo->addPhpDocTagNode($varTagValueNode); } else { // create completely new docblock $varDocComment = sprintf("/**\n * @%s %s\n */", $name, $docStringType); diff --git a/packages/NodeTypeResolver/tests/NodeVisitor/FunctionMethodAndClassNodeVisitor/Fixture/anonymous_class.php.inc b/packages/NodeTypeResolver/tests/NodeVisitor/FunctionMethodAndClassNodeVisitor/Fixture/anonymous_class.php.inc index 371c0b5b0999..456246c363b3 100644 --- a/packages/NodeTypeResolver/tests/NodeVisitor/FunctionMethodAndClassNodeVisitor/Fixture/anonymous_class.php.inc +++ b/packages/NodeTypeResolver/tests/NodeVisitor/FunctionMethodAndClassNodeVisitor/Fixture/anonymous_class.php.inc @@ -30,7 +30,7 @@ array( 5: RandomProperty ) attributes: array( - classNode: null + PhpParser\Node\Stmt\ClassLike: null className: null methodName: null ) @@ -41,7 +41,7 @@ array( name: Identifier #3( name: foo attributes: array( - classNode: Stmt_Class #2 + PhpParser\Node\Stmt\ClassLike: Stmt_Class #2 className: Rector\NodeTypeResolver\Tests\NodeVisitor\FunctionMethodAndClassNodeVisitorTest\RandomProperty\foo methodName: null ) @@ -53,7 +53,7 @@ array( name: Identifier #5( name: bar attributes: array( - classNode: Stmt_Class #2 + PhpParser\Node\Stmt\ClassLike: Stmt_Class #2 className: Rector\NodeTypeResolver\Tests\NodeVisitor\FunctionMethodAndClassNodeVisitorTest\RandomProperty\foo methodName: bar ) @@ -70,32 +70,32 @@ array( name: Identifier #10( name: baz attributes: array( - classNode: Stmt_Class #8 + PhpParser\Node\Stmt\ClassLike: Stmt_Class #8 className: null methodName: baz ) ) attributes: array( - classNode: Stmt_Class #8 + PhpParser\Node\Stmt\ClassLike: Stmt_Class #8 className: null methodName: baz ) ) ) attributes: array( - classNode: Stmt_Class #8 + PhpParser\Node\Stmt\ClassLike: Stmt_Class #8 className: null methodName: bar ) ) attributes: array( - classNode: Stmt_Class #2 + PhpParser\Node\Stmt\ClassLike: Stmt_Class #2 className: Rector\NodeTypeResolver\Tests\NodeVisitor\FunctionMethodAndClassNodeVisitorTest\RandomProperty\foo methodName: bar ) ) attributes: array( - classNode: Stmt_Class #2 + PhpParser\Node\Stmt\ClassLike: Stmt_Class #2 className: Rector\NodeTypeResolver\Tests\NodeVisitor\FunctionMethodAndClassNodeVisitorTest\RandomProperty\foo methodName: bar ) @@ -107,40 +107,40 @@ array( 0: moreCode ) attributes: array( - classNode: Stmt_Class #2 + PhpParser\Node\Stmt\ClassLike: Stmt_Class #2 className: Rector\NodeTypeResolver\Tests\NodeVisitor\FunctionMethodAndClassNodeVisitorTest\RandomProperty\foo methodName: baz ) ) attributes: array( - classNode: Stmt_Class #2 + PhpParser\Node\Stmt\ClassLike: Stmt_Class #2 className: Rector\NodeTypeResolver\Tests\NodeVisitor\FunctionMethodAndClassNodeVisitorTest\RandomProperty\foo methodName: baz ) ) attributes: array( - classNode: Stmt_Class #2 + PhpParser\Node\Stmt\ClassLike: Stmt_Class #2 className: Rector\NodeTypeResolver\Tests\NodeVisitor\FunctionMethodAndClassNodeVisitorTest\RandomProperty\foo methodName: baz ) ) ) attributes: array( - classNode: Stmt_Class #2 + PhpParser\Node\Stmt\ClassLike: Stmt_Class #2 className: Rector\NodeTypeResolver\Tests\NodeVisitor\FunctionMethodAndClassNodeVisitorTest\RandomProperty\foo methodName: bar ) ) ) attributes: array( - classNode: Stmt_Class #2 + PhpParser\Node\Stmt\ClassLike: Stmt_Class #2 className: Rector\NodeTypeResolver\Tests\NodeVisitor\FunctionMethodAndClassNodeVisitorTest\RandomProperty\foo methodName: null ) ) ) attributes: array( - classNode: null + PhpParser\Node\Stmt\ClassLike: null className: null methodName: null ) diff --git a/packages/NodeTypeResolver/tests/NodeVisitor/FunctionMethodAndClassNodeVisitor/Fixture/simple.php.inc b/packages/NodeTypeResolver/tests/NodeVisitor/FunctionMethodAndClassNodeVisitor/Fixture/simple.php.inc index cd1c83c7298a..103e154f7e8f 100644 --- a/packages/NodeTypeResolver/tests/NodeVisitor/FunctionMethodAndClassNodeVisitor/Fixture/simple.php.inc +++ b/packages/NodeTypeResolver/tests/NodeVisitor/FunctionMethodAndClassNodeVisitor/Fixture/simple.php.inc @@ -23,7 +23,7 @@ array( 5: Simple ) attributes: array( - classNode: null + PhpParser\Node\Stmt\ClassLike: null className: null methodName: null ) @@ -34,7 +34,7 @@ array( name: Identifier #3( name: foo attributes: array( - classNode: Stmt_Class #2 + PhpParser\Node\Stmt\ClassLike: Stmt_Class #2 className: Rector\NodeTypeResolver\Tests\NodeVisitor\FunctionMethodAndClassNodeVisitorTest\Simple\foo methodName: null ) @@ -46,27 +46,27 @@ array( name: Identifier #5( name: bar attributes: array( - classNode: Stmt_Class #2 + PhpParser\Node\Stmt\ClassLike: Stmt_Class #2 className: Rector\NodeTypeResolver\Tests\NodeVisitor\FunctionMethodAndClassNodeVisitorTest\Simple\foo methodName: bar ) ) attributes: array( - classNode: Stmt_Class #2 + PhpParser\Node\Stmt\ClassLike: Stmt_Class #2 className: Rector\NodeTypeResolver\Tests\NodeVisitor\FunctionMethodAndClassNodeVisitorTest\Simple\foo methodName: bar ) ) ) attributes: array( - classNode: Stmt_Class #2 + PhpParser\Node\Stmt\ClassLike: Stmt_Class #2 className: Rector\NodeTypeResolver\Tests\NodeVisitor\FunctionMethodAndClassNodeVisitorTest\Simple\foo methodName: null ) ) ) attributes: array( - classNode: null + PhpParser\Node\Stmt\ClassLike: null className: null methodName: null ) diff --git a/packages/PHPStan/src/Rector/Assign/PHPStormVarAnnotationRector.php b/packages/PHPStan/src/Rector/Assign/PHPStormVarAnnotationRector.php index 4e5fc8586a00..6f279f9780d3 100644 --- a/packages/PHPStan/src/Rector/Assign/PHPStormVarAnnotationRector.php +++ b/packages/PHPStan/src/Rector/Assign/PHPStormVarAnnotationRector.php @@ -10,6 +10,7 @@ use PhpParser\Node\Expr\Assign; use PhpParser\Node\Expr\Variable; use PhpParser\Node\Stmt\Nop; +use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\Rector\AbstractRector; use Rector\RectorDefinition\CodeSample; @@ -21,6 +22,16 @@ */ final class PHPStormVarAnnotationRector extends AbstractRector { + /** + * @var PhpDocInfoFactory + */ + private $phpDocInfoFactory; + + public function __construct(PhpDocInfoFactory $phpDocInfoFactory) + { + $this->phpDocInfoFactory = $phpDocInfoFactory; + } + public function getDefinition(): RectorDefinition { return new RectorDefinition('Change various @var annotation formats to one PHPStorm understands', [ @@ -51,6 +62,7 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { + /** @var Node\Stmt\Expression|null $expression */ $expression = $node->getAttribute(AttributeKey::CURRENT_STATEMENT); // unable to analyze @@ -85,6 +97,10 @@ public function refactor(Node $node): ?Node // switch docs $expression->setDocComment($this->createDocComment($nextNode)); + + $expressionPhpDocInfo = $this->phpDocInfoFactory->createFromNode($expression); + $expression->setAttribute(AttributeKey::PHP_DOC_INFO, $expressionPhpDocInfo); + // invoke override $expression->setAttribute(AttributeKey::ORIGINAL_NODE, null); @@ -94,6 +110,8 @@ public function refactor(Node $node): ?Node return null; } + // remove commnets + $nextNode->setAttribute(AttributeKey::PHP_DOC_INFO, null); $nextNode->setAttribute('comments', null); return $node; diff --git a/packages/PHPStan/src/Rector/Node/RemoveNonExistingVarAnnotationRector.php b/packages/PHPStan/src/Rector/Node/RemoveNonExistingVarAnnotationRector.php index 7df718dbb44b..b444d34812ac 100644 --- a/packages/PHPStan/src/Rector/Node/RemoveNonExistingVarAnnotationRector.php +++ b/packages/PHPStan/src/Rector/Node/RemoveNonExistingVarAnnotationRector.php @@ -18,7 +18,10 @@ use PhpParser\Node\Stmt\Switch_; use PhpParser\Node\Stmt\Throw_; use PhpParser\Node\Stmt\While_; +use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode; use Rector\AttributeAwarePhpDoc\Ast\PhpDoc\AttributeAwareVarTagValueNode; +use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo; +use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\Rector\AbstractRector; use Rector\RectorDefinition\CodeSample; use Rector\RectorDefinition\RectorDefinition; @@ -76,12 +79,18 @@ public function refactor(Node $node): ?Node return null; } + $nodeContent = $this->print($node); + // clear phpdoc - @see https://regex101.com/r/uwY5KW/1 + $nodeContentWithoutPhpDoc = Strings::replace($nodeContent, '#\/\*\*(.*?)*\/#'); + // it's there - if (Strings::match($this->print($node), '#' . preg_quote($variableName, '#') . '\b#')) { + if (Strings::match($nodeContentWithoutPhpDoc, '#' . preg_quote($variableName, '#') . '\b#')) { return null; } - $this->docBlockManipulator->removeTagFromNode($node, 'var'); + /** @var PhpDocInfo $phpDocInfo */ + $phpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO); + $phpDocInfo->removeByType(VarTagValueNode::class); return $node; } diff --git a/packages/PHPUnit/src/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector.php b/packages/PHPUnit/src/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector.php index 8bdd58286fe7..a45c1ad7c3af 100644 --- a/packages/PHPUnit/src/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector.php +++ b/packages/PHPUnit/src/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector.php @@ -15,6 +15,7 @@ use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo; use Rector\FileSystemRector\Parser\FileInfoParser; use Rector\NodeContainer\ParsedNodesByType; +use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator; use Rector\Rector\AbstractPHPUnitRector; use Rector\RectorDefinition\CodeSample; @@ -150,13 +151,11 @@ private function addDoesNotPerformAssertion(ClassMethod $classMethod): void // B. extend current doc /** @var PhpDocInfo $phpDocInfo */ - $phpDocInfo = $this->getPhpDocInfo($classMethod); + $phpDocInfo = $classMethod->getAttribute(AttributeKey::PHP_DOC_INFO); $phpDocNode = $phpDocInfo->getPhpDocNode(); $phpDocNode->children[] = new AttributeAwarePhpDocTagNode('@doesNotPerformAssertion', new GenericTagValueNode( '' )); - - $this->docBlockManipulator->updateNodeWithPhpDocInfo($classMethod, $phpDocInfo); } private function containsAssertCall(ClassMethod $classMethod): bool diff --git a/packages/PHPUnit/src/Rector/ClassMethod/FixDataProviderAnnotationTypoRector.php b/packages/PHPUnit/src/Rector/ClassMethod/FixDataProviderAnnotationTypoRector.php index 498d8822e326..23e26031bc90 100644 --- a/packages/PHPUnit/src/Rector/ClassMethod/FixDataProviderAnnotationTypoRector.php +++ b/packages/PHPUnit/src/Rector/ClassMethod/FixDataProviderAnnotationTypoRector.php @@ -4,10 +4,11 @@ namespace Rector\PHPUnit\Rector\ClassMethod; -use Nette\Utils\Strings; -use PhpParser\Comment\Doc; use PhpParser\Node; use PhpParser\Node\Stmt\ClassMethod; +use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; +use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo; +use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\Rector\AbstractPHPUnitRector; use Rector\RectorDefinition\CodeSample; use Rector\RectorDefinition\RectorDefinition; @@ -70,29 +71,29 @@ public function refactor(Node $node): ?Node return null; } - if ($node->getDocComment() === null) { + /** @var PhpDocInfo|null $phpDocInfo */ + $phpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO); + if ($phpDocInfo === null) { return null; } - $textDocComment = $node->getDocComment()->getText(); + $phpDocNode = $phpDocInfo->getPhpDocNode(); + foreach ($phpDocNode->children as $phpDocChildNode) { + if (! $phpDocChildNode instanceof PhpDocTagNode) { + continue; + } + + $annotationName = $phpDocChildNode->name; + $annotationName = trim($annotationName, '()@'); - $fixedTextDocComment = Strings::replace($textDocComment, '#@(?\w+)#', function ( - $match - ): ?string { // more than 2 letter difference, probably not a typo - if (levenshtein($match['annotationName'], 'dataProvider') > 4) { - return null; + if (levenshtein($annotationName, 'dataProvider') > 4) { + continue; } - return '@dataProvider'; - }); - - if ($textDocComment === $fixedTextDocComment) { - return null; + $phpDocChildNode->name = '@dataProvider '; } - $node->setDocComment(new Doc($fixedTextDocComment)); - return $node; } } diff --git a/packages/PHPUnit/src/Rector/Class_/RemoveDataProviderTestPrefixRector.php b/packages/PHPUnit/src/Rector/Class_/RemoveDataProviderTestPrefixRector.php index 641ea899933a..432a9e959ea4 100644 --- a/packages/PHPUnit/src/Rector/Class_/RemoveDataProviderTestPrefixRector.php +++ b/packages/PHPUnit/src/Rector/Class_/RemoveDataProviderTestPrefixRector.php @@ -5,10 +5,13 @@ namespace Rector\PHPUnit\Rector\Class_; use Nette\Utils\Strings; -use PhpParser\Comment\Doc; use PhpParser\Node; use PhpParser\Node\Identifier; use PhpParser\Node\Stmt\Class_; +use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; +use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo; +use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\Rector\AbstractPHPUnitRector; use Rector\RectorDefinition\CodeSample; use Rector\RectorDefinition\RectorDefinition; @@ -20,16 +23,6 @@ */ final class RemoveDataProviderTestPrefixRector extends AbstractPHPUnitRector { - /** - * @var string - */ - private const DATA_PROVIDER_ANNOTATION_PATTERN = '#(@dataProvider\s+)(?test\w+)#'; - - /** - * @var string - */ - private const DATA_PROVIDER_EXACT_NAME_PATTERN = '#(@dataProvider\s+)(%s)#'; - /** * @var string[] */ @@ -98,7 +91,6 @@ public function refactor(Node $node): ?Node $this->providerMethodNamesToNewNames = []; $this->renameDataProviderAnnotationsAndCollectRenamedMethods($node); - $this->renameProviderMethods($node); return $node; @@ -107,31 +99,37 @@ public function refactor(Node $node): ?Node private function renameDataProviderAnnotationsAndCollectRenamedMethods(Class_ $class): void { foreach ($class->getMethods() as $classMethod) { - if ($classMethod->getDocComment() === null) { + /** @var PhpDocInfo|null $phpDocInfo */ + $phpDocInfo = $classMethod->getAttribute(AttributeKey::PHP_DOC_INFO); + if ($phpDocInfo === null) { continue; } - $docCommentText = $classMethod->getDocComment()->getText(); - if (! Strings::match($docCommentText, self::DATA_PROVIDER_ANNOTATION_PATTERN)) { + $dataProviderTags = $phpDocInfo->getTagsByName('dataProvider'); + if ($dataProviderTags === []) { continue; } - // replace the name in the doc - $matches = Strings::matchAll($docCommentText, self::DATA_PROVIDER_ANNOTATION_PATTERN); - foreach ($matches as $match) { - $currentProviderMethodName = $match['providerMethodName']; + foreach ($dataProviderTags as $dataProviderTag) { + // @todo use custom annotation object! + /** @var PhpDocTagNode $dataProviderTag */ + if (! $dataProviderTag->value instanceof GenericTagValueNode) { + continue; + } - $newMethodName = Strings::substring($currentProviderMethodName, strlen('test')); - $newMethodName = lcfirst($newMethodName); + $oldMethodName = $dataProviderTag->value->value; + if (! Strings::startsWith($oldMethodName, 'test')) { + continue; + } - $currentMethodPattern = sprintf(self::DATA_PROVIDER_EXACT_NAME_PATTERN, $currentProviderMethodName); + $newMethodName = $this->createNewMethodName($oldMethodName); + $dataProviderTag->value->value = $newMethodName; - $docCommentText = Strings::replace($docCommentText, $currentMethodPattern, '$1' . $newMethodName); + $oldMethodName = trim($oldMethodName, '()'); + $newMethodName = trim($newMethodName, '()'); - $this->providerMethodNamesToNewNames[$currentProviderMethodName] = $newMethodName; + $this->providerMethodNamesToNewNames[$oldMethodName] = $newMethodName; } - - $classMethod->setDocComment(new Doc($docCommentText)); } } @@ -147,4 +145,11 @@ private function renameProviderMethods(Class_ $class): void } } } + + private function createNewMethodName(string $oldMethodName): string + { + $newMethodName = Strings::substring($oldMethodName, strlen('test')); + + return lcfirst($newMethodName); + } } diff --git a/packages/PHPUnit/src/Rector/Class_/SelfContainerGetMethodCallFromTestToInjectPropertyRector.php b/packages/PHPUnit/src/Rector/Class_/SelfContainerGetMethodCallFromTestToInjectPropertyRector.php index c6f37bf7ca47..a7cb167fac51 100644 --- a/packages/PHPUnit/src/Rector/Class_/SelfContainerGetMethodCallFromTestToInjectPropertyRector.php +++ b/packages/PHPUnit/src/Rector/Class_/SelfContainerGetMethodCallFromTestToInjectPropertyRector.php @@ -10,6 +10,8 @@ use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode; use Rector\AttributeAwarePhpDoc\Ast\PhpDoc\AttributeAwarePhpDocTagNode; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo; +use Rector\Exception\ShouldNotHappenException; +use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\PhpParser\Node\Manipulator\ClassManipulator; use Rector\PHPUnit\Manipulator\OnContainerGetCallManipulator; use Rector\Rector\AbstractPHPUnitRector; @@ -154,18 +156,21 @@ public function refactor(Node $node): ?Node */ private function addInjectAnnotationToProperties(array $properties): void { - foreach ($properties as $privateProperty) { - $this->addInjectAnnotationToProperty($privateProperty); + foreach ($properties as $property) { + $this->addInjectAnnotationToProperty($property); } } private function addInjectAnnotationToProperty(Property $privateProperty): void { - /** @var PhpDocInfo $phpDocInfo */ - $phpDocInfo = $this->getPhpDocInfo($privateProperty); - $phpDocNode = $phpDocInfo->getPhpDocNode(); - $phpDocNode->children[] = new AttributeAwarePhpDocTagNode('@inject', new GenericTagValueNode('')); + /** @var PhpDocInfo|null $phpDocInfo */ + $phpDocInfo = $privateProperty->getAttribute(AttributeKey::PHP_DOC_INFO); - $this->docBlockManipulator->updateNodeWithPhpDocInfo($privateProperty, $phpDocInfo); + if ($phpDocInfo === null) { + throw new ShouldNotHappenException(); + } + + $injectTag = new AttributeAwarePhpDocTagNode('@inject', new GenericTagValueNode('')); + $phpDocInfo->addPhpDocTagNode($injectTag); } } diff --git a/packages/Php74/src/Rector/Property/TypedPropertyRector.php b/packages/Php74/src/Rector/Property/TypedPropertyRector.php index 02b4a9815cd0..40a683dc620d 100644 --- a/packages/Php74/src/Rector/Property/TypedPropertyRector.php +++ b/packages/Php74/src/Rector/Property/TypedPropertyRector.php @@ -10,6 +10,7 @@ use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode; use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode; use PHPStan\Type\MixedType; +use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\Rector\AbstractRector; use Rector\RectorDefinition\CodeSample; use Rector\RectorDefinition\RectorDefinition; @@ -112,7 +113,7 @@ public function refactor(Node $node): ?Node private function removeVarPhpTagValueNodeIfNotComment(Property $property): void { - $propertyPhpDocInfo = $this->getPhpDocInfo($property); + $propertyPhpDocInfo = $property->getAttribute(AttributeKey::PHP_DOC_INFO); // nothing to remove if ($propertyPhpDocInfo === null) { return; @@ -139,7 +140,6 @@ private function removeVarPhpTagValueNodeIfNotComment(Property $property): void } $propertyPhpDocInfo->removeByType(VarTagValueNode::class); - $this->docBlockManipulator->updateNodeWithPhpDocInfo($property, $propertyPhpDocInfo); } private function isNonBasicArrayType(Property $property, VarTagValueNode $varTagValueNode): bool diff --git a/packages/Php80/src/Rector/FunctionLike/UnionTypesRector.php b/packages/Php80/src/Rector/FunctionLike/UnionTypesRector.php index 51d23fe5a0bd..dedbe23aa562 100644 --- a/packages/Php80/src/Rector/FunctionLike/UnionTypesRector.php +++ b/packages/Php80/src/Rector/FunctionLike/UnionTypesRector.php @@ -12,6 +12,7 @@ use PhpParser\Node\UnionType as PhpParserUnionType; use PHPStan\Type\UnionType; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo; +use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\Rector\AbstractRector; use Rector\RectorDefinition\CodeSample; use Rector\RectorDefinition\RectorDefinition; @@ -69,7 +70,7 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - $phpDocInfo = $this->getPhpDocInfo($node); + $phpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO); if ($phpDocInfo === null) { return null; } diff --git a/packages/Renaming/src/Rector/Class_/RenameClassRector.php b/packages/Renaming/src/Rector/Class_/RenameClassRector.php index ae74e0997e66..deb4fedcbbf3 100644 --- a/packages/Renaming/src/Rector/Class_/RenameClassRector.php +++ b/packages/Renaming/src/Rector/Class_/RenameClassRector.php @@ -149,7 +149,7 @@ public function refactor(Node $node): ?Node */ private function refactorPhpDoc(Node $node): void { - $nodePhpDocInfo = $this->getPhpDocInfo($node); + $nodePhpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO); if ($nodePhpDocInfo === null) { return; } diff --git a/packages/SOLID/src/Rector/If_/ChangeIfElseValueAssignToEarlyReturnRector.php b/packages/SOLID/src/Rector/If_/ChangeIfElseValueAssignToEarlyReturnRector.php index cc64f61fc897..3530dda4260b 100644 --- a/packages/SOLID/src/Rector/If_/ChangeIfElseValueAssignToEarlyReturnRector.php +++ b/packages/SOLID/src/Rector/If_/ChangeIfElseValueAssignToEarlyReturnRector.php @@ -101,14 +101,16 @@ public function refactor(Node $node): ?Node return null; } + $lastIfStmtKey = array_key_last($node->stmts); + /** @var Assign $assign */ $assign = $this->stmtsManipulator->getUnwrappedLastStmt($node->stmts); - $lastIfStmtKey = array_key_last($node->stmts); $node->stmts[$lastIfStmtKey] = new Return_($assign->expr); /** @var Assign $assign */ $assign = $this->stmtsManipulator->getUnwrappedLastStmt($node->else->stmts); + $lastElseStmtKey = array_key_last($node->else->stmts); $elseStmts = $node->else->stmts; diff --git a/packages/Sensio/src/Rector/FrameworkExtraBundle/TemplateAnnotationRector.php b/packages/Sensio/src/Rector/FrameworkExtraBundle/TemplateAnnotationRector.php index 051a8d6ffe6b..a85fec33f153 100644 --- a/packages/Sensio/src/Rector/FrameworkExtraBundle/TemplateAnnotationRector.php +++ b/packages/Sensio/src/Rector/FrameworkExtraBundle/TemplateAnnotationRector.php @@ -14,6 +14,7 @@ use PhpParser\Node\Stmt\Return_; use Rector\BetterPhpDocParser\PhpDocNode\Sensio\SensioTemplateTagValueNode; use Rector\NodeContainer\ParsedNodesByType; +use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\Rector\AbstractRector; use Rector\RectorDefinition\CodeSample; use Rector\RectorDefinition\RectorDefinition; @@ -114,7 +115,7 @@ private function addAbstractControllerParentClassIfMissing(Class_ $node): ?Class private function classHasTemplateAnnotations(Class_ $node): bool { foreach ($node->getMethods() as $classMethod) { - $phpDocInfo = $this->getPhpDocInfo($classMethod); + $phpDocInfo = $classMethod->getAttribute(AttributeKey::PHP_DOC_INFO); if ($phpDocInfo === null) { continue; } @@ -263,7 +264,7 @@ private function refactorClassMethod( private function getSensioTemplateTagValueNode(ClassMethod $classMethod): ?SensioTemplateTagValueNode { - $phpDocInfo = $this->getPhpDocInfo($classMethod); + $phpDocInfo = $classMethod->getAttribute(AttributeKey::PHP_DOC_INFO); if ($phpDocInfo === null) { return null; } diff --git a/packages/StrictCodeQuality/src/Rector/Stmt/VarInlineAnnotationToAssertRector.php b/packages/StrictCodeQuality/src/Rector/Stmt/VarInlineAnnotationToAssertRector.php index 2d79f58cbc63..c20e3b2e03d4 100644 --- a/packages/StrictCodeQuality/src/Rector/Stmt/VarInlineAnnotationToAssertRector.php +++ b/packages/StrictCodeQuality/src/Rector/Stmt/VarInlineAnnotationToAssertRector.php @@ -21,6 +21,7 @@ use PHPStan\Type\StringType; use PHPStan\Type\Type; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo; +use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\Rector\AbstractRector; use Rector\RectorDefinition\CodeSample; use Rector\RectorDefinition\RectorDefinition; @@ -73,13 +74,13 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - $phpDocInfo = $this->getPhpDocInfo($node); - if ($phpDocInfo === null) { + // skip properties + if ($node instanceof Property) { return null; } - // skip properties - if ($node instanceof Property) { + $phpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO); + if ($phpDocInfo === null) { return null; } @@ -133,6 +134,7 @@ private function isVariableJustCreated(Node $node, string $docVariableName): boo if (! $node instanceof Expression) { return false; } + if (! $node->expr instanceof Assign) { return false; } @@ -149,7 +151,7 @@ private function isVariableJustCreated(Node $node, string $docVariableName): boo private function refactorFreshlyCreatedNode(Node $node, PhpDocInfo $phpDocInfo, Variable $variable): ?Node { - $node->setAttribute('comments', []); + $node->setAttribute('comments', null); $type = $phpDocInfo->getVarType(); $assertFuncCall = $this->createFuncCallBasedOnType($type, $variable); @@ -157,7 +159,8 @@ private function refactorFreshlyCreatedNode(Node $node, PhpDocInfo $phpDocInfo, return null; } - $this->removeVarAnnotation($variable, $phpDocInfo); + $phpDocInfo->removeByType(VarTagValueNode::class); + $this->addNodeBeforeNode($assertFuncCall, $node); return $node; @@ -166,7 +169,6 @@ private function refactorFreshlyCreatedNode(Node $node, PhpDocInfo $phpDocInfo, private function refactorAlreadyCreatedNode(Node $node, PhpDocInfo $phpDocInfo, Variable $variable): ?Node { $varTagValue = $phpDocInfo->getVarTagValue(); - $phpStanType = $this->staticTypeMapper->mapPHPStanPhpDocTypeNodeToPHPStanType( $varTagValue->type, $variable @@ -177,7 +179,6 @@ private function refactorAlreadyCreatedNode(Node $node, PhpDocInfo $phpDocInfo, return null; } - $this->removeVarAnnotation($variable, $phpDocInfo); $this->addNodeAfterNode($assertFuncCall, $node); return $node; @@ -212,11 +213,4 @@ private function createFuncCallBasedOnType(Type $type, Variable $variable): ?Fun return null; } - - private function removeVarAnnotation(Node $node, PhpDocInfo $phpDocInfo): void - { - $phpDocInfo->removeByType(VarTagValueNode::class); - - $this->docBlockManipulator->updateNodeWithPhpDocInfo($node, $phpDocInfo); - } } diff --git a/packages/StrictCodeQuality/tests/Rector/Stmt/VarInlineAnnotationToAssertRector/Fixture/single_scalar_assert.php.inc b/packages/StrictCodeQuality/tests/Rector/Stmt/VarInlineAnnotationToAssertRector/Fixture/single_scalar_assert.php.inc new file mode 100644 index 000000000000..49d97390208a --- /dev/null +++ b/packages/StrictCodeQuality/tests/Rector/Stmt/VarInlineAnnotationToAssertRector/Fixture/single_scalar_assert.php.inc @@ -0,0 +1,29 @@ +call(); + } +} + +?> +----- +call(); + } +} + +?> diff --git a/packages/Symfony/src/Rector/ClassMethod/MergeMethodAnnotationToRouteAnnotationRector.php b/packages/Symfony/src/Rector/ClassMethod/MergeMethodAnnotationToRouteAnnotationRector.php index c8505b6de51b..67f99ed6f6c4 100644 --- a/packages/Symfony/src/Rector/ClassMethod/MergeMethodAnnotationToRouteAnnotationRector.php +++ b/packages/Symfony/src/Rector/ClassMethod/MergeMethodAnnotationToRouteAnnotationRector.php @@ -84,11 +84,12 @@ public function refactor(Node $node): ?Node return null; } - $phpDocInfo = $this->getPhpDocInfo($node); + $phpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO); if ($phpDocInfo === null) { return null; } + /** @var SensioMethodTagValueNode|null $symfonyMethodPhpDocTagValueNode */ $symfonyMethodPhpDocTagValueNode = $phpDocInfo->getByType(SensioMethodTagValueNode::class); if ($symfonyMethodPhpDocTagValueNode === null) { return null; @@ -102,8 +103,6 @@ public function refactor(Node $node): ?Node $phpDocInfo->removeTagValueNodeFromNode($symfonyMethodPhpDocTagValueNode); - $this->docBlockManipulator->updateNodeWithPhpDocInfo($node, $phpDocInfo); - return $node; } } diff --git a/packages/TypeDeclaration/src/Rector/ClassMethod/AddArrayReturnDocTypeRector.php b/packages/TypeDeclaration/src/Rector/ClassMethod/AddArrayReturnDocTypeRector.php index d76aeb1dd7e6..2a862a71f1f2 100644 --- a/packages/TypeDeclaration/src/Rector/ClassMethod/AddArrayReturnDocTypeRector.php +++ b/packages/TypeDeclaration/src/Rector/ClassMethod/AddArrayReturnDocTypeRector.php @@ -12,6 +12,7 @@ use PHPStan\Type\MixedType; use PHPStan\Type\Type; use PHPStan\Type\UnionType; +use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\Rector\AbstractRector; use Rector\RectorDefinition\CodeSample; use Rector\RectorDefinition\RectorDefinition; @@ -125,7 +126,7 @@ private function shouldSkip(ClassMethod $classMethod): bool return true; } - $currentPhpDocInfo = $this->getPhpDocInfo($classMethod); + $currentPhpDocInfo = $classMethod->getAttribute(AttributeKey::PHP_DOC_INFO); if ($currentPhpDocInfo === null) { return false; } @@ -162,7 +163,7 @@ private function shouldSkipArrayType(ArrayType $arrayType, ClassMethod $classMet private function isNewAndCurrentTypeBothCallable(ArrayType $newArrayType, ClassMethod $classMethod): bool { - $currentPhpDocInfo = $this->getPhpDocInfo($classMethod); + $currentPhpDocInfo = $classMethod->getAttribute(AttributeKey::PHP_DOC_INFO); if ($currentPhpDocInfo === null) { return false; } @@ -185,7 +186,7 @@ private function isMixedOfSpecificOverride(ArrayType $arrayType, ClassMethod $cl return false; } - $currentPhpDocInfo = $this->getPhpDocInfo($classMethod); + $currentPhpDocInfo = $classMethod->getAttribute(AttributeKey::PHP_DOC_INFO); if ($currentPhpDocInfo === null) { return false; } diff --git a/packages/TypeDeclaration/src/TypeInferer/PropertyTypeInferer/DoctrineColumnPropertyTypeInferer.php b/packages/TypeDeclaration/src/TypeInferer/PropertyTypeInferer/DoctrineColumnPropertyTypeInferer.php index b97f521c533f..4b57e81689cc 100644 --- a/packages/TypeDeclaration/src/TypeInferer/PropertyTypeInferer/DoctrineColumnPropertyTypeInferer.php +++ b/packages/TypeDeclaration/src/TypeInferer/PropertyTypeInferer/DoctrineColumnPropertyTypeInferer.php @@ -14,8 +14,9 @@ use PHPStan\Type\ObjectType; use PHPStan\Type\StringType; use PHPStan\Type\Type; +use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo; use Rector\BetterPhpDocParser\PhpDocNode\Doctrine\Property_\ColumnTagValueNode; -use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator; +use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory; use Rector\TypeDeclaration\Contract\TypeInferer\PropertyTypeInfererInterface; @@ -26,11 +27,6 @@ final class DoctrineColumnPropertyTypeInferer implements PropertyTypeInfererInte */ private $typeFactory; - /** - * @var DocBlockManipulator - */ - private $docBlockManipulator; - /** * @var Type[] * @@ -39,10 +35,9 @@ final class DoctrineColumnPropertyTypeInferer implements PropertyTypeInfererInte */ private $doctrineTypeToScalarType = []; - public function __construct(TypeFactory $typeFactory, DocBlockManipulator $docBlockManipulator) + public function __construct(TypeFactory $typeFactory) { $this->typeFactory = $typeFactory; - $this->docBlockManipulator = $docBlockManipulator; $this->doctrineTypeToScalarType = [ 'tinyint' => new BooleanType(), @@ -84,12 +79,12 @@ public function __construct(TypeFactory $typeFactory, DocBlockManipulator $docBl public function inferProperty(Property $property): Type { - if ($property->getDocComment() === null) { + /** @var PhpDocInfo|null $phpDocInfo */ + $phpDocInfo = $property->getAttribute(AttributeKey::PHP_DOC_INFO); + if ($phpDocInfo === null) { return new MixedType(); } - $phpDocInfo = $this->docBlockManipulator->createPhpDocInfoFromNode($property); - $doctrineColumnTagValueNode = $phpDocInfo->getByType(ColumnTagValueNode::class); if ($doctrineColumnTagValueNode === null) { return new MixedType(); diff --git a/packages/TypeDeclaration/src/TypeInferer/PropertyTypeInferer/DoctrineRelationPropertyTypeInferer.php b/packages/TypeDeclaration/src/TypeInferer/PropertyTypeInferer/DoctrineRelationPropertyTypeInferer.php index 4add4e50d303..b2d0928b5941 100644 --- a/packages/TypeDeclaration/src/TypeInferer/PropertyTypeInferer/DoctrineRelationPropertyTypeInferer.php +++ b/packages/TypeDeclaration/src/TypeInferer/PropertyTypeInferer/DoctrineRelationPropertyTypeInferer.php @@ -12,8 +12,9 @@ use Rector\BetterPhpDocParser\Contract\Doctrine\DoctrineRelationTagValueNodeInterface; use Rector\BetterPhpDocParser\Contract\Doctrine\ToManyTagNodeInterface; use Rector\BetterPhpDocParser\Contract\Doctrine\ToOneTagNodeInterface; +use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo; use Rector\BetterPhpDocParser\PhpDocNode\Doctrine\Property_\JoinColumnTagValueNode; -use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator; +use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory; use Rector\PHPStan\Type\FullyQualifiedObjectType; use Rector\TypeDeclaration\Contract\TypeInferer\PropertyTypeInfererInterface; @@ -25,29 +26,24 @@ final class DoctrineRelationPropertyTypeInferer implements PropertyTypeInfererIn */ private const COLLECTION_TYPE = 'Doctrine\Common\Collections\Collection'; - /** - * @var DocBlockManipulator - */ - private $docBlockManipulator; - /** * @var TypeFactory */ private $typeFactory; - public function __construct(DocBlockManipulator $docBlockManipulator, TypeFactory $typeFactory) + public function __construct(TypeFactory $typeFactory) { - $this->docBlockManipulator = $docBlockManipulator; $this->typeFactory = $typeFactory; } public function inferProperty(Property $property): Type { - if ($property->getDocComment() === null) { + /** @var PhpDocInfo|null $phpDocInfo */ + $phpDocInfo = $property->getAttribute(AttributeKey::PHP_DOC_INFO); + if ($phpDocInfo === null) { return new MixedType(); } - $phpDocInfo = $this->docBlockManipulator->createPhpDocInfoFromNode($property); $relationTagValueNode = $phpDocInfo->getByType(DoctrineRelationTagValueNodeInterface::class); if ($relationTagValueNode === null) { return new MixedType(); diff --git a/packages/TypeDeclaration/src/TypeInferer/PropertyTypeInferer/VarDocPropertyTypeInferer.php b/packages/TypeDeclaration/src/TypeInferer/PropertyTypeInferer/VarDocPropertyTypeInferer.php index 6ea99f6f85ce..3fabe403f4b0 100644 --- a/packages/TypeDeclaration/src/TypeInferer/PropertyTypeInferer/VarDocPropertyTypeInferer.php +++ b/packages/TypeDeclaration/src/TypeInferer/PropertyTypeInferer/VarDocPropertyTypeInferer.php @@ -7,30 +7,18 @@ use PhpParser\Node\Stmt\Property; use PHPStan\Type\MixedType; use PHPStan\Type\Type; -use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator; +use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo; +use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\TypeDeclaration\Contract\TypeInferer\PropertyTypeInfererInterface; final class VarDocPropertyTypeInferer implements PropertyTypeInfererInterface { - /** - * @var DocBlockManipulator - */ - private $docBlockManipulator; - - public function __construct(DocBlockManipulator $docBlockManipulator) - { - $this->docBlockManipulator = $docBlockManipulator; - } - public function inferProperty(Property $property): Type { - if ($property->getDocComment() === null) { - return new MixedType(); - } - - $phpDocInfo = $this->docBlockManipulator->createPhpDocInfoFromNode($property); + /** @var PhpDocInfo|null $phpDocInfo */ + $phpDocInfo = $property->getAttribute(AttributeKey::PHP_DOC_INFO); - return $phpDocInfo->getVarType(); + return $phpDocInfo !== null ? $phpDocInfo->getVarType() : new MixedType(); } public function getPriority(): int diff --git a/phpstan.neon b/phpstan.neon index 1f7f31781824..af9a67f3ccb6 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -233,3 +233,6 @@ parameters: - '#Parameter \#1 \$node of method PHPStan\\Analyser\\Scope\:\:getType\(\) expects PhpParser\\Node\\Expr, PhpParser\\Node given#' - '#Parameter \#2 \$name of class PhpParser\\Node\\Expr\\MethodCall constructor expects PhpParser\\Node\\Expr\|PhpParser\\Node\\Identifier\|string, string\|null given#' + - '#Ternary operator condition is always false#' + + - '#Parameter \#1 \$tagValueNode of method Rector\\BetterPhpDocParser\\PhpDocInfo\\PhpDocInfo\:\:addTagValueNodeWithShortName\(\) expects Rector\\BetterPhpDocParser\\PhpDocNode\\AbstractTagValueNode, PHPStan\\PhpDocParser\\Ast\\PhpDoc\\PhpDocTagNode\|Rector\\BetterPhpDocParser\\PhpDocNode\\Doctrine\\Property_\\JoinColumnTagValueNode given#' diff --git a/src/Application/AppliedRectorCollector.php b/src/Application/AppliedRectorCollector.php index beb4ac9a1e24..4eb22e13c985 100644 --- a/src/Application/AppliedRectorCollector.php +++ b/src/Application/AppliedRectorCollector.php @@ -23,10 +23,10 @@ public function addRectorClass(string $rectorClass, SmartFileInfo $smartFileInfo */ public function getRectorClasses(SmartFileInfo $smartFileInfo): array { - if (isset($this->rectorClassesByFile[$smartFileInfo->getRealPath()])) { - return array_unique($this->rectorClassesByFile[$smartFileInfo->getRealPath()]); + if (! isset($this->rectorClassesByFile[$smartFileInfo->getRealPath()])) { + return []; } - return []; + return array_unique($this->rectorClassesByFile[$smartFileInfo->getRealPath()]); } } diff --git a/src/PhpDoc/PhpDocClassRenamer.php b/src/PhpDoc/PhpDocClassRenamer.php index c657e02fdf18..d36f403edcb2 100644 --- a/src/PhpDoc/PhpDocClassRenamer.php +++ b/src/PhpDoc/PhpDocClassRenamer.php @@ -4,38 +4,15 @@ namespace Rector\PhpDoc; -use PhpParser\Comment\Doc; use PhpParser\Node; use Rector\BetterPhpDocParser\Contract\Doctrine\DoctrineRelationTagValueNodeInterface; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo; -use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; use Rector\BetterPhpDocParser\PhpDocNode\JMS\SerializerTypeTagValueNode; use Rector\BetterPhpDocParser\PhpDocNode\Symfony\Validator\Constraints\AssertChoiceTagValueNode; -use Rector\BetterPhpDocParser\Printer\PhpDocInfoPrinter; +use Rector\NodeTypeResolver\Node\AttributeKey; final class PhpDocClassRenamer { - /** - * @var PhpDocInfoFactory - */ - private $phpDocInfoFactory; - - /** - * @var PhpDocInfoPrinter - */ - private $phpDocInfoPrinter; - - /** - * @var bool - */ - private $shouldUpdate = false; - - public function __construct(PhpDocInfoFactory $phpDocInfoFactory, PhpDocInfoPrinter $phpDocInfoPrinter) - { - $this->phpDocInfoFactory = $phpDocInfoFactory; - $this->phpDocInfoPrinter = $phpDocInfoPrinter; - } - /** * Covers annotations like @ORM, @Serializer, @Assert etc * See https://github.com/rectorphp/rector/issues/1872 @@ -44,24 +21,15 @@ public function __construct(PhpDocInfoFactory $phpDocInfoFactory, PhpDocInfoPrin */ public function changeTypeInAnnotationTypes(Node $node, array $oldToNewClasses): void { - $docComment = $node->getDocComment(); - if ($docComment === null) { + /** @var PhpDocInfo|null $phpDocInfo */ + $phpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO); + if ($phpDocInfo === null) { return; } - $this->shouldUpdate = false; - - $phpDocInfo = $this->phpDocInfoFactory->createFromNode($node); $this->processAssertChoiceTagValueNode($oldToNewClasses, $phpDocInfo); $this->processDoctrineRelationTagValueNode($oldToNewClasses, $phpDocInfo); $this->processSerializerTypeTagValueNode($oldToNewClasses, $phpDocInfo); - - if (! $this->shouldUpdate) { - return; - } - - $textDocComment = $this->phpDocInfoPrinter->printFormatPreserving($phpDocInfo); - $node->setDocComment(new Doc($textDocComment)); } /** @@ -80,7 +48,7 @@ private function processAssertChoiceTagValueNode(array $oldToNewClasses, PhpDocI } $choiceTagValueNode->changeCallbackClass($newClass); - $this->shouldUpdate = true; +// $this->shouldUpdate = true; break; } } @@ -101,7 +69,6 @@ private function processDoctrineRelationTagValueNode(array $oldToNewClasses, Php } $relationTagValueNode->changeTargetEntity($newClass); - $this->shouldUpdate = true; break; } } @@ -117,9 +84,7 @@ private function processSerializerTypeTagValueNode(array $oldToNewClasses, PhpDo } foreach ($oldToNewClasses as $oldClass => $newClass) { - if ($serializerTypeTagValueNode->replaceName($oldClass, $newClass)) { - $this->shouldUpdate = true; - } + $serializerTypeTagValueNode->replaceName($oldClass, $newClass); } } } diff --git a/src/PhpParser/Node/NodeFactory.php b/src/PhpParser/Node/NodeFactory.php index 3bb6288ec4b7..dd68318e6c89 100644 --- a/src/PhpParser/Node/NodeFactory.php +++ b/src/PhpParser/Node/NodeFactory.php @@ -30,6 +30,7 @@ use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Property; use PHPStan\Type\Type; +use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; use Rector\Exception\NotImplementedException; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\NodeTypeResolver\StaticTypeMapper; @@ -49,10 +50,19 @@ final class NodeFactory */ private $staticTypeMapper; - public function __construct(BuilderFactory $builderFactory, StaticTypeMapper $staticTypeMapper) - { + /** + * @var PhpDocInfoFactory + */ + private $phpDocInfoFactory; + + public function __construct( + BuilderFactory $builderFactory, + StaticTypeMapper $staticTypeMapper, + PhpDocInfoFactory $phpDocInfoFactory + ) { $this->builderFactory = $builderFactory; $this->staticTypeMapper = $staticTypeMapper; + $this->phpDocInfoFactory = $phpDocInfoFactory; } /** @@ -164,12 +174,17 @@ public function createPrivatePropertyFromNameAndType(string $name, ?Type $type): $propertyBuilder->makePrivate(); if ($type !== null) { + // @todo use PhpDocInfo approach $docComment = $this->createVarDoc($type); $propertyBuilder->setDocComment($docComment); } $property = $propertyBuilder->getNode(); + // add PHP_DOC_INFO + $phpDocInfo = $this->phpDocInfoFactory->createFromNode($property); + $property->setAttribute(AttributeKey::PHP_DOC_INFO, $phpDocInfo); + $this->decorateParentPropertyProperty($property); return $property; diff --git a/src/PhpParser/Printer/BetterStandardPrinter.php b/src/PhpParser/Printer/BetterStandardPrinter.php index 8bc74bc2e047..c1e2feeca69e 100644 --- a/src/PhpParser/Printer/BetterStandardPrinter.php +++ b/src/PhpParser/Printer/BetterStandardPrinter.php @@ -21,6 +21,7 @@ use PhpParser\Node\Stmt\TraitUse; use PhpParser\PrettyPrinter\Standard; use Rector\NodeTypeResolver\Node\AttributeKey; +use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator; use Symplify\SmartFileSystem\SmartFileInfo; /** @@ -34,6 +35,11 @@ final class BetterStandardPrinter extends Standard */ private $tabOrSpaceIndentCharacter = ' '; + /** + * @var DocBlockManipulator + */ + private $docBlockManipulator; + /** * @param mixed[] $options */ @@ -48,6 +54,14 @@ public function __construct(array $options = []) $this->insertionMap['Expr_Closure->returnType'] = [')', false, ': ', null]; } + /** + * @required + */ + public function autowireBetterStandardPrinter(DocBlockManipulator $docBlockManipulator): void + { + $this->docBlockManipulator = $docBlockManipulator; + } + public function printFormatPreserving(array $stmts, array $origStmts, array $origTokens): string { // detect per print @@ -60,6 +74,17 @@ public function printFormatPreserving(array $stmts, array $origStmts, array $ori return Strings::replace($clearContent, '#\<\?php(\s+)\?\>#'); } + /** + * @param Node|Node[]|null $node + */ + public function printWithoutComments($node): string + { + $printerNode = $this->print($node); + + $nodeWithoutComments = $this->removeComments($printerNode); + return trim($nodeWithoutComments); + } + /** * @param Node|Node[]|null $node */ @@ -73,12 +98,6 @@ public function print($node): string return 'UNABLE_TO_PRINT_ENCAPSED_STRING'; } - // remove comments, for value compare - if ($node instanceof Node) { - $node = clone $node; - $node->setAttribute('comments', null); - } - if (! is_array($node)) { $node = [$node]; } @@ -95,6 +114,15 @@ public function areNodesEqual($firstNode, $secondNode): bool return $this->print($firstNode) === $this->print($secondNode); } + /** + * @param Node|Node[]|null $firstNode + * @param Node|Node[]|null $secondNode + */ + public function areNodesWithoutCommentsEqual($firstNode, $secondNode): bool + { + return $this->printWithoutComments($firstNode) === $this->printWithoutComments($secondNode); + } + /** * This allows to use both spaces and tabs vs. original space-only */ @@ -150,6 +178,8 @@ protected function pArray( // reindex positions for printer $nodes = array_values($nodes); + $this->moveCommentsFromAttributeObjectToCommentsAttribute($nodes); + $content = parent::pArray($nodes, $origNodes, $pos, $indentAdjustment, $parentNodeType, $subNodeName, $fixup); if ($content === null) { @@ -191,11 +221,16 @@ protected function pScalar_DNumber(DNumber $DNumber): string } /** - * Add space after "use (" + * Add space: + * "use(" + * ↓ + * "use (" */ protected function pExpr_Closure(Closure $closure): string { - return Strings::replace(parent::pExpr_Closure($closure), '#( use)\(#', '$1 ('); + $closureContent = parent::pExpr_Closure($closure); + + return Strings::replace($closureContent, '#( use)\(#', '$1 ('); } /** @@ -246,6 +281,16 @@ protected function pScalar_String(String_ $node): string return parent::pScalar_String($node); } + /** + * @param Node[] $nodes + */ + protected function pStmts(array $nodes, bool $indent = true): string + { + $this->moveCommentsFromAttributeObjectToCommentsAttribute($nodes); + + return parent::pStmts($nodes, $indent); + } + /** * "...$params) : ReturnType" * ↓ @@ -339,4 +384,25 @@ private function containsNop(array $nodes): bool return false; } + + /** + * @todo decopule + */ + private function moveCommentsFromAttributeObjectToCommentsAttribute(array $nodes): void + { + // move phpdoc from node to "comment" attirbute + foreach ($nodes as $node) { + if (! $node instanceof Node) { + continue; + } + + $this->docBlockManipulator->updateNodeWithPhpDocInfo($node); + } + } + + private function removeComments(string $printerNode): string + { + $printerNode = Strings::replace($printerNode, '#\/*\*(.*?)\*\/#'); + return Strings::replace($printerNode, '#\/\/(.*?)$#m'); + } } diff --git a/src/Rector/AbstractRector.php b/src/Rector/AbstractRector.php index 19192ade3e25..061f8d4c79e3 100644 --- a/src/Rector/AbstractRector.php +++ b/src/Rector/AbstractRector.php @@ -14,12 +14,18 @@ use PhpParser\Node\Stmt\Expression; use PhpParser\NodeVisitorAbstract; use PHPStan\Analyser\Scope; +use Rector\BetterPhpDocParser\Printer\PhpDocInfoPrinter; +use Rector\CodingStyle\Rector\ClassMethod\NewlineBeforeNewAssignSetRector; +use Rector\CodingStyle\Rector\Namespace_\ImportFullyQualifiedNamesRector; use Rector\Commander\CommanderCollector; use Rector\Contract\PhpParser\Node\CommanderInterface; use Rector\Contract\Rector\PhpRectorInterface; +use Rector\DeadCode\Rector\FunctionLike\RemoveCodeAfterReturnRector; use Rector\Exclusion\ExclusionManager; use Rector\NodeTypeResolver\FileSystem\CurrentFileInfoProvider; use Rector\NodeTypeResolver\Node\AttributeKey; +use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator; +use Rector\NodeTypeResolver\StaticTypeMapper; use Rector\Php\PhpVersionProvider; use Rector\Rector\AbstractRector\AbstractRectorTrait; use Rector\Rector\AbstractRector\NodeCommandersTrait; @@ -60,6 +66,21 @@ abstract class AbstractRector extends NodeVisitorAbstract implements PhpRectorIn */ private $currentFileInfoProvider; + /** + * @var PhpDocInfoPrinter + */ + protected $phpDocInfoPrinter; + + /** + * @var DocBlockManipulator + */ + protected $docBlockManipulator; + + /** + * @var StaticTypeMapper + */ + protected $staticTypeMapper; + /** * Run once in the every end of one processed file */ @@ -76,7 +97,10 @@ public function autowireAbstractRectorDependencies( BuilderFactory $builderFactory, ExclusionManager $exclusionManager, CommanderCollector $commanderCollector, - CurrentFileInfoProvider $currentFileInfoProvider + CurrentFileInfoProvider $currentFileInfoProvider, + PhpDocInfoPrinter $phpDocInfoPrinter, + DocBlockManipulator $docBlockManipulator, + StaticTypeMapper $staticTypeMapper ): void { $this->symfonyStyle = $symfonyStyle; $this->phpVersionProvider = $phpVersionProvider; @@ -84,6 +108,9 @@ public function autowireAbstractRectorDependencies( $this->exclusionManager = $exclusionManager; $this->commanderCollector = $commanderCollector; $this->currentFileInfoProvider = $currentFileInfoProvider; + $this->phpDocInfoPrinter = $phpDocInfoPrinter; + $this->docBlockManipulator = $docBlockManipulator; + $this->staticTypeMapper = $staticTypeMapper; } /** @@ -112,8 +139,6 @@ final public function enterNode(Node $node) $originalNode = $node->getAttribute(AttributeKey::ORIGINAL_NODE) ?? clone $node; $originalNodeWithAttributes = clone $node; - $originalComment = $node->getComments(); - $originalDocComment = $node->getDocComment(); $node = $this->refactor($node); // nothing to change → continue @@ -127,10 +152,6 @@ final public function enterNode(Node $node) $this->updateAttributes($node); $this->keepFileInfoAttribute($node, $originalNode); $this->notifyNodeChangeFileInfo($node); - - // doc block has changed - } elseif ($node->getComments() !== $originalComment || $node->getDocComment() !== $originalDocComment) { - $this->notifyNodeChangeFileInfo($node); } // if stmt ("$value;") was replaced by expr ("$value"), add the ending ";" (Expression) to prevent breaking the code @@ -275,6 +296,10 @@ private function updateAttributes(Node $node): void private function hasNodeChanged(Node $originalNode, Node $node): bool { + if ($this->isNameIdentical($node, $originalNode)) { + return false; + } + return ! $this->areNodesEqual($originalNode, $node); } @@ -314,4 +339,18 @@ protected function unwrapStmts(array $stmts, Node $node): void $this->addNodeAfterNode($ifStmt, $node); } } + + private function isNameIdentical(Node $node, Node $originalNode): bool + { + if (static::class !== ImportFullyQualifiedNamesRector::class) { + return false; + } + + if (! $originalNode instanceof Name) { + return false; + } + + // names are the same + return $this->areNodesEqual($originalNode->getAttribute('originalName'), $node); + } } diff --git a/src/Rector/AbstractRector/AbstractRectorTrait.php b/src/Rector/AbstractRector/AbstractRectorTrait.php index 660eed8ac4ba..f1a47497c685 100644 --- a/src/Rector/AbstractRector/AbstractRectorTrait.php +++ b/src/Rector/AbstractRector/AbstractRectorTrait.php @@ -12,7 +12,6 @@ trait AbstractRectorTrait { use AppliedRectorCollectorTrait; - use DocBlockManipulatorTrait; use DoctrineTrait; use NodeTypeResolverTrait; use NameResolverTrait; diff --git a/src/Rector/AbstractRector/BetterStandardPrinterTrait.php b/src/Rector/AbstractRector/BetterStandardPrinterTrait.php index b48369e1ea21..0ac658b40368 100644 --- a/src/Rector/AbstractRector/BetterStandardPrinterTrait.php +++ b/src/Rector/AbstractRector/BetterStandardPrinterTrait.php @@ -63,6 +63,15 @@ protected function areNodesEqual($firstNode, $secondNode): bool return $this->betterStandardPrinter->areNodesEqual($firstNode, $secondNode); } + /** + * @param Node|Node[]|null $firstNode + * @param Node|Node[]|null $secondNode + */ + protected function areNodesWithoutCommentsEqual($firstNode, $secondNode): bool + { + return $this->betterStandardPrinter->areNodesWithoutCommentsEqual($firstNode, $secondNode); + } + /** * @param Node[] $availableNodes */ @@ -77,7 +86,7 @@ protected function isNodeEqual(Node $singleNode, array $availableNodes): bool $availableNode = clone $availableNode; $availableNode->setAttribute('comments', null); - if ($this->areNodesEqual($singleNode, $availableNode)) { + if ($this->areNodesWithoutCommentsEqual($singleNode, $availableNode)) { return true; } } diff --git a/src/Rector/AbstractRector/DocBlockManipulatorTrait.php b/src/Rector/AbstractRector/DocBlockManipulatorTrait.php deleted file mode 100644 index abbd983e7dd6..000000000000 --- a/src/Rector/AbstractRector/DocBlockManipulatorTrait.php +++ /dev/null @@ -1,47 +0,0 @@ -docBlockManipulator = $docBlockManipulator; - $this->staticTypeMapper = $staticTypeMapper; - } - - protected function getPhpDocInfo(Node $node): ?PhpDocInfo - { - if ($node->getDocComment() === null) { - return null; - } - - return $this->docBlockManipulator->createPhpDocInfoFromNode($node); - } -} diff --git a/src/Rector/AbstractRector/NodeFactoryTrait.php b/src/Rector/AbstractRector/NodeFactoryTrait.php index aa9358e36ae8..a561d0395dc2 100644 --- a/src/Rector/AbstractRector/NodeFactoryTrait.php +++ b/src/Rector/AbstractRector/NodeFactoryTrait.php @@ -101,6 +101,8 @@ protected function createArray(array $nodes): Array_ */ protected function createFunction(string $name, array $arguments = []): FuncCall { + $arguments = $this->createArgs($arguments); + return new FuncCall(new Name($name), $arguments); } diff --git a/src/Rector/Property/InjectAnnotationClassRector.php b/src/Rector/Property/InjectAnnotationClassRector.php index 015bfc264aaa..857a77e7a470 100644 --- a/src/Rector/Property/InjectAnnotationClassRector.php +++ b/src/Rector/Property/InjectAnnotationClassRector.php @@ -6,6 +6,7 @@ use DI\Annotation\Inject as PHPDIInject; use JMS\DiExtraBundle\Annotation\Inject as JMSInject; +use Nette\Utils\Strings; use PhpParser\Node; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\Property; @@ -14,6 +15,7 @@ use PHPStan\Type\ObjectType; use PHPStan\Type\Type; use Rector\Application\ErrorAndDiffCollector; +use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo; use Rector\BetterPhpDocParser\PhpDocNode\JMS\JMSInjectTagValueNode; use Rector\BetterPhpDocParser\PhpDocNode\PHPDI\PHPDIInjectTagValueNode; use Rector\Exception\NotImplementedException; @@ -124,7 +126,7 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - $phpDocInfo = $this->getPhpDocInfo($node); + $phpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO); if ($phpDocInfo === null) { return null; } @@ -139,7 +141,12 @@ public function refactor(Node $node): ?Node continue; } + if ($this->isParameterInject($injectTagValueNode)) { + return null; + } + $type = $this->resolveType($node, $injectTagValueNode); + return $this->refactorPropertyWithAnnotation($node, $type, $tagClass); } @@ -214,7 +221,10 @@ private function resolveJMSDIInjectType(Node $node, JMSInjectTagValueNode $jmsIn } } - $varType = $this->docBlockManipulator->getVarType($node); + /** @var PhpDocInfo $phpDocInfo */ + $phpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO); + + $varType = $phpDocInfo->getVarType(); if (! $varType instanceof MixedType) { return $varType; } @@ -233,4 +243,18 @@ private function resolveJMSDIInjectType(Node $node, JMSInjectTagValueNode $jmsIn return new MixedType(); } + + private function isParameterInject(PhpDocTagValueNode $phpDocTagValueNode): bool + { + if (! $phpDocTagValueNode instanceof JMSInjectTagValueNode) { + return false; + } + + $serviceName = $phpDocTagValueNode->getServiceName(); + if ($serviceName === null) { + return false; + } + + return (bool) Strings::match($serviceName, '#%(.*?)%#'); + } } diff --git a/tests/Rector/Property/InjectAnnotationClassRector/Fixture/fixture.php.inc b/tests/Rector/Property/InjectAnnotationClassRector/Fixture/fixture.php.inc index beebd68a1392..b38ad64045b8 100644 --- a/tests/Rector/Property/InjectAnnotationClassRector/Fixture/fixture.php.inc +++ b/tests/Rector/Property/InjectAnnotationClassRector/Fixture/fixture.php.inc @@ -15,11 +15,6 @@ class SomeController * @DI\Inject("translator", strict = false, required = false) */ private $translator; - - /** - * @DI\Inject("%kernel.cache_dir%") - */ - private $cacheDir; } ?> @@ -36,16 +31,10 @@ class SomeController * @var \Rector\Symfony\Tests\Rector\FrameworkBundle\AbstractToConstructorInjectionRectorSource\SomeEntityManager */ private $entityManager; - /** * @var \Rector\Symfony\Tests\Rector\FrameworkBundle\AbstractToConstructorInjectionRectorSource\SomeTranslatorInterface */ private $translator; - - /** - * @DI\Inject("%kernel.cache_dir%") - */ - private $cacheDir; public function __construct(\Rector\Symfony\Tests\Rector\FrameworkBundle\AbstractToConstructorInjectionRectorSource\SomeEntityManager $entityManager, \Rector\Symfony\Tests\Rector\FrameworkBundle\AbstractToConstructorInjectionRectorSource\SomeTranslatorInterface $translator) { $this->entityManager = $entityManager; diff --git a/tests/Rector/Property/InjectAnnotationClassRector/Fixture/skip_parameter_inject.php.inc b/tests/Rector/Property/InjectAnnotationClassRector/Fixture/skip_parameter_inject.php.inc new file mode 100644 index 000000000000..47fa6182de80 --- /dev/null +++ b/tests/Rector/Property/InjectAnnotationClassRector/Fixture/skip_parameter_inject.php.inc @@ -0,0 +1,13 @@ +