rector/packages/BetterPhpDocParser/PhpDocParser/BetterPhpDocParser.php

141 lines
6.9 KiB
PHP

<?php
declare (strict_types=1);
namespace Rector\BetterPhpDocParser\PhpDocParser;
use PhpParser\Node;
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\PhpDocParser;
use PHPStan\PhpDocParser\Parser\TokenIterator;
use PHPStan\PhpDocParser\Parser\TypeParser;
use Rector\BetterPhpDocParser\Contract\PhpDocParser\PhpDocNodeDecoratorInterface;
use Rector\BetterPhpDocParser\PhpDocInfo\TokenIteratorFactory;
use Rector\BetterPhpDocParser\ValueObject\Parser\BetterTokenIterator;
use Rector\BetterPhpDocParser\ValueObject\PhpDocAttributeKey;
use Rector\BetterPhpDocParser\ValueObject\StartAndEnd;
use Rector\Core\Configuration\CurrentNodeProvider;
use Rector\Core\Exception\ShouldNotHappenException;
use RectorPrefix20220606\Symplify\PackageBuilder\Reflection\PrivatesCaller;
/**
* @see \Rector\Tests\BetterPhpDocParser\PhpDocParser\TagValueNodeReprint\TagValueNodeReprintTest
*/
final class BetterPhpDocParser extends \PHPStan\PhpDocParser\Parser\PhpDocParser
{
/**
* @readonly
* @var \Rector\Core\Configuration\CurrentNodeProvider
*/
private $currentNodeProvider;
/**
* @readonly
* @var \Rector\BetterPhpDocParser\PhpDocInfo\TokenIteratorFactory
*/
private $tokenIteratorFactory;
/**
* @var PhpDocNodeDecoratorInterface[]
* @readonly
*/
private $phpDocNodeDecorators;
/**
* @readonly
* @var \Symplify\PackageBuilder\Reflection\PrivatesCaller
*/
private $privatesCaller;
/**
* @param PhpDocNodeDecoratorInterface[] $phpDocNodeDecorators
*/
public function __construct(\PHPStan\PhpDocParser\Parser\TypeParser $typeParser, \PHPStan\PhpDocParser\Parser\ConstExprParser $constExprParser, \Rector\Core\Configuration\CurrentNodeProvider $currentNodeProvider, \Rector\BetterPhpDocParser\PhpDocInfo\TokenIteratorFactory $tokenIteratorFactory, array $phpDocNodeDecorators, \RectorPrefix20220606\Symplify\PackageBuilder\Reflection\PrivatesCaller $privatesCaller = null)
{
$privatesCaller = $privatesCaller ?? new \RectorPrefix20220606\Symplify\PackageBuilder\Reflection\PrivatesCaller();
$this->currentNodeProvider = $currentNodeProvider;
$this->tokenIteratorFactory = $tokenIteratorFactory;
$this->phpDocNodeDecorators = $phpDocNodeDecorators;
$this->privatesCaller = $privatesCaller;
parent::__construct($typeParser, $constExprParser);
}
public function parse(\PHPStan\PhpDocParser\Parser\TokenIterator $tokenIterator) : \PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode
{
$tokenIterator->consumeTokenType(\PHPStan\PhpDocParser\Lexer\Lexer::TOKEN_OPEN_PHPDOC);
$tokenIterator->tryConsumeTokenType(\PHPStan\PhpDocParser\Lexer\Lexer::TOKEN_PHPDOC_EOL);
$children = [];
if (!$tokenIterator->isCurrentTokenType(\PHPStan\PhpDocParser\Lexer\Lexer::TOKEN_CLOSE_PHPDOC)) {
$children[] = $this->parseChildAndStoreItsPositions($tokenIterator);
while ($tokenIterator->tryConsumeTokenType(\PHPStan\PhpDocParser\Lexer\Lexer::TOKEN_PHPDOC_EOL) && !$tokenIterator->isCurrentTokenType(\PHPStan\PhpDocParser\Lexer\Lexer::TOKEN_CLOSE_PHPDOC)) {
$children[] = $this->parseChildAndStoreItsPositions($tokenIterator);
}
}
// might be in the middle of annotations
$tokenIterator->tryConsumeTokenType(\PHPStan\PhpDocParser\Lexer\Lexer::TOKEN_CLOSE_PHPDOC);
$phpDocNode = new \PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode($children);
// decorate FQN classes etc.
$node = $this->currentNodeProvider->getNode();
if (!$node instanceof \PhpParser\Node) {
throw new \Rector\Core\Exception\ShouldNotHappenException();
}
foreach ($this->phpDocNodeDecorators as $phpDocNodeDecorator) {
$phpDocNodeDecorator->decorate($phpDocNode, $node);
}
return $phpDocNode;
}
public function parseTag(\PHPStan\PhpDocParser\Parser\TokenIterator $tokenIterator) : \PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode
{
// replace generic nodes with DoctrineAnnotations
if (!$tokenIterator instanceof \Rector\BetterPhpDocParser\ValueObject\Parser\BetterTokenIterator) {
throw new \Rector\Core\Exception\ShouldNotHappenException();
}
$tag = $this->resolveTag($tokenIterator);
$phpDocTagValueNode = $this->parseTagValue($tokenIterator, $tag);
return new \PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode($tag, $phpDocTagValueNode);
}
/**
* @param BetterTokenIterator $tokenIterator
*/
public function parseTagValue(\PHPStan\PhpDocParser\Parser\TokenIterator $tokenIterator, string $tag) : \PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode
{
$startPosition = $tokenIterator->currentPosition();
$tagValueNode = parent::parseTagValue($tokenIterator, $tag);
$endPosition = $tokenIterator->currentPosition();
$startAndEnd = new \Rector\BetterPhpDocParser\ValueObject\StartAndEnd($startPosition, $endPosition);
$tagValueNode->setAttribute(\Rector\BetterPhpDocParser\ValueObject\PhpDocAttributeKey::START_AND_END, $startAndEnd);
return $tagValueNode;
}
/**
* @return PhpDocTextNode|PhpDocTagNode
*/
private function parseChildAndStoreItsPositions(\PHPStan\PhpDocParser\Parser\TokenIterator $tokenIterator) : \PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocChildNode
{
$betterTokenIterator = $this->tokenIteratorFactory->createFromTokenIterator($tokenIterator);
$startPosition = $betterTokenIterator->currentPosition();
/** @var PhpDocChildNode $phpDocNode */
$phpDocNode = $this->privatesCaller->callPrivateMethod($this, 'parseChild', [$betterTokenIterator]);
$endPosition = $betterTokenIterator->currentPosition();
$startAndEnd = new \Rector\BetterPhpDocParser\ValueObject\StartAndEnd($startPosition, $endPosition);
$phpDocNode->setAttribute(\Rector\BetterPhpDocParser\ValueObject\PhpDocAttributeKey::START_AND_END, $startAndEnd);
return $phpDocNode;
}
private function resolveTag(\Rector\BetterPhpDocParser\ValueObject\Parser\BetterTokenIterator $tokenIterator) : string
{
$tag = $tokenIterator->currentTokenValue();
$tokenIterator->next();
// there is a space → stop
if ($tokenIterator->isPrecededByHorizontalWhitespace()) {
return $tag;
}
// is not e.g "@var "
// join tags like "@ORM\Column" etc.
if (!$tokenIterator->isCurrentTokenType(\PHPStan\PhpDocParser\Lexer\Lexer::TOKEN_IDENTIFIER)) {
return $tag;
}
// @todo use joinUntil("(")?
$tag .= $tokenIterator->currentTokenValue();
$tokenIterator->next();
return $tag;
}
}