2019-02-28 21:50:53 +00:00
|
|
|
<?php declare(strict_types=1);
|
|
|
|
|
|
|
|
namespace Rector\BetterPhpDocParser\PhpDocParser;
|
|
|
|
|
2019-05-31 08:50:33 +00:00
|
|
|
use Nette\Utils\Strings;
|
2019-02-28 21:50:53 +00:00
|
|
|
use PHPStan\PhpDocParser\Ast\Node;
|
|
|
|
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
|
2019-08-26 09:37:02 +00:00
|
|
|
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
|
2019-02-28 21:50:53 +00:00
|
|
|
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode;
|
|
|
|
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 Rector\BetterPhpDocParser\Attributes\Ast\AttributeAwareNodeFactory;
|
|
|
|
use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\AttributeAwarePhpDocNode;
|
|
|
|
use Rector\BetterPhpDocParser\Attributes\Attribute\Attribute;
|
2019-09-26 14:26:44 +00:00
|
|
|
use Rector\BetterPhpDocParser\Contract\PhpDocNodeFactoryInterface;
|
|
|
|
use Rector\BetterPhpDocParser\Contract\PhpDocParserAwareInterface;
|
2019-06-04 09:12:24 +00:00
|
|
|
use Rector\BetterPhpDocParser\Printer\MultilineSpaceFormatPreserver;
|
2019-09-27 14:25:15 +00:00
|
|
|
use Rector\BetterPhpDocParser\ValueObject\StartEndValueObject;
|
2019-09-26 14:26:44 +00:00
|
|
|
use Rector\Configuration\CurrentNodeProvider;
|
2019-02-28 21:50:53 +00:00
|
|
|
use Symplify\PackageBuilder\Reflection\PrivatesAccessor;
|
|
|
|
use Symplify\PackageBuilder\Reflection\PrivatesCaller;
|
|
|
|
|
2019-09-26 14:26:44 +00:00
|
|
|
/**
|
2019-10-02 18:45:33 +00:00
|
|
|
* @see \Rector\BetterPhpDocParser\Tests\PhpDocParser\OrmTagParser\Class_\DoctrinePhpDocParserTest
|
2019-09-26 14:26:44 +00:00
|
|
|
* @see \Rector\BetterPhpDocParser\Tests\PhpDocParser\OrmTagParser\Property_\OrmTagParserPropertyTest
|
|
|
|
*/
|
2019-02-28 21:50:53 +00:00
|
|
|
final class BetterPhpDocParser extends PhpDocParser
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* @var bool
|
|
|
|
*/
|
|
|
|
private $isComment = false;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var PrivatesCaller
|
|
|
|
*/
|
|
|
|
private $privatesCaller;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var PrivatesAccessor
|
|
|
|
*/
|
|
|
|
private $privatesAccessor;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var AttributeAwareNodeFactory
|
|
|
|
*/
|
|
|
|
private $attributeAwareNodeFactory;
|
|
|
|
|
2019-06-04 09:12:24 +00:00
|
|
|
/**
|
|
|
|
* @var MultilineSpaceFormatPreserver
|
|
|
|
*/
|
|
|
|
private $multilineSpaceFormatPreserver;
|
|
|
|
|
2019-09-26 14:26:44 +00:00
|
|
|
/**
|
|
|
|
* @var PhpDocNodeFactoryInterface[]
|
|
|
|
*/
|
|
|
|
private $phpDocNodeFactories = [];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var CurrentNodeProvider
|
|
|
|
*/
|
|
|
|
private $currentNodeProvider;
|
|
|
|
|
2019-08-26 22:15:56 +00:00
|
|
|
/**
|
2019-09-26 14:26:44 +00:00
|
|
|
* @param PhpDocNodeFactoryInterface[] $phpDocNodeFactories
|
2019-08-26 22:15:56 +00:00
|
|
|
*/
|
2019-02-28 21:50:53 +00:00
|
|
|
public function __construct(
|
|
|
|
TypeParser $typeParser,
|
|
|
|
ConstExprParser $constExprParser,
|
2019-06-04 09:12:24 +00:00
|
|
|
AttributeAwareNodeFactory $attributeAwareNodeFactory,
|
2019-08-26 09:37:02 +00:00
|
|
|
MultilineSpaceFormatPreserver $multilineSpaceFormatPreserver,
|
2019-09-26 14:26:44 +00:00
|
|
|
CurrentNodeProvider $currentNodeProvider,
|
2019-09-27 14:25:15 +00:00
|
|
|
array $phpDocNodeFactories = []
|
2019-02-28 21:50:53 +00:00
|
|
|
) {
|
|
|
|
parent::__construct($typeParser, $constExprParser);
|
|
|
|
|
|
|
|
$this->privatesCaller = new PrivatesCaller();
|
|
|
|
$this->privatesAccessor = new PrivatesAccessor();
|
|
|
|
$this->attributeAwareNodeFactory = $attributeAwareNodeFactory;
|
2019-06-04 09:12:24 +00:00
|
|
|
$this->multilineSpaceFormatPreserver = $multilineSpaceFormatPreserver;
|
2019-09-26 14:26:44 +00:00
|
|
|
$this->phpDocNodeFactories = $phpDocNodeFactories;
|
|
|
|
$this->currentNodeProvider = $currentNodeProvider;
|
2019-02-28 21:50:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return AttributeAwarePhpDocNode|PhpDocNode
|
|
|
|
*/
|
|
|
|
public function parse(TokenIterator $tokenIterator): PhpDocNode
|
|
|
|
{
|
|
|
|
$this->isComment = false;
|
|
|
|
|
|
|
|
try {
|
|
|
|
$tokenIterator->consumeTokenType(Lexer::TOKEN_OPEN_PHPDOC);
|
|
|
|
} catch (ParserException $parserException) {
|
|
|
|
// probably "//" start
|
|
|
|
$this->isComment = true;
|
|
|
|
$tokenIterator->consumeTokenType(Lexer::TOKEN_OTHER);
|
|
|
|
}
|
|
|
|
|
|
|
|
$tokenIterator->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
|
|
|
|
|
|
|
$children = [];
|
|
|
|
if (! $tokenIterator->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) {
|
|
|
|
$children[] = $this->parseChildAndStoreItsPositions($tokenIterator);
|
|
|
|
while ($tokenIterator->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL) && ! $tokenIterator->isCurrentTokenType(
|
|
|
|
Lexer::TOKEN_CLOSE_PHPDOC
|
|
|
|
)) {
|
|
|
|
$children[] = $this->parseChildAndStoreItsPositions($tokenIterator);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (! $this->isComment) {
|
2019-09-26 14:26:44 +00:00
|
|
|
// might be in the middle of annotations
|
|
|
|
$tokenIterator->tryConsumeTokenType(Lexer::TOKEN_CLOSE_PHPDOC);
|
2019-02-28 21:50:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
$phpDocNode = new PhpDocNode(array_values($children));
|
|
|
|
|
|
|
|
return $this->attributeAwareNodeFactory->createFromNode($phpDocNode);
|
|
|
|
}
|
|
|
|
|
2019-08-26 09:37:02 +00:00
|
|
|
public function parseTag(TokenIterator $tokenIterator): PhpDocTagNode
|
|
|
|
{
|
2019-10-02 19:18:45 +00:00
|
|
|
$tag = $this->resolveTag($tokenIterator);
|
2019-08-26 09:37:02 +00:00
|
|
|
|
2019-09-27 14:25:15 +00:00
|
|
|
$phpDocTagValueNode = $this->parseTagValue($tokenIterator, $tag);
|
2019-08-26 09:37:02 +00:00
|
|
|
|
2019-09-27 14:25:15 +00:00
|
|
|
return new PhpDocTagNode($tag, $phpDocTagValueNode);
|
2019-08-26 09:37:02 +00:00
|
|
|
}
|
|
|
|
|
2019-02-28 21:50:53 +00:00
|
|
|
public function parseTagValue(TokenIterator $tokenIterator, string $tag): PhpDocTagValueNode
|
|
|
|
{
|
2019-07-15 19:01:00 +00:00
|
|
|
// needed for reference support in params, see https://github.com/rectorphp/rector/issues/1734
|
2019-09-26 14:26:44 +00:00
|
|
|
$tagValueNode = null;
|
|
|
|
foreach ($this->phpDocNodeFactories as $phpDocNodeFactory) {
|
|
|
|
// to prevent circular reference of this service
|
|
|
|
if ($phpDocNodeFactory instanceof PhpDocParserAwareInterface) {
|
|
|
|
$phpDocNodeFactory->setPhpDocParser($this);
|
|
|
|
}
|
|
|
|
|
2019-09-27 19:53:34 +00:00
|
|
|
// compare regardless sensitivity
|
2019-09-27 20:09:00 +00:00
|
|
|
if ($this->isTagMatchingPhpDocNodeFactory($tag, $phpDocNodeFactory)) {
|
2019-09-26 14:26:44 +00:00
|
|
|
$currentNode = $this->currentNodeProvider->getNode();
|
|
|
|
$tagValueNode = $phpDocNodeFactory->createFromNodeAndTokens($currentNode, $tokenIterator);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-27 14:25:15 +00:00
|
|
|
// fallback to original parser
|
2019-09-26 14:26:44 +00:00
|
|
|
if ($tagValueNode === null) {
|
2019-09-17 09:14:06 +00:00
|
|
|
$tagValueNode = parent::parseTagValue($tokenIterator, $tag);
|
2019-07-15 19:01:00 +00:00
|
|
|
}
|
|
|
|
|
2019-02-28 21:50:53 +00:00
|
|
|
return $this->attributeAwareNodeFactory->createFromNode($tagValueNode);
|
|
|
|
}
|
|
|
|
|
|
|
|
private function parseChildAndStoreItsPositions(TokenIterator $tokenIterator): Node
|
|
|
|
{
|
2019-09-27 14:25:15 +00:00
|
|
|
$tokenStart = $this->getTokenIteratorIndex($tokenIterator);
|
|
|
|
$phpDocNode = $this->privatesCaller->callPrivateMethod($this, 'parseChild', $tokenIterator);
|
|
|
|
$tokenEnd = $this->getTokenIteratorIndex($tokenIterator);
|
|
|
|
$startEndValueObject = new StartEndValueObject($tokenStart, $tokenEnd);
|
2019-02-28 21:50:53 +00:00
|
|
|
|
2019-09-27 14:25:15 +00:00
|
|
|
$attributeAwareNode = $this->attributeAwareNodeFactory->createFromNode($phpDocNode);
|
|
|
|
$attributeAwareNode->setAttribute(Attribute::PHP_DOC_NODE_INFO, $startEndValueObject);
|
2019-02-28 21:50:53 +00:00
|
|
|
|
2019-06-04 09:12:24 +00:00
|
|
|
$possibleMultilineText = $this->multilineSpaceFormatPreserver->resolveCurrentPhpDocNodeText(
|
|
|
|
$attributeAwareNode
|
|
|
|
);
|
2019-05-31 08:50:33 +00:00
|
|
|
|
2019-06-02 08:01:01 +00:00
|
|
|
if ($possibleMultilineText) {
|
|
|
|
// add original text, for keeping trimmed spaces
|
|
|
|
$originalContent = $this->getOriginalContentFromTokenIterator($tokenIterator);
|
2019-05-31 08:50:33 +00:00
|
|
|
|
|
|
|
// we try to match original content without trimmed spaces
|
2019-06-02 08:01:01 +00:00
|
|
|
$currentTextPattern = '#' . preg_quote($possibleMultilineText, '#') . '#s';
|
2019-07-29 08:12:47 +00:00
|
|
|
$currentTextPattern = Strings::replace($currentTextPattern, '#(\s)+#', '\s+');
|
2019-05-31 08:50:33 +00:00
|
|
|
$match = Strings::match($originalContent, $currentTextPattern);
|
|
|
|
|
|
|
|
if (isset($match[0])) {
|
|
|
|
$attributeAwareNode->setAttribute(Attribute::ORIGINAL_CONTENT, $match[0]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-28 21:50:53 +00:00
|
|
|
return $attributeAwareNode;
|
|
|
|
}
|
2019-05-31 08:50:33 +00:00
|
|
|
|
|
|
|
private function getOriginalContentFromTokenIterator(TokenIterator $tokenIterator): string
|
|
|
|
{
|
2019-09-17 11:45:12 +00:00
|
|
|
// @todo iterate through tokens...
|
2019-05-31 08:50:33 +00:00
|
|
|
$originalTokens = $this->privatesAccessor->getPrivateProperty($tokenIterator, 'tokens');
|
|
|
|
$originalContent = '';
|
|
|
|
|
|
|
|
foreach ($originalTokens as $originalToken) {
|
|
|
|
// skip opening
|
2019-09-26 14:26:44 +00:00
|
|
|
if ($originalToken[1] === Lexer::TOKEN_OPEN_PHPDOC) {
|
2019-05-31 08:50:33 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// skip closing
|
2019-09-26 14:26:44 +00:00
|
|
|
if ($originalToken[1] === Lexer::TOKEN_CLOSE_PHPDOC) {
|
2019-05-31 08:50:33 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-09-26 14:26:44 +00:00
|
|
|
if ($originalToken[1] === Lexer::TOKEN_PHPDOC_EOL) {
|
2019-05-31 08:50:33 +00:00
|
|
|
$originalToken[0] = PHP_EOL;
|
|
|
|
}
|
|
|
|
|
|
|
|
$originalContent .= $originalToken[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
return trim($originalContent);
|
|
|
|
}
|
2019-09-27 14:25:15 +00:00
|
|
|
|
|
|
|
private function getTokenIteratorIndex(TokenIterator $tokenIterator): int
|
|
|
|
{
|
|
|
|
return (int) $this->privatesAccessor->getPrivateProperty($tokenIterator, 'index');
|
|
|
|
}
|
2019-09-27 20:09:00 +00:00
|
|
|
|
|
|
|
private function isTagMatchingPhpDocNodeFactory(string $tag, PhpDocNodeFactoryInterface $phpDocNodeFactory): bool
|
|
|
|
{
|
|
|
|
if (Strings::lower($phpDocNodeFactory->getName()) === Strings::lower($tag)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (in_array($tag, ['@param'], true)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// possible short import
|
|
|
|
if (Strings::contains($phpDocNodeFactory->getName(), '\\')) {
|
|
|
|
$lastNamePart = Strings::after($phpDocNodeFactory->getName(), '\\', -1);
|
|
|
|
if (Strings::lower('@' . $lastNamePart) === Strings::lower($tag)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
2019-10-02 19:18:45 +00:00
|
|
|
|
|
|
|
private function resolveTag(TokenIterator $tokenIterator): string
|
|
|
|
{
|
|
|
|
$tag = $tokenIterator->currentTokenValue();
|
|
|
|
|
|
|
|
$tokenIterator->next();
|
|
|
|
|
|
|
|
// is not e.g "@var "
|
|
|
|
// join tags like "@ORM\Column" etc.
|
|
|
|
if ($tokenIterator->currentTokenType() !== Lexer::TOKEN_IDENTIFIER) {
|
|
|
|
return $tag;
|
|
|
|
}
|
|
|
|
$oldTag = $tag;
|
|
|
|
|
|
|
|
$tag .= $tokenIterator->currentTokenValue();
|
|
|
|
|
|
|
|
$isTagMatchedByFactories = $this->isTagMatchedByFactories($tag);
|
|
|
|
if (! $isTagMatchedByFactories) {
|
|
|
|
return $oldTag;
|
|
|
|
}
|
|
|
|
|
|
|
|
$tokenIterator->next();
|
|
|
|
|
|
|
|
return $tag;
|
|
|
|
}
|
|
|
|
|
|
|
|
private function isTagMatchedByFactories(string $tag): bool
|
|
|
|
{
|
|
|
|
foreach ($this->phpDocNodeFactories as $phpDocNodeFactory) {
|
|
|
|
if ($this->isTagMatchingPhpDocNodeFactory($tag, $phpDocNodeFactory)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
2019-02-28 21:50:53 +00:00
|
|
|
}
|