Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace Rector\Tests\DeadCode\Rector\Property\RemoveUselessVarTagRector\Fixture;

final class AfterInlineTag
{
/**
* @internal Use @see \Rector\Config\RectorConfig::paths() instead
* @var string
*/
public const string NAME = 'value';
}

?>
-----
<?php

namespace Rector\Tests\DeadCode\Rector\Property\RemoveUselessVarTagRector\Fixture;

final class AfterInlineTag
{
/**
* @internal Use @see \Rector\Config\RectorConfig::paths() instead
*/
public const string NAME = 'value';
}

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

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

use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Entity()
* @ORM\Table('user', indexes={
* @ORM\Index(name="name_index", columns={"name"}),
* @ORM\Index(name="surname_index", columns={"surname"}),
* })
*/
class SingleQuoteAlias
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,19 @@ private function transformGenericTagValueNodesToDoctrineAnnotationTagValueNodes(
PhpDocNode $phpDocNode,
Node $currentPhpNode
): void {
$incompleteAnnotationContent = null;

foreach ($phpDocNode->children as $key => $phpDocChildNode) {
// the next children are the rest of an incomplete annotation, e.g. its nested annotations; keep them
if ($incompleteAnnotationContent !== null) {
$incompleteAnnotationContent .= "\n" . $phpDocChildNode;
if ($this->isClosedContent($incompleteAnnotationContent)) {
$incompleteAnnotationContent = null;
}

continue;
}

// the @\FQN use case
if ($phpDocChildNode instanceof PhpDocTextNode) {
$this->processTextSpacelessInTextNode($phpDocNode, $phpDocChildNode, $currentPhpNode, $key);
Expand All @@ -215,6 +227,12 @@ private function transformGenericTagValueNodesToDoctrineAnnotationTagValueNodes(

// single quoted got invalid tag, keep process
if ($phpDocChildNode->value instanceof InvalidTagValueNode) {
// the value ends before a nested annotation on the next line
if (! $this->isClosedContent($phpDocChildNode->value->value)) {
$incompleteAnnotationContent = $phpDocChildNode->value->value;
continue;
}

$name = ltrim($phpDocChildNode->name, '@');

$values = $phpDocChildNode->value->value;
Expand Down Expand Up @@ -313,9 +331,6 @@ private function transformGenericTagValueNodesToDoctrineAnnotationTagValueNodes(
$this->attributeMirrorer->mirror($phpDocNode->children[$key], $spacelessPhpDocTagNode);
$phpDocNode->children[$key] = $spacelessPhpDocTagNode;

// require to reprint the generic
$phpDocNode->children[$key]->value->setAttribute(PhpDocAttributeKey::ORIG_NODE, null);

array_splice($phpDocNode->children, $key + 1, 0, $spacelessPhpDocTagNodes);
}
}
Expand Down
5 changes: 2 additions & 3 deletions src/BetterPhpDocParser/PhpDocNodeMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
namespace Rector\BetterPhpDocParser;

use PHPStan\PhpDocParser\Ast\NodeTraverser;
use PHPStan\PhpDocParser\Ast\NodeVisitor\CloningVisitor;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
use Rector\PhpDocParser\PhpDocParser\PhpDocNodeVisitor\CloningPhpDocNodeVisitor;
use Rector\PhpDocParser\PhpDocParser\PhpDocNodeVisitor\ParentConnectingPhpDocNodeVisitor;
use Webmozart\Assert\Assert;

Expand All @@ -18,11 +18,10 @@
private NodeTraverser $phpDocNodeTraverser;

public function __construct(
CloningPhpDocNodeVisitor $cloningPhpDocNodeVisitor,
ParentConnectingPhpDocNodeVisitor $parentConnectingPhpDocNodeVisitor,
) {
$this->phpDocNodeTraverser = new NodeTraverser([
$cloningPhpDocNodeVisitor,
new CloningVisitor(),
$parentConnectingPhpDocNodeVisitor,
]);
}
Expand Down
125 changes: 63 additions & 62 deletions src/BetterPhpDocParser/PhpDocParser/BetterPhpDocParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,52 +6,56 @@

use Nette\Utils\Strings;
use PhpParser\Node;
use PHPStan\PhpDocParser\Ast\Attribute;
use PHPStan\PhpDocParser\Ast\Node as PhpDocParserNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocChildNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTextNode;
use PHPStan\PhpDocParser\Lexer\Lexer;
use PHPStan\PhpDocParser\Parser\ConstExprParser;
use PHPStan\PhpDocParser\Parser\ParserException;
use PHPStan\PhpDocParser\Parser\PhpDocParser;
use PHPStan\PhpDocParser\Parser\TokenIterator;
use PHPStan\PhpDocParser\Parser\TypeParser;
use PHPStan\PhpDocParser\ParserConfig;
use Rector\BetterPhpDocParser\Contract\PhpDocParser\PhpDocNodeDecoratorInterface;
use Rector\BetterPhpDocParser\NodeDecorator\ArrayItemClassNameDecorator;
use Rector\BetterPhpDocParser\NodeDecorator\ConstExprClassNameDecorator;
use Rector\BetterPhpDocParser\NodeDecorator\DoctrineAnnotationDecorator;
use Rector\BetterPhpDocParser\NodeDecorator\PhpDocTagGenericUsesDecorator;
use Rector\BetterPhpDocParser\ValueObject\PhpDocAttributeKey;
use Rector\BetterPhpDocParser\ValueObject\StartAndEnd;
use Rector\Util\Reflection\PrivatesAccessor;

/**
* @see \Rector\Tests\BetterPhpDocParser\PhpDocParser\TagValueNodeReprint\TagValueNodeReprintTest
*/
final class BetterPhpDocParser extends PhpDocParser
final readonly class BetterPhpDocParser
{
/**
* @see https://regex101.com/r/JOKSmr/5
*/
private const string MULTI_NEW_LINES_REGEX = '#(?<new_line>\r\n|\n){2,}#';

/**
* @var int[]
*/
private const array NO_TEXT_TOKEN_TYPES = [
Lexer::TOKEN_OPEN_PHPDOC,
Lexer::TOKEN_CLOSE_PHPDOC,
Lexer::TOKEN_PHPDOC_EOL,
Lexer::TOKEN_HORIZONTAL_WS,
Lexer::TOKEN_END,
];

/**
* @var PhpDocNodeDecoratorInterface[]
*/
private readonly array $phpDocNodeDecorators;
private array $phpDocNodeDecorators;

public function __construct(
ParserConfig $parserConfig,
TypeParser $typeParser,
ConstExprParser $constExprParser,
private PhpDocParser $phpDocParser,
ConstExprClassNameDecorator $constExprClassNameDecorator,
DoctrineAnnotationDecorator $doctrineAnnotationDecorator,
ArrayItemClassNameDecorator $arrayItemClassNameDecorator,
PhpDocTagGenericUsesDecorator $phpDocTagGenericUsesDecorator,
private readonly PrivatesAccessor $privatesAccessor,
) {
// The decorator order below is significant; keep it as is. DoctrineAnnotationDecorator must
// run before ArrayItemClassNameDecorator, which resolves class names inside the array items the
Expand All @@ -63,84 +67,81 @@ public function __construct(
$arrayItemClassNameDecorator,
$phpDocTagGenericUsesDecorator,
];

parent::__construct(
// ParserConfig
$parserConfig,
// TypeParser
$typeParser,
// ConstExprParser
$constExprParser,
);
}

public function parseWithNode(TokenIterator $tokenIterator, Node $node): PhpDocNode
{
$tokenIterator->consumeTokenType(Lexer::TOKEN_OPEN_PHPDOC);
$tokenIterator->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
try {
$phpDocNode = $this->phpDocParser->parse($tokenIterator);
} catch (ParserException) {
// invalid Doctrine annotation arguments; keep the docblock as it is
return $this->createTextPhpDocNode($tokenIterator);
}

$children = [];
if (! $tokenIterator->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) {
$children[] = $this->parseChildAndStoreItsPositions($tokenIterator);
foreach ($phpDocNode->children as $phpDocChildNode) {
$this->setStartAndEnd($phpDocChildNode);

while ($tokenIterator->tryConsumeTokenType(
Lexer::TOKEN_PHPDOC_EOL
) && ! $tokenIterator->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) {
$children[] = $this->parseChildAndStoreItsPositions($tokenIterator);
if (! $phpDocChildNode instanceof PhpDocTagNode) {
continue;
}
}

// might be in the middle of annotations
$tokenIterator->tryConsumeTokenType(Lexer::TOKEN_CLOSE_PHPDOC);
$this->setStartAndEnd($phpDocChildNode->value);

if ($phpDocChildNode->value instanceof GenericTagValueNode) {
$phpDocChildNode->value->value = Strings::replace(
$phpDocChildNode->value->value,
self::MULTI_NEW_LINES_REGEX,
static fn (array $match): string => (string) $match['new_line']
);
}
}

$phpDocNode = new PhpDocNode($children);
foreach ($this->phpDocNodeDecorators as $phpDocNodeDecorator) {
$phpDocNodeDecorator->decorate($phpDocNode, $node);
}

return $phpDocNode;
}

public function parseTagValue(TokenIterator $tokenIterator, string $tag): PhpDocTagValueNode
/**
* The Doctrine annotation decorator moves these positions to the nodes it creates
*/
private function setStartAndEnd(PhpDocParserNode $phpDocParserNode): void
{
$startPosition = $tokenIterator->currentTokenIndex();
$phpDocTagValueNode = parent::parseTagValue($tokenIterator, $tag);

$endPosition = $tokenIterator->currentTokenIndex();

$startAndEnd = new StartAndEnd($startPosition, $endPosition);
$phpDocTagValueNode->setAttribute(PhpDocAttributeKey::START_AND_END, $startAndEnd);

if ($phpDocTagValueNode instanceof GenericTagValueNode) {
$phpDocTagValueNode->value = Strings::replace(
$phpDocTagValueNode->value,
self::MULTI_NEW_LINES_REGEX,
static fn (array $match): string => (string) $match['new_line']
);
$startIndex = $phpDocParserNode->getAttribute(Attribute::START_INDEX);
$endIndex = $phpDocParserNode->getAttribute(Attribute::END_INDEX);
if (! is_int($startIndex) || ! is_int($endIndex)) {
return;
}

return $phpDocTagValueNode;
// an empty node has no tokens, its end index is before its start index
$phpDocParserNode->setAttribute(
PhpDocAttributeKey::START_AND_END,
new StartAndEnd($startIndex, max($startIndex, $endIndex + 1))
);
}

/**
* @return PhpDocTextNode|PhpDocTagNode
* One text node with the content between "/**" and "*\/"
*/
private function parseChildAndStoreItsPositions(TokenIterator $tokenIterator): PhpDocChildNode
private function createTextPhpDocNode(TokenIterator $tokenIterator): PhpDocNode
{
$startPosition = $tokenIterator->currentTokenIndex();
$tokens = $tokenIterator->getTokens();
$startIndex = 0;
$endIndex = count($tokens) - 1;

try {
/** @var PhpDocTextNode|PhpDocTagNode $phpDocNode */
$phpDocNode = $this->privatesAccessor->callPrivateMethod($this, 'parseChild', [$tokenIterator]);
} catch (ParserException) {
$phpDocNode = new PhpDocTextNode('');
while ($startIndex < $endIndex && in_array($tokens[$startIndex][Lexer::TYPE_OFFSET], self::NO_TEXT_TOKEN_TYPES, true)) {
++$startIndex;
}

$endPosition = $tokenIterator->currentTokenIndex();
while ($endIndex > $startIndex && in_array($tokens[$endIndex][Lexer::TYPE_OFFSET], self::NO_TEXT_TOKEN_TYPES, true)) {
--$endIndex;
}

$startAndEnd = new StartAndEnd($startPosition, $endPosition);
$phpDocNode->setAttribute(PhpDocAttributeKey::START_AND_END, $startAndEnd);
$phpDocTextNode = new PhpDocTextNode($tokenIterator->getContentBetween($startIndex, $endIndex + 1));
$phpDocTextNode->setAttribute(Attribute::START_INDEX, $startIndex);
$phpDocTextNode->setAttribute(Attribute::END_INDEX, $endIndex);

return $phpDocNode;
return new PhpDocNode([$phpDocTextNode]);
}
}
Loading
Loading