Initial Commit
This commit is contained in:
188
dependencies/phpstan/phpdoc-parser/src/Parser/ConstExprParser.php
vendored
Normal file
188
dependencies/phpstan/phpdoc-parser/src/Parser/ConstExprParser.php
vendored
Normal file
@ -0,0 +1,188 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace WP_Ultimo\Dependencies\PHPStan\PhpDocParser\Parser;
|
||||
|
||||
use WP_Ultimo\Dependencies\PHPStan\PhpDocParser\Ast;
|
||||
use WP_Ultimo\Dependencies\PHPStan\PhpDocParser\Lexer\Lexer;
|
||||
use function str_replace;
|
||||
use function strtolower;
|
||||
use function substr;
|
||||
class ConstExprParser
|
||||
{
|
||||
/** @var bool */
|
||||
private $unescapeStrings;
|
||||
/** @var bool */
|
||||
private $quoteAwareConstExprString;
|
||||
/** @var bool */
|
||||
private $useLinesAttributes;
|
||||
/** @var bool */
|
||||
private $useIndexAttributes;
|
||||
/** @var bool */
|
||||
private $parseDoctrineStrings;
|
||||
/**
|
||||
* @param array{lines?: bool, indexes?: bool} $usedAttributes
|
||||
*/
|
||||
public function __construct(bool $unescapeStrings = \false, bool $quoteAwareConstExprString = \false, array $usedAttributes = [])
|
||||
{
|
||||
$this->unescapeStrings = $unescapeStrings;
|
||||
$this->quoteAwareConstExprString = $quoteAwareConstExprString;
|
||||
$this->useLinesAttributes = $usedAttributes['lines'] ?? \false;
|
||||
$this->useIndexAttributes = $usedAttributes['indexes'] ?? \false;
|
||||
$this->parseDoctrineStrings = \false;
|
||||
}
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function toDoctrine() : self
|
||||
{
|
||||
$self = new self($this->unescapeStrings, $this->quoteAwareConstExprString, ['lines' => $this->useLinesAttributes, 'indexes' => $this->useIndexAttributes]);
|
||||
$self->parseDoctrineStrings = \true;
|
||||
return $self;
|
||||
}
|
||||
public function parse(TokenIterator $tokens, bool $trimStrings = \false) : Ast\ConstExpr\ConstExprNode
|
||||
{
|
||||
$startLine = $tokens->currentTokenLine();
|
||||
$startIndex = $tokens->currentTokenIndex();
|
||||
if ($tokens->isCurrentTokenType(Lexer::TOKEN_FLOAT)) {
|
||||
$value = $tokens->currentTokenValue();
|
||||
$tokens->next();
|
||||
return $this->enrichWithAttributes($tokens, new Ast\ConstExpr\ConstExprFloatNode(str_replace('_', '', $value)), $startLine, $startIndex);
|
||||
}
|
||||
if ($tokens->isCurrentTokenType(Lexer::TOKEN_INTEGER)) {
|
||||
$value = $tokens->currentTokenValue();
|
||||
$tokens->next();
|
||||
return $this->enrichWithAttributes($tokens, new Ast\ConstExpr\ConstExprIntegerNode(str_replace('_', '', $value)), $startLine, $startIndex);
|
||||
}
|
||||
if ($this->parseDoctrineStrings && $tokens->isCurrentTokenType(Lexer::TOKEN_DOCTRINE_ANNOTATION_STRING)) {
|
||||
$value = $tokens->currentTokenValue();
|
||||
$tokens->next();
|
||||
return $this->enrichWithAttributes($tokens, new Ast\ConstExpr\DoctrineConstExprStringNode(Ast\ConstExpr\DoctrineConstExprStringNode::unescape($value)), $startLine, $startIndex);
|
||||
}
|
||||
if ($tokens->isCurrentTokenType(Lexer::TOKEN_SINGLE_QUOTED_STRING, Lexer::TOKEN_DOUBLE_QUOTED_STRING)) {
|
||||
if ($this->parseDoctrineStrings) {
|
||||
if ($tokens->isCurrentTokenType(Lexer::TOKEN_SINGLE_QUOTED_STRING)) {
|
||||
throw new ParserException($tokens->currentTokenValue(), $tokens->currentTokenType(), $tokens->currentTokenOffset(), Lexer::TOKEN_DOUBLE_QUOTED_STRING, null, $tokens->currentTokenLine());
|
||||
}
|
||||
$value = $tokens->currentTokenValue();
|
||||
$tokens->next();
|
||||
return $this->enrichWithAttributes($tokens, $this->parseDoctrineString($value, $tokens), $startLine, $startIndex);
|
||||
}
|
||||
$value = $tokens->currentTokenValue();
|
||||
$type = $tokens->currentTokenType();
|
||||
if ($trimStrings) {
|
||||
if ($this->unescapeStrings) {
|
||||
$value = StringUnescaper::unescapeString($value);
|
||||
} else {
|
||||
$value = substr($value, 1, -1);
|
||||
}
|
||||
}
|
||||
$tokens->next();
|
||||
if ($this->quoteAwareConstExprString) {
|
||||
return $this->enrichWithAttributes($tokens, new Ast\ConstExpr\QuoteAwareConstExprStringNode($value, $type === Lexer::TOKEN_SINGLE_QUOTED_STRING ? Ast\ConstExpr\QuoteAwareConstExprStringNode::SINGLE_QUOTED : Ast\ConstExpr\QuoteAwareConstExprStringNode::DOUBLE_QUOTED), $startLine, $startIndex);
|
||||
}
|
||||
return $this->enrichWithAttributes($tokens, new Ast\ConstExpr\ConstExprStringNode($value), $startLine, $startIndex);
|
||||
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_IDENTIFIER)) {
|
||||
$identifier = $tokens->currentTokenValue();
|
||||
$tokens->next();
|
||||
switch (strtolower($identifier)) {
|
||||
case 'true':
|
||||
return $this->enrichWithAttributes($tokens, new Ast\ConstExpr\ConstExprTrueNode(), $startLine, $startIndex);
|
||||
case 'false':
|
||||
return $this->enrichWithAttributes($tokens, new Ast\ConstExpr\ConstExprFalseNode(), $startLine, $startIndex);
|
||||
case 'null':
|
||||
return $this->enrichWithAttributes($tokens, new Ast\ConstExpr\ConstExprNullNode(), $startLine, $startIndex);
|
||||
case 'array':
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES);
|
||||
return $this->parseArray($tokens, Lexer::TOKEN_CLOSE_PARENTHESES, $startIndex);
|
||||
}
|
||||
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_DOUBLE_COLON)) {
|
||||
$classConstantName = '';
|
||||
$lastType = null;
|
||||
while (\true) {
|
||||
if ($lastType !== Lexer::TOKEN_IDENTIFIER && $tokens->currentTokenType() === Lexer::TOKEN_IDENTIFIER) {
|
||||
$classConstantName .= $tokens->currentTokenValue();
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
|
||||
$lastType = Lexer::TOKEN_IDENTIFIER;
|
||||
continue;
|
||||
}
|
||||
if ($lastType !== Lexer::TOKEN_WILDCARD && $tokens->tryConsumeTokenType(Lexer::TOKEN_WILDCARD)) {
|
||||
$classConstantName .= '*';
|
||||
$lastType = Lexer::TOKEN_WILDCARD;
|
||||
if ($tokens->getSkippedHorizontalWhiteSpaceIfAny() !== '') {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if ($lastType === null) {
|
||||
// trigger parse error if nothing valid was consumed
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_WILDCARD);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return $this->enrichWithAttributes($tokens, new Ast\ConstExpr\ConstFetchNode($identifier, $classConstantName), $startLine, $startIndex);
|
||||
}
|
||||
return $this->enrichWithAttributes($tokens, new Ast\ConstExpr\ConstFetchNode('', $identifier), $startLine, $startIndex);
|
||||
} elseif ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
|
||||
return $this->parseArray($tokens, Lexer::TOKEN_CLOSE_SQUARE_BRACKET, $startIndex);
|
||||
}
|
||||
throw new ParserException($tokens->currentTokenValue(), $tokens->currentTokenType(), $tokens->currentTokenOffset(), Lexer::TOKEN_IDENTIFIER, null, $tokens->currentTokenLine());
|
||||
}
|
||||
private function parseArray(TokenIterator $tokens, int $endToken, int $startIndex) : Ast\ConstExpr\ConstExprArrayNode
|
||||
{
|
||||
$items = [];
|
||||
$startLine = $tokens->currentTokenLine();
|
||||
if (!$tokens->tryConsumeTokenType($endToken)) {
|
||||
do {
|
||||
$items[] = $this->parseArrayItem($tokens);
|
||||
} while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA) && !$tokens->isCurrentTokenType($endToken));
|
||||
$tokens->consumeTokenType($endToken);
|
||||
}
|
||||
return $this->enrichWithAttributes($tokens, new Ast\ConstExpr\ConstExprArrayNode($items), $startLine, $startIndex);
|
||||
}
|
||||
/**
|
||||
* This method is supposed to be called with TokenIterator after reading TOKEN_DOUBLE_QUOTED_STRING and shifting
|
||||
* to the next token.
|
||||
*/
|
||||
public function parseDoctrineString(string $text, TokenIterator $tokens) : Ast\ConstExpr\DoctrineConstExprStringNode
|
||||
{
|
||||
// Because of how Lexer works, a valid Doctrine string
|
||||
// can consist of a sequence of TOKEN_DOUBLE_QUOTED_STRING and TOKEN_DOCTRINE_ANNOTATION_STRING
|
||||
while ($tokens->isCurrentTokenType(Lexer::TOKEN_DOUBLE_QUOTED_STRING, Lexer::TOKEN_DOCTRINE_ANNOTATION_STRING)) {
|
||||
$text .= $tokens->currentTokenValue();
|
||||
$tokens->next();
|
||||
}
|
||||
return new Ast\ConstExpr\DoctrineConstExprStringNode(Ast\ConstExpr\DoctrineConstExprStringNode::unescape($text));
|
||||
}
|
||||
private function parseArrayItem(TokenIterator $tokens) : Ast\ConstExpr\ConstExprArrayItemNode
|
||||
{
|
||||
$startLine = $tokens->currentTokenLine();
|
||||
$startIndex = $tokens->currentTokenIndex();
|
||||
$expr = $this->parse($tokens);
|
||||
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_DOUBLE_ARROW)) {
|
||||
$key = $expr;
|
||||
$value = $this->parse($tokens);
|
||||
} else {
|
||||
$key = null;
|
||||
$value = $expr;
|
||||
}
|
||||
return $this->enrichWithAttributes($tokens, new Ast\ConstExpr\ConstExprArrayItemNode($key, $value), $startLine, $startIndex);
|
||||
}
|
||||
/**
|
||||
* @template T of Ast\ConstExpr\ConstExprNode
|
||||
* @param T $node
|
||||
* @return T
|
||||
*/
|
||||
private function enrichWithAttributes(TokenIterator $tokens, Ast\ConstExpr\ConstExprNode $node, int $startLine, int $startIndex) : Ast\ConstExpr\ConstExprNode
|
||||
{
|
||||
if ($this->useLinesAttributes) {
|
||||
$node->setAttribute(Ast\Attribute::START_LINE, $startLine);
|
||||
$node->setAttribute(Ast\Attribute::END_LINE, $tokens->currentTokenLine());
|
||||
}
|
||||
if ($this->useIndexAttributes) {
|
||||
$node->setAttribute(Ast\Attribute::START_INDEX, $startIndex);
|
||||
$node->setAttribute(Ast\Attribute::END_INDEX, $tokens->endIndexOfLastRelevantToken());
|
||||
}
|
||||
return $node;
|
||||
}
|
||||
}
|
68
dependencies/phpstan/phpdoc-parser/src/Parser/ParserException.php
vendored
Normal file
68
dependencies/phpstan/phpdoc-parser/src/Parser/ParserException.php
vendored
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace WP_Ultimo\Dependencies\PHPStan\PhpDocParser\Parser;
|
||||
|
||||
use Exception;
|
||||
use WP_Ultimo\Dependencies\PHPStan\PhpDocParser\Lexer\Lexer;
|
||||
use function assert;
|
||||
use function json_encode;
|
||||
use function sprintf;
|
||||
use const JSON_INVALID_UTF8_SUBSTITUTE;
|
||||
use const JSON_UNESCAPED_SLASHES;
|
||||
use const JSON_UNESCAPED_UNICODE;
|
||||
class ParserException extends Exception
|
||||
{
|
||||
/** @var string */
|
||||
private $currentTokenValue;
|
||||
/** @var int */
|
||||
private $currentTokenType;
|
||||
/** @var int */
|
||||
private $currentOffset;
|
||||
/** @var int */
|
||||
private $expectedTokenType;
|
||||
/** @var string|null */
|
||||
private $expectedTokenValue;
|
||||
/** @var int|null */
|
||||
private $currentTokenLine;
|
||||
public function __construct(string $currentTokenValue, int $currentTokenType, int $currentOffset, int $expectedTokenType, ?string $expectedTokenValue = null, ?int $currentTokenLine = null)
|
||||
{
|
||||
$this->currentTokenValue = $currentTokenValue;
|
||||
$this->currentTokenType = $currentTokenType;
|
||||
$this->currentOffset = $currentOffset;
|
||||
$this->expectedTokenType = $expectedTokenType;
|
||||
$this->expectedTokenValue = $expectedTokenValue;
|
||||
$this->currentTokenLine = $currentTokenLine;
|
||||
parent::__construct(sprintf('Unexpected token %s, expected %s%s at offset %d%s', $this->formatValue($currentTokenValue), Lexer::TOKEN_LABELS[$expectedTokenType], $expectedTokenValue !== null ? sprintf(' (%s)', $this->formatValue($expectedTokenValue)) : '', $currentOffset, $currentTokenLine === null ? '' : sprintf(' on line %d', $currentTokenLine)));
|
||||
}
|
||||
public function getCurrentTokenValue() : string
|
||||
{
|
||||
return $this->currentTokenValue;
|
||||
}
|
||||
public function getCurrentTokenType() : int
|
||||
{
|
||||
return $this->currentTokenType;
|
||||
}
|
||||
public function getCurrentOffset() : int
|
||||
{
|
||||
return $this->currentOffset;
|
||||
}
|
||||
public function getExpectedTokenType() : int
|
||||
{
|
||||
return $this->expectedTokenType;
|
||||
}
|
||||
public function getExpectedTokenValue() : ?string
|
||||
{
|
||||
return $this->expectedTokenValue;
|
||||
}
|
||||
public function getCurrentTokenLine() : ?int
|
||||
{
|
||||
return $this->currentTokenLine;
|
||||
}
|
||||
private function formatValue(string $value) : string
|
||||
{
|
||||
$json = json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_SUBSTITUTE);
|
||||
assert($json !== \false);
|
||||
return $json;
|
||||
}
|
||||
}
|
854
dependencies/phpstan/phpdoc-parser/src/Parser/PhpDocParser.php
vendored
Normal file
854
dependencies/phpstan/phpdoc-parser/src/Parser/PhpDocParser.php
vendored
Normal file
@ -0,0 +1,854 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace WP_Ultimo\Dependencies\PHPStan\PhpDocParser\Parser;
|
||||
|
||||
use LogicException;
|
||||
use WP_Ultimo\Dependencies\PHPStan\PhpDocParser\Ast;
|
||||
use WP_Ultimo\Dependencies\PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode;
|
||||
use WP_Ultimo\Dependencies\PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode;
|
||||
use WP_Ultimo\Dependencies\PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode;
|
||||
use WP_Ultimo\Dependencies\PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine;
|
||||
use WP_Ultimo\Dependencies\PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
|
||||
use WP_Ultimo\Dependencies\PHPStan\PhpDocParser\Lexer\Lexer;
|
||||
use WP_Ultimo\Dependencies\PHPStan\ShouldNotHappenException;
|
||||
use function array_key_exists;
|
||||
use function array_values;
|
||||
use function count;
|
||||
use function rtrim;
|
||||
use function str_replace;
|
||||
use function trim;
|
||||
/**
|
||||
* @phpstan-import-type ValueType from Doctrine\DoctrineArgument as DoctrineValueType
|
||||
*/
|
||||
class PhpDocParser
|
||||
{
|
||||
private const DISALLOWED_DESCRIPTION_START_TOKENS = [Lexer::TOKEN_UNION, Lexer::TOKEN_INTERSECTION];
|
||||
/** @var TypeParser */
|
||||
private $typeParser;
|
||||
/** @var ConstExprParser */
|
||||
private $constantExprParser;
|
||||
/** @var ConstExprParser */
|
||||
private $doctrineConstantExprParser;
|
||||
/** @var bool */
|
||||
private $requireWhitespaceBeforeDescription;
|
||||
/** @var bool */
|
||||
private $preserveTypeAliasesWithInvalidTypes;
|
||||
/** @var bool */
|
||||
private $parseDoctrineAnnotations;
|
||||
/** @var bool */
|
||||
private $useLinesAttributes;
|
||||
/** @var bool */
|
||||
private $useIndexAttributes;
|
||||
/** @var bool */
|
||||
private $textBetweenTagsBelongsToDescription;
|
||||
/**
|
||||
* @param array{lines?: bool, indexes?: bool} $usedAttributes
|
||||
*/
|
||||
public function __construct(TypeParser $typeParser, ConstExprParser $constantExprParser, bool $requireWhitespaceBeforeDescription = \false, bool $preserveTypeAliasesWithInvalidTypes = \false, array $usedAttributes = [], bool $parseDoctrineAnnotations = \false, bool $textBetweenTagsBelongsToDescription = \false)
|
||||
{
|
||||
$this->typeParser = $typeParser;
|
||||
$this->constantExprParser = $constantExprParser;
|
||||
$this->doctrineConstantExprParser = $constantExprParser->toDoctrine();
|
||||
$this->requireWhitespaceBeforeDescription = $requireWhitespaceBeforeDescription;
|
||||
$this->preserveTypeAliasesWithInvalidTypes = $preserveTypeAliasesWithInvalidTypes;
|
||||
$this->parseDoctrineAnnotations = $parseDoctrineAnnotations;
|
||||
$this->useLinesAttributes = $usedAttributes['lines'] ?? \false;
|
||||
$this->useIndexAttributes = $usedAttributes['indexes'] ?? \false;
|
||||
$this->textBetweenTagsBelongsToDescription = $textBetweenTagsBelongsToDescription;
|
||||
}
|
||||
public function parse(TokenIterator $tokens) : Ast\PhpDoc\PhpDocNode
|
||||
{
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_OPEN_PHPDOC);
|
||||
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
||||
$children = [];
|
||||
if ($this->parseDoctrineAnnotations) {
|
||||
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) {
|
||||
$lastChild = $this->parseChild($tokens);
|
||||
$children[] = $lastChild;
|
||||
while (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) {
|
||||
if ($lastChild instanceof Ast\PhpDoc\PhpDocTagNode && ($lastChild->value instanceof Doctrine\DoctrineTagValueNode || $lastChild->value instanceof Ast\PhpDoc\GenericTagValueNode)) {
|
||||
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
||||
if ($tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) {
|
||||
break;
|
||||
}
|
||||
$lastChild = $this->parseChild($tokens);
|
||||
$children[] = $lastChild;
|
||||
continue;
|
||||
}
|
||||
if (!$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL)) {
|
||||
break;
|
||||
}
|
||||
if ($tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) {
|
||||
break;
|
||||
}
|
||||
$lastChild = $this->parseChild($tokens);
|
||||
$children[] = $lastChild;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) {
|
||||
$children[] = $this->parseChild($tokens);
|
||||
while ($tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL) && !$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) {
|
||||
$children[] = $this->parseChild($tokens);
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PHPDOC);
|
||||
} catch (ParserException $e) {
|
||||
$name = '';
|
||||
$startLine = $tokens->currentTokenLine();
|
||||
$startIndex = $tokens->currentTokenIndex();
|
||||
if (count($children) > 0) {
|
||||
$lastChild = $children[count($children) - 1];
|
||||
if ($lastChild instanceof Ast\PhpDoc\PhpDocTagNode) {
|
||||
$name = $lastChild->name;
|
||||
$startLine = $tokens->currentTokenLine();
|
||||
$startIndex = $tokens->currentTokenIndex();
|
||||
}
|
||||
}
|
||||
$tag = new Ast\PhpDoc\PhpDocTagNode($name, $this->enrichWithAttributes($tokens, new Ast\PhpDoc\InvalidTagValueNode($e->getMessage(), $e), $startLine, $startIndex));
|
||||
$tokens->forwardToTheEnd();
|
||||
return $this->enrichWithAttributes($tokens, new Ast\PhpDoc\PhpDocNode([$this->enrichWithAttributes($tokens, $tag, $startLine, $startIndex)]), 1, 0);
|
||||
}
|
||||
return $this->enrichWithAttributes($tokens, new Ast\PhpDoc\PhpDocNode(array_values($children)), 1, 0);
|
||||
}
|
||||
/** @phpstan-impure */
|
||||
private function parseChild(TokenIterator $tokens) : Ast\PhpDoc\PhpDocChildNode
|
||||
{
|
||||
if ($tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_TAG)) {
|
||||
$startLine = $tokens->currentTokenLine();
|
||||
$startIndex = $tokens->currentTokenIndex();
|
||||
return $this->enrichWithAttributes($tokens, $this->parseTag($tokens), $startLine, $startIndex);
|
||||
}
|
||||
if ($tokens->isCurrentTokenType(Lexer::TOKEN_DOCTRINE_TAG)) {
|
||||
$startLine = $tokens->currentTokenLine();
|
||||
$startIndex = $tokens->currentTokenIndex();
|
||||
$tag = $tokens->currentTokenValue();
|
||||
$tokens->next();
|
||||
$tagStartLine = $tokens->currentTokenLine();
|
||||
$tagStartIndex = $tokens->currentTokenIndex();
|
||||
return $this->enrichWithAttributes($tokens, new Ast\PhpDoc\PhpDocTagNode($tag, $this->enrichWithAttributes($tokens, $this->parseDoctrineTagValue($tokens, $tag), $tagStartLine, $tagStartIndex)), $startLine, $startIndex);
|
||||
}
|
||||
$startLine = $tokens->currentTokenLine();
|
||||
$startIndex = $tokens->currentTokenIndex();
|
||||
$text = $this->parseText($tokens);
|
||||
return $this->enrichWithAttributes($tokens, $text, $startLine, $startIndex);
|
||||
}
|
||||
/**
|
||||
* @template T of Ast\Node
|
||||
* @param T $tag
|
||||
* @return T
|
||||
*/
|
||||
private function enrichWithAttributes(TokenIterator $tokens, Ast\Node $tag, int $startLine, int $startIndex) : Ast\Node
|
||||
{
|
||||
if ($this->useLinesAttributes) {
|
||||
$tag->setAttribute(Ast\Attribute::START_LINE, $startLine);
|
||||
$tag->setAttribute(Ast\Attribute::END_LINE, $tokens->currentTokenLine());
|
||||
}
|
||||
if ($this->useIndexAttributes) {
|
||||
$tag->setAttribute(Ast\Attribute::START_INDEX, $startIndex);
|
||||
$tag->setAttribute(Ast\Attribute::END_INDEX, $tokens->endIndexOfLastRelevantToken());
|
||||
}
|
||||
return $tag;
|
||||
}
|
||||
private function parseText(TokenIterator $tokens) : Ast\PhpDoc\PhpDocTextNode
|
||||
{
|
||||
$text = '';
|
||||
$endTokens = [Lexer::TOKEN_PHPDOC_EOL, Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END];
|
||||
if ($this->textBetweenTagsBelongsToDescription) {
|
||||
$endTokens = [Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END];
|
||||
}
|
||||
$savepoint = \false;
|
||||
// if the next token is EOL, everything below is skipped and empty string is returned
|
||||
while ($this->textBetweenTagsBelongsToDescription || !$tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL)) {
|
||||
$tmpText = $tokens->getSkippedHorizontalWhiteSpaceIfAny() . $tokens->joinUntil(Lexer::TOKEN_PHPDOC_EOL, ...$endTokens);
|
||||
$text .= $tmpText;
|
||||
// stop if we're not at EOL - meaning it's the end of PHPDoc
|
||||
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL)) {
|
||||
break;
|
||||
}
|
||||
if ($this->textBetweenTagsBelongsToDescription) {
|
||||
if (!$savepoint) {
|
||||
$tokens->pushSavePoint();
|
||||
$savepoint = \true;
|
||||
} elseif ($tmpText !== '') {
|
||||
$tokens->dropSavePoint();
|
||||
$tokens->pushSavePoint();
|
||||
}
|
||||
}
|
||||
$tokens->pushSavePoint();
|
||||
$tokens->next();
|
||||
// if we're at EOL, check what's next
|
||||
// if next is a PHPDoc tag, EOL, or end of PHPDoc, stop
|
||||
if ($tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_TAG, Lexer::TOKEN_DOCTRINE_TAG, ...$endTokens)) {
|
||||
$tokens->rollback();
|
||||
break;
|
||||
}
|
||||
// otherwise if the next is text, continue building the description string
|
||||
$tokens->dropSavePoint();
|
||||
$text .= $tokens->getDetectedNewline() ?? "\n";
|
||||
}
|
||||
if ($savepoint) {
|
||||
$tokens->rollback();
|
||||
$text = rtrim($text, $tokens->getDetectedNewline() ?? "\n");
|
||||
}
|
||||
return new Ast\PhpDoc\PhpDocTextNode(trim($text, " \t"));
|
||||
}
|
||||
private function parseOptionalDescriptionAfterDoctrineTag(TokenIterator $tokens) : string
|
||||
{
|
||||
$text = '';
|
||||
$endTokens = [Lexer::TOKEN_PHPDOC_EOL, Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END];
|
||||
if ($this->textBetweenTagsBelongsToDescription) {
|
||||
$endTokens = [Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END];
|
||||
}
|
||||
$savepoint = \false;
|
||||
// if the next token is EOL, everything below is skipped and empty string is returned
|
||||
while ($this->textBetweenTagsBelongsToDescription || !$tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL)) {
|
||||
$tmpText = $tokens->getSkippedHorizontalWhiteSpaceIfAny() . $tokens->joinUntil(Lexer::TOKEN_PHPDOC_TAG, Lexer::TOKEN_DOCTRINE_TAG, Lexer::TOKEN_PHPDOC_EOL, ...$endTokens);
|
||||
$text .= $tmpText;
|
||||
// stop if we're not at EOL - meaning it's the end of PHPDoc
|
||||
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL)) {
|
||||
if (!$tokens->isPrecededByHorizontalWhitespace()) {
|
||||
return trim($text . $this->parseText($tokens)->text, " \t");
|
||||
}
|
||||
if ($tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_TAG)) {
|
||||
$tokens->pushSavePoint();
|
||||
$child = $this->parseChild($tokens);
|
||||
if ($child instanceof Ast\PhpDoc\PhpDocTagNode) {
|
||||
if ($child->value instanceof Ast\PhpDoc\GenericTagValueNode || $child->value instanceof Doctrine\DoctrineTagValueNode) {
|
||||
$tokens->rollback();
|
||||
break;
|
||||
}
|
||||
if ($child->value instanceof Ast\PhpDoc\InvalidTagValueNode) {
|
||||
$tokens->rollback();
|
||||
$tokens->pushSavePoint();
|
||||
$tokens->next();
|
||||
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) {
|
||||
$tokens->rollback();
|
||||
break;
|
||||
}
|
||||
$tokens->rollback();
|
||||
return trim($text . $this->parseText($tokens)->text, " \t");
|
||||
}
|
||||
}
|
||||
$tokens->rollback();
|
||||
return trim($text . $this->parseText($tokens)->text, " \t");
|
||||
}
|
||||
break;
|
||||
}
|
||||
if ($this->textBetweenTagsBelongsToDescription) {
|
||||
if (!$savepoint) {
|
||||
$tokens->pushSavePoint();
|
||||
$savepoint = \true;
|
||||
} elseif ($tmpText !== '') {
|
||||
$tokens->dropSavePoint();
|
||||
$tokens->pushSavePoint();
|
||||
}
|
||||
}
|
||||
$tokens->pushSavePoint();
|
||||
$tokens->next();
|
||||
// if we're at EOL, check what's next
|
||||
// if next is a PHPDoc tag, EOL, or end of PHPDoc, stop
|
||||
if ($tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_TAG, Lexer::TOKEN_DOCTRINE_TAG, ...$endTokens)) {
|
||||
$tokens->rollback();
|
||||
break;
|
||||
}
|
||||
// otherwise if the next is text, continue building the description string
|
||||
$tokens->dropSavePoint();
|
||||
$text .= $tokens->getDetectedNewline() ?? "\n";
|
||||
}
|
||||
if ($savepoint) {
|
||||
$tokens->rollback();
|
||||
$text = rtrim($text, $tokens->getDetectedNewline() ?? "\n");
|
||||
}
|
||||
return trim($text, " \t");
|
||||
}
|
||||
public function parseTag(TokenIterator $tokens) : Ast\PhpDoc\PhpDocTagNode
|
||||
{
|
||||
$tag = $tokens->currentTokenValue();
|
||||
$tokens->next();
|
||||
$value = $this->parseTagValue($tokens, $tag);
|
||||
return new Ast\PhpDoc\PhpDocTagNode($tag, $value);
|
||||
}
|
||||
public function parseTagValue(TokenIterator $tokens, string $tag) : Ast\PhpDoc\PhpDocTagValueNode
|
||||
{
|
||||
$startLine = $tokens->currentTokenLine();
|
||||
$startIndex = $tokens->currentTokenIndex();
|
||||
try {
|
||||
$tokens->pushSavePoint();
|
||||
switch ($tag) {
|
||||
case '@param':
|
||||
case '@phpstan-param':
|
||||
case '@psalm-param':
|
||||
$tagValue = $this->parseParamTagValue($tokens);
|
||||
break;
|
||||
case '@var':
|
||||
case '@phpstan-var':
|
||||
case '@psalm-var':
|
||||
$tagValue = $this->parseVarTagValue($tokens);
|
||||
break;
|
||||
case '@return':
|
||||
case '@phpstan-return':
|
||||
case '@psalm-return':
|
||||
$tagValue = $this->parseReturnTagValue($tokens);
|
||||
break;
|
||||
case '@throws':
|
||||
case '@phpstan-throws':
|
||||
$tagValue = $this->parseThrowsTagValue($tokens);
|
||||
break;
|
||||
case '@mixin':
|
||||
$tagValue = $this->parseMixinTagValue($tokens);
|
||||
break;
|
||||
case '@deprecated':
|
||||
$tagValue = $this->parseDeprecatedTagValue($tokens);
|
||||
break;
|
||||
case '@property':
|
||||
case '@property-read':
|
||||
case '@property-write':
|
||||
case '@phpstan-property':
|
||||
case '@phpstan-property-read':
|
||||
case '@phpstan-property-write':
|
||||
case '@psalm-property':
|
||||
case '@psalm-property-read':
|
||||
case '@psalm-property-write':
|
||||
$tagValue = $this->parsePropertyTagValue($tokens);
|
||||
break;
|
||||
case '@method':
|
||||
case '@phpstan-method':
|
||||
case '@psalm-method':
|
||||
$tagValue = $this->parseMethodTagValue($tokens);
|
||||
break;
|
||||
case '@template':
|
||||
case '@phpstan-template':
|
||||
case '@psalm-template':
|
||||
case '@template-covariant':
|
||||
case '@phpstan-template-covariant':
|
||||
case '@psalm-template-covariant':
|
||||
case '@template-contravariant':
|
||||
case '@phpstan-template-contravariant':
|
||||
case '@psalm-template-contravariant':
|
||||
$tagValue = $this->parseTemplateTagValue($tokens, \true);
|
||||
break;
|
||||
case '@extends':
|
||||
case '@phpstan-extends':
|
||||
case '@template-extends':
|
||||
$tagValue = $this->parseExtendsTagValue('@extends', $tokens);
|
||||
break;
|
||||
case '@implements':
|
||||
case '@phpstan-implements':
|
||||
case '@template-implements':
|
||||
$tagValue = $this->parseExtendsTagValue('@implements', $tokens);
|
||||
break;
|
||||
case '@use':
|
||||
case '@phpstan-use':
|
||||
case '@template-use':
|
||||
$tagValue = $this->parseExtendsTagValue('@use', $tokens);
|
||||
break;
|
||||
case '@phpstan-type':
|
||||
case '@psalm-type':
|
||||
$tagValue = $this->parseTypeAliasTagValue($tokens);
|
||||
break;
|
||||
case '@phpstan-import-type':
|
||||
case '@psalm-import-type':
|
||||
$tagValue = $this->parseTypeAliasImportTagValue($tokens);
|
||||
break;
|
||||
case '@phpstan-assert':
|
||||
case '@phpstan-assert-if-true':
|
||||
case '@phpstan-assert-if-false':
|
||||
case '@psalm-assert':
|
||||
case '@psalm-assert-if-true':
|
||||
case '@psalm-assert-if-false':
|
||||
$tagValue = $this->parseAssertTagValue($tokens);
|
||||
break;
|
||||
case '@phpstan-this-out':
|
||||
case '@phpstan-self-out':
|
||||
case '@psalm-this-out':
|
||||
case '@psalm-self-out':
|
||||
$tagValue = $this->parseSelfOutTagValue($tokens);
|
||||
break;
|
||||
case '@param-out':
|
||||
case '@phpstan-param-out':
|
||||
case '@psalm-param-out':
|
||||
$tagValue = $this->parseParamOutTagValue($tokens);
|
||||
break;
|
||||
default:
|
||||
if ($this->parseDoctrineAnnotations) {
|
||||
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) {
|
||||
$tagValue = $this->parseDoctrineTagValue($tokens, $tag);
|
||||
} else {
|
||||
$tagValue = new Ast\PhpDoc\GenericTagValueNode($this->parseOptionalDescriptionAfterDoctrineTag($tokens));
|
||||
}
|
||||
break;
|
||||
}
|
||||
$tagValue = new Ast\PhpDoc\GenericTagValueNode($this->parseOptionalDescription($tokens));
|
||||
break;
|
||||
}
|
||||
$tokens->dropSavePoint();
|
||||
} catch (ParserException $e) {
|
||||
$tokens->rollback();
|
||||
$tagValue = new Ast\PhpDoc\InvalidTagValueNode($this->parseOptionalDescription($tokens), $e);
|
||||
}
|
||||
return $this->enrichWithAttributes($tokens, $tagValue, $startLine, $startIndex);
|
||||
}
|
||||
private function parseDoctrineTagValue(TokenIterator $tokens, string $tag) : Ast\PhpDoc\PhpDocTagValueNode
|
||||
{
|
||||
$startLine = $tokens->currentTokenLine();
|
||||
$startIndex = $tokens->currentTokenIndex();
|
||||
return new Doctrine\DoctrineTagValueNode($this->enrichWithAttributes($tokens, new Doctrine\DoctrineAnnotation($tag, $this->parseDoctrineArguments($tokens, \false)), $startLine, $startIndex), $this->parseOptionalDescriptionAfterDoctrineTag($tokens));
|
||||
}
|
||||
/**
|
||||
* @return list<Doctrine\DoctrineArgument>
|
||||
*/
|
||||
private function parseDoctrineArguments(TokenIterator $tokens, bool $deep) : array
|
||||
{
|
||||
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) {
|
||||
return [];
|
||||
}
|
||||
if (!$deep) {
|
||||
$tokens->addEndOfLineToSkippedTokens();
|
||||
}
|
||||
$arguments = [];
|
||||
try {
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES);
|
||||
do {
|
||||
if ($tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PARENTHESES)) {
|
||||
break;
|
||||
}
|
||||
$arguments[] = $this->parseDoctrineArgument($tokens);
|
||||
} while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA));
|
||||
} finally {
|
||||
if (!$deep) {
|
||||
$tokens->removeEndOfLineFromSkippedTokens();
|
||||
}
|
||||
}
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES);
|
||||
return $arguments;
|
||||
}
|
||||
private function parseDoctrineArgument(TokenIterator $tokens) : Doctrine\DoctrineArgument
|
||||
{
|
||||
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_IDENTIFIER)) {
|
||||
$startLine = $tokens->currentTokenLine();
|
||||
$startIndex = $tokens->currentTokenIndex();
|
||||
return $this->enrichWithAttributes($tokens, new Doctrine\DoctrineArgument(null, $this->parseDoctrineArgumentValue($tokens)), $startLine, $startIndex);
|
||||
}
|
||||
$startLine = $tokens->currentTokenLine();
|
||||
$startIndex = $tokens->currentTokenIndex();
|
||||
try {
|
||||
$tokens->pushSavePoint();
|
||||
$currentValue = $tokens->currentTokenValue();
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
|
||||
$key = $this->enrichWithAttributes($tokens, new IdentifierTypeNode($currentValue), $startLine, $startIndex);
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_EQUAL);
|
||||
$value = $this->parseDoctrineArgumentValue($tokens);
|
||||
$tokens->dropSavePoint();
|
||||
return $this->enrichWithAttributes($tokens, new Doctrine\DoctrineArgument($key, $value), $startLine, $startIndex);
|
||||
} catch (ParserException $e) {
|
||||
$tokens->rollback();
|
||||
return $this->enrichWithAttributes($tokens, new Doctrine\DoctrineArgument(null, $this->parseDoctrineArgumentValue($tokens)), $startLine, $startIndex);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @return DoctrineValueType
|
||||
*/
|
||||
private function parseDoctrineArgumentValue(TokenIterator $tokens)
|
||||
{
|
||||
$startLine = $tokens->currentTokenLine();
|
||||
$startIndex = $tokens->currentTokenIndex();
|
||||
if ($tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_TAG, Lexer::TOKEN_DOCTRINE_TAG)) {
|
||||
$name = $tokens->currentTokenValue();
|
||||
$tokens->next();
|
||||
return $this->enrichWithAttributes($tokens, new Doctrine\DoctrineAnnotation($name, $this->parseDoctrineArguments($tokens, \true)), $startLine, $startIndex);
|
||||
}
|
||||
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_CURLY_BRACKET)) {
|
||||
$items = [];
|
||||
do {
|
||||
if ($tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET)) {
|
||||
break;
|
||||
}
|
||||
$items[] = $this->parseDoctrineArrayItem($tokens);
|
||||
} while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA));
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET);
|
||||
return $this->enrichWithAttributes($tokens, new Doctrine\DoctrineArray($items), $startLine, $startIndex);
|
||||
}
|
||||
$currentTokenValue = $tokens->currentTokenValue();
|
||||
$tokens->pushSavePoint();
|
||||
// because of ConstFetchNode
|
||||
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_IDENTIFIER)) {
|
||||
$identifier = $this->enrichWithAttributes($tokens, new Ast\Type\IdentifierTypeNode($currentTokenValue), $startLine, $startIndex);
|
||||
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_DOUBLE_COLON)) {
|
||||
$tokens->dropSavePoint();
|
||||
return $identifier;
|
||||
}
|
||||
$tokens->rollback();
|
||||
// because of ConstFetchNode
|
||||
} else {
|
||||
$tokens->dropSavePoint();
|
||||
// because of ConstFetchNode
|
||||
}
|
||||
$currentTokenValue = $tokens->currentTokenValue();
|
||||
$currentTokenType = $tokens->currentTokenType();
|
||||
$currentTokenOffset = $tokens->currentTokenOffset();
|
||||
$currentTokenLine = $tokens->currentTokenLine();
|
||||
try {
|
||||
$constExpr = $this->doctrineConstantExprParser->parse($tokens, \true);
|
||||
if ($constExpr instanceof Ast\ConstExpr\ConstExprArrayNode) {
|
||||
throw new ParserException($currentTokenValue, $currentTokenType, $currentTokenOffset, Lexer::TOKEN_IDENTIFIER, null, $currentTokenLine);
|
||||
}
|
||||
return $constExpr;
|
||||
} catch (LogicException $e) {
|
||||
throw new ParserException($currentTokenValue, $currentTokenType, $currentTokenOffset, Lexer::TOKEN_IDENTIFIER, null, $currentTokenLine);
|
||||
}
|
||||
}
|
||||
private function parseDoctrineArrayItem(TokenIterator $tokens) : Doctrine\DoctrineArrayItem
|
||||
{
|
||||
$startLine = $tokens->currentTokenLine();
|
||||
$startIndex = $tokens->currentTokenIndex();
|
||||
try {
|
||||
$tokens->pushSavePoint();
|
||||
$key = $this->parseDoctrineArrayKey($tokens);
|
||||
if (!$tokens->tryConsumeTokenType(Lexer::TOKEN_EQUAL)) {
|
||||
if (!$tokens->tryConsumeTokenType(Lexer::TOKEN_COLON)) {
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_EQUAL);
|
||||
// will throw exception
|
||||
}
|
||||
}
|
||||
$value = $this->parseDoctrineArgumentValue($tokens);
|
||||
$tokens->dropSavePoint();
|
||||
return $this->enrichWithAttributes($tokens, new Doctrine\DoctrineArrayItem($key, $value), $startLine, $startIndex);
|
||||
} catch (ParserException $e) {
|
||||
$tokens->rollback();
|
||||
return $this->enrichWithAttributes($tokens, new Doctrine\DoctrineArrayItem(null, $this->parseDoctrineArgumentValue($tokens)), $startLine, $startIndex);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @return ConstExprIntegerNode|ConstExprStringNode|IdentifierTypeNode|ConstFetchNode
|
||||
*/
|
||||
private function parseDoctrineArrayKey(TokenIterator $tokens)
|
||||
{
|
||||
$startLine = $tokens->currentTokenLine();
|
||||
$startIndex = $tokens->currentTokenIndex();
|
||||
if ($tokens->isCurrentTokenType(Lexer::TOKEN_INTEGER)) {
|
||||
$key = new Ast\ConstExpr\ConstExprIntegerNode(str_replace('_', '', $tokens->currentTokenValue()));
|
||||
$tokens->next();
|
||||
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_DOCTRINE_ANNOTATION_STRING)) {
|
||||
$key = new Ast\ConstExpr\DoctrineConstExprStringNode(Ast\ConstExpr\DoctrineConstExprStringNode::unescape($tokens->currentTokenValue()));
|
||||
$tokens->next();
|
||||
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_DOUBLE_QUOTED_STRING)) {
|
||||
$value = $tokens->currentTokenValue();
|
||||
$tokens->next();
|
||||
$key = $this->doctrineConstantExprParser->parseDoctrineString($value, $tokens);
|
||||
} else {
|
||||
$currentTokenValue = $tokens->currentTokenValue();
|
||||
$tokens->pushSavePoint();
|
||||
// because of ConstFetchNode
|
||||
if (!$tokens->tryConsumeTokenType(Lexer::TOKEN_IDENTIFIER)) {
|
||||
$tokens->dropSavePoint();
|
||||
throw new ParserException($tokens->currentTokenValue(), $tokens->currentTokenType(), $tokens->currentTokenOffset(), Lexer::TOKEN_IDENTIFIER, null, $tokens->currentTokenLine());
|
||||
}
|
||||
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_DOUBLE_COLON)) {
|
||||
$tokens->dropSavePoint();
|
||||
return $this->enrichWithAttributes($tokens, new IdentifierTypeNode($currentTokenValue), $startLine, $startIndex);
|
||||
}
|
||||
$tokens->rollback();
|
||||
$constExpr = $this->doctrineConstantExprParser->parse($tokens, \true);
|
||||
if (!$constExpr instanceof Ast\ConstExpr\ConstFetchNode) {
|
||||
throw new ParserException($tokens->currentTokenValue(), $tokens->currentTokenType(), $tokens->currentTokenOffset(), Lexer::TOKEN_IDENTIFIER, null, $tokens->currentTokenLine());
|
||||
}
|
||||
return $constExpr;
|
||||
}
|
||||
return $this->enrichWithAttributes($tokens, $key, $startLine, $startIndex);
|
||||
}
|
||||
/**
|
||||
* @return Ast\PhpDoc\ParamTagValueNode|Ast\PhpDoc\TypelessParamTagValueNode
|
||||
*/
|
||||
private function parseParamTagValue(TokenIterator $tokens) : Ast\PhpDoc\PhpDocTagValueNode
|
||||
{
|
||||
if ($tokens->isCurrentTokenType(Lexer::TOKEN_REFERENCE, Lexer::TOKEN_VARIADIC, Lexer::TOKEN_VARIABLE)) {
|
||||
$type = null;
|
||||
} else {
|
||||
$type = $this->typeParser->parse($tokens);
|
||||
}
|
||||
$isReference = $tokens->tryConsumeTokenType(Lexer::TOKEN_REFERENCE);
|
||||
$isVariadic = $tokens->tryConsumeTokenType(Lexer::TOKEN_VARIADIC);
|
||||
$parameterName = $this->parseRequiredVariableName($tokens);
|
||||
$description = $this->parseOptionalDescription($tokens);
|
||||
if ($type !== null) {
|
||||
return new Ast\PhpDoc\ParamTagValueNode($type, $isVariadic, $parameterName, $description, $isReference);
|
||||
}
|
||||
return new Ast\PhpDoc\TypelessParamTagValueNode($isVariadic, $parameterName, $description, $isReference);
|
||||
}
|
||||
private function parseVarTagValue(TokenIterator $tokens) : Ast\PhpDoc\VarTagValueNode
|
||||
{
|
||||
$type = $this->typeParser->parse($tokens);
|
||||
$variableName = $this->parseOptionalVariableName($tokens);
|
||||
$description = $this->parseOptionalDescription($tokens, $variableName === '');
|
||||
return new Ast\PhpDoc\VarTagValueNode($type, $variableName, $description);
|
||||
}
|
||||
private function parseReturnTagValue(TokenIterator $tokens) : Ast\PhpDoc\ReturnTagValueNode
|
||||
{
|
||||
$type = $this->typeParser->parse($tokens);
|
||||
$description = $this->parseOptionalDescription($tokens, \true);
|
||||
return new Ast\PhpDoc\ReturnTagValueNode($type, $description);
|
||||
}
|
||||
private function parseThrowsTagValue(TokenIterator $tokens) : Ast\PhpDoc\ThrowsTagValueNode
|
||||
{
|
||||
$type = $this->typeParser->parse($tokens);
|
||||
$description = $this->parseOptionalDescription($tokens, \true);
|
||||
return new Ast\PhpDoc\ThrowsTagValueNode($type, $description);
|
||||
}
|
||||
private function parseMixinTagValue(TokenIterator $tokens) : Ast\PhpDoc\MixinTagValueNode
|
||||
{
|
||||
$type = $this->typeParser->parse($tokens);
|
||||
$description = $this->parseOptionalDescription($tokens, \true);
|
||||
return new Ast\PhpDoc\MixinTagValueNode($type, $description);
|
||||
}
|
||||
private function parseDeprecatedTagValue(TokenIterator $tokens) : Ast\PhpDoc\DeprecatedTagValueNode
|
||||
{
|
||||
$description = $this->parseOptionalDescription($tokens);
|
||||
return new Ast\PhpDoc\DeprecatedTagValueNode($description);
|
||||
}
|
||||
private function parsePropertyTagValue(TokenIterator $tokens) : Ast\PhpDoc\PropertyTagValueNode
|
||||
{
|
||||
$type = $this->typeParser->parse($tokens);
|
||||
$parameterName = $this->parseRequiredVariableName($tokens);
|
||||
$description = $this->parseOptionalDescription($tokens);
|
||||
return new Ast\PhpDoc\PropertyTagValueNode($type, $parameterName, $description);
|
||||
}
|
||||
private function parseMethodTagValue(TokenIterator $tokens) : Ast\PhpDoc\MethodTagValueNode
|
||||
{
|
||||
$isStatic = $tokens->tryConsumeTokenValue('static');
|
||||
$startLine = $tokens->currentTokenLine();
|
||||
$startIndex = $tokens->currentTokenIndex();
|
||||
$returnTypeOrMethodName = $this->typeParser->parse($tokens);
|
||||
if ($tokens->isCurrentTokenType(Lexer::TOKEN_IDENTIFIER)) {
|
||||
$returnType = $returnTypeOrMethodName;
|
||||
$methodName = $tokens->currentTokenValue();
|
||||
$tokens->next();
|
||||
} elseif ($returnTypeOrMethodName instanceof Ast\Type\IdentifierTypeNode) {
|
||||
$returnType = $isStatic ? $this->typeParser->enrichWithAttributes($tokens, new Ast\Type\IdentifierTypeNode('static'), $startLine, $startIndex) : null;
|
||||
$methodName = $returnTypeOrMethodName->name;
|
||||
$isStatic = \false;
|
||||
} else {
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
|
||||
// will throw exception
|
||||
exit;
|
||||
}
|
||||
$templateTypes = [];
|
||||
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET)) {
|
||||
do {
|
||||
$startLine = $tokens->currentTokenLine();
|
||||
$startIndex = $tokens->currentTokenIndex();
|
||||
$templateTypes[] = $this->enrichWithAttributes($tokens, $this->parseTemplateTagValue($tokens, \false), $startLine, $startIndex);
|
||||
} while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA));
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET);
|
||||
}
|
||||
$parameters = [];
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES);
|
||||
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PARENTHESES)) {
|
||||
$parameters[] = $this->parseMethodTagValueParameter($tokens);
|
||||
while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)) {
|
||||
$parameters[] = $this->parseMethodTagValueParameter($tokens);
|
||||
}
|
||||
}
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES);
|
||||
$description = $this->parseOptionalDescription($tokens);
|
||||
return new Ast\PhpDoc\MethodTagValueNode($isStatic, $returnType, $methodName, $parameters, $description, $templateTypes);
|
||||
}
|
||||
private function parseMethodTagValueParameter(TokenIterator $tokens) : Ast\PhpDoc\MethodTagValueParameterNode
|
||||
{
|
||||
$startLine = $tokens->currentTokenLine();
|
||||
$startIndex = $tokens->currentTokenIndex();
|
||||
switch ($tokens->currentTokenType()) {
|
||||
case Lexer::TOKEN_IDENTIFIER:
|
||||
case Lexer::TOKEN_OPEN_PARENTHESES:
|
||||
case Lexer::TOKEN_NULLABLE:
|
||||
$parameterType = $this->typeParser->parse($tokens);
|
||||
break;
|
||||
default:
|
||||
$parameterType = null;
|
||||
}
|
||||
$isReference = $tokens->tryConsumeTokenType(Lexer::TOKEN_REFERENCE);
|
||||
$isVariadic = $tokens->tryConsumeTokenType(Lexer::TOKEN_VARIADIC);
|
||||
$parameterName = $tokens->currentTokenValue();
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_VARIABLE);
|
||||
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_EQUAL)) {
|
||||
$defaultValue = $this->constantExprParser->parse($tokens);
|
||||
} else {
|
||||
$defaultValue = null;
|
||||
}
|
||||
return $this->enrichWithAttributes($tokens, new Ast\PhpDoc\MethodTagValueParameterNode($parameterType, $isReference, $isVariadic, $parameterName, $defaultValue), $startLine, $startIndex);
|
||||
}
|
||||
private function parseTemplateTagValue(TokenIterator $tokens, bool $parseDescription) : Ast\PhpDoc\TemplateTagValueNode
|
||||
{
|
||||
$name = $tokens->currentTokenValue();
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
|
||||
if ($tokens->tryConsumeTokenValue('of') || $tokens->tryConsumeTokenValue('as')) {
|
||||
$bound = $this->typeParser->parse($tokens);
|
||||
} else {
|
||||
$bound = null;
|
||||
}
|
||||
if ($tokens->tryConsumeTokenValue('=')) {
|
||||
$default = $this->typeParser->parse($tokens);
|
||||
} else {
|
||||
$default = null;
|
||||
}
|
||||
if ($parseDescription) {
|
||||
$description = $this->parseOptionalDescription($tokens);
|
||||
} else {
|
||||
$description = '';
|
||||
}
|
||||
return new Ast\PhpDoc\TemplateTagValueNode($name, $bound, $description, $default);
|
||||
}
|
||||
private function parseExtendsTagValue(string $tagName, TokenIterator $tokens) : Ast\PhpDoc\PhpDocTagValueNode
|
||||
{
|
||||
$startLine = $tokens->currentTokenLine();
|
||||
$startIndex = $tokens->currentTokenIndex();
|
||||
$baseType = new IdentifierTypeNode($tokens->currentTokenValue());
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
|
||||
$type = $this->typeParser->parseGeneric($tokens, $this->typeParser->enrichWithAttributes($tokens, $baseType, $startLine, $startIndex));
|
||||
$description = $this->parseOptionalDescription($tokens);
|
||||
switch ($tagName) {
|
||||
case '@extends':
|
||||
return new Ast\PhpDoc\ExtendsTagValueNode($type, $description);
|
||||
case '@implements':
|
||||
return new Ast\PhpDoc\ImplementsTagValueNode($type, $description);
|
||||
case '@use':
|
||||
return new Ast\PhpDoc\UsesTagValueNode($type, $description);
|
||||
}
|
||||
throw new ShouldNotHappenException();
|
||||
}
|
||||
private function parseTypeAliasTagValue(TokenIterator $tokens) : Ast\PhpDoc\TypeAliasTagValueNode
|
||||
{
|
||||
$alias = $tokens->currentTokenValue();
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
|
||||
// support psalm-type syntax
|
||||
$tokens->tryConsumeTokenType(Lexer::TOKEN_EQUAL);
|
||||
if ($this->preserveTypeAliasesWithInvalidTypes) {
|
||||
$startLine = $tokens->currentTokenLine();
|
||||
$startIndex = $tokens->currentTokenIndex();
|
||||
try {
|
||||
$type = $this->typeParser->parse($tokens);
|
||||
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) {
|
||||
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL)) {
|
||||
throw new ParserException($tokens->currentTokenValue(), $tokens->currentTokenType(), $tokens->currentTokenOffset(), Lexer::TOKEN_PHPDOC_EOL, null, $tokens->currentTokenLine());
|
||||
}
|
||||
}
|
||||
return new Ast\PhpDoc\TypeAliasTagValueNode($alias, $type);
|
||||
} catch (ParserException $e) {
|
||||
$this->parseOptionalDescription($tokens);
|
||||
return new Ast\PhpDoc\TypeAliasTagValueNode($alias, $this->enrichWithAttributes($tokens, new Ast\Type\InvalidTypeNode($e), $startLine, $startIndex));
|
||||
}
|
||||
}
|
||||
$type = $this->typeParser->parse($tokens);
|
||||
return new Ast\PhpDoc\TypeAliasTagValueNode($alias, $type);
|
||||
}
|
||||
private function parseTypeAliasImportTagValue(TokenIterator $tokens) : Ast\PhpDoc\TypeAliasImportTagValueNode
|
||||
{
|
||||
$importedAlias = $tokens->currentTokenValue();
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
|
||||
$tokens->consumeTokenValue(Lexer::TOKEN_IDENTIFIER, 'from');
|
||||
$identifierStartLine = $tokens->currentTokenLine();
|
||||
$identifierStartIndex = $tokens->currentTokenIndex();
|
||||
$importedFrom = $tokens->currentTokenValue();
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
|
||||
$importedFromType = $this->enrichWithAttributes($tokens, new IdentifierTypeNode($importedFrom), $identifierStartLine, $identifierStartIndex);
|
||||
$importedAs = null;
|
||||
if ($tokens->tryConsumeTokenValue('as')) {
|
||||
$importedAs = $tokens->currentTokenValue();
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
|
||||
}
|
||||
return new Ast\PhpDoc\TypeAliasImportTagValueNode($importedAlias, $importedFromType, $importedAs);
|
||||
}
|
||||
/**
|
||||
* @return Ast\PhpDoc\AssertTagValueNode|Ast\PhpDoc\AssertTagPropertyValueNode|Ast\PhpDoc\AssertTagMethodValueNode
|
||||
*/
|
||||
private function parseAssertTagValue(TokenIterator $tokens) : Ast\PhpDoc\PhpDocTagValueNode
|
||||
{
|
||||
$isNegated = $tokens->tryConsumeTokenType(Lexer::TOKEN_NEGATED);
|
||||
$isEquality = $tokens->tryConsumeTokenType(Lexer::TOKEN_EQUAL);
|
||||
$type = $this->typeParser->parse($tokens);
|
||||
$parameter = $this->parseAssertParameter($tokens);
|
||||
$description = $this->parseOptionalDescription($tokens);
|
||||
if (array_key_exists('method', $parameter)) {
|
||||
return new Ast\PhpDoc\AssertTagMethodValueNode($type, $parameter['parameter'], $parameter['method'], $isNegated, $description, $isEquality);
|
||||
} elseif (array_key_exists('property', $parameter)) {
|
||||
return new Ast\PhpDoc\AssertTagPropertyValueNode($type, $parameter['parameter'], $parameter['property'], $isNegated, $description, $isEquality);
|
||||
}
|
||||
return new Ast\PhpDoc\AssertTagValueNode($type, $parameter['parameter'], $isNegated, $description, $isEquality);
|
||||
}
|
||||
/**
|
||||
* @return array{parameter: string}|array{parameter: string, property: string}|array{parameter: string, method: string}
|
||||
*/
|
||||
private function parseAssertParameter(TokenIterator $tokens) : array
|
||||
{
|
||||
if ($tokens->isCurrentTokenType(Lexer::TOKEN_THIS_VARIABLE)) {
|
||||
$parameter = '$this';
|
||||
$tokens->next();
|
||||
} else {
|
||||
$parameter = $tokens->currentTokenValue();
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_VARIABLE);
|
||||
}
|
||||
if ($tokens->isCurrentTokenType(Lexer::TOKEN_ARROW)) {
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_ARROW);
|
||||
$propertyOrMethod = $tokens->currentTokenValue();
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
|
||||
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) {
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES);
|
||||
return ['parameter' => $parameter, 'method' => $propertyOrMethod];
|
||||
}
|
||||
return ['parameter' => $parameter, 'property' => $propertyOrMethod];
|
||||
}
|
||||
return ['parameter' => $parameter];
|
||||
}
|
||||
private function parseSelfOutTagValue(TokenIterator $tokens) : Ast\PhpDoc\SelfOutTagValueNode
|
||||
{
|
||||
$type = $this->typeParser->parse($tokens);
|
||||
$description = $this->parseOptionalDescription($tokens);
|
||||
return new Ast\PhpDoc\SelfOutTagValueNode($type, $description);
|
||||
}
|
||||
private function parseParamOutTagValue(TokenIterator $tokens) : Ast\PhpDoc\ParamOutTagValueNode
|
||||
{
|
||||
$type = $this->typeParser->parse($tokens);
|
||||
$parameterName = $this->parseRequiredVariableName($tokens);
|
||||
$description = $this->parseOptionalDescription($tokens);
|
||||
return new Ast\PhpDoc\ParamOutTagValueNode($type, $parameterName, $description);
|
||||
}
|
||||
private function parseOptionalVariableName(TokenIterator $tokens) : string
|
||||
{
|
||||
if ($tokens->isCurrentTokenType(Lexer::TOKEN_VARIABLE)) {
|
||||
$parameterName = $tokens->currentTokenValue();
|
||||
$tokens->next();
|
||||
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_THIS_VARIABLE)) {
|
||||
$parameterName = '$this';
|
||||
$tokens->next();
|
||||
} else {
|
||||
$parameterName = '';
|
||||
}
|
||||
return $parameterName;
|
||||
}
|
||||
private function parseRequiredVariableName(TokenIterator $tokens) : string
|
||||
{
|
||||
$parameterName = $tokens->currentTokenValue();
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_VARIABLE);
|
||||
return $parameterName;
|
||||
}
|
||||
private function parseOptionalDescription(TokenIterator $tokens, bool $limitStartToken = \false) : string
|
||||
{
|
||||
if ($limitStartToken) {
|
||||
foreach (self::DISALLOWED_DESCRIPTION_START_TOKENS as $disallowedStartToken) {
|
||||
if (!$tokens->isCurrentTokenType($disallowedStartToken)) {
|
||||
continue;
|
||||
}
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_OTHER);
|
||||
// will throw exception
|
||||
}
|
||||
if ($this->requireWhitespaceBeforeDescription && !$tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL, Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END) && !$tokens->isPrecededByHorizontalWhitespace()) {
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_HORIZONTAL_WS);
|
||||
// will throw exception
|
||||
}
|
||||
}
|
||||
return $this->parseText($tokens)->text;
|
||||
}
|
||||
}
|
63
dependencies/phpstan/phpdoc-parser/src/Parser/StringUnescaper.php
vendored
Normal file
63
dependencies/phpstan/phpdoc-parser/src/Parser/StringUnescaper.php
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace WP_Ultimo\Dependencies\PHPStan\PhpDocParser\Parser;
|
||||
|
||||
use function chr;
|
||||
use function hexdec;
|
||||
use function octdec;
|
||||
use function preg_replace_callback;
|
||||
use function str_replace;
|
||||
use function substr;
|
||||
class StringUnescaper
|
||||
{
|
||||
private const REPLACEMENTS = ['\\' => '\\', 'n' => "\n", 'r' => "\r", 't' => "\t", 'f' => "\f", 'v' => "\v", 'e' => "\x1b"];
|
||||
public static function unescapeString(string $string) : string
|
||||
{
|
||||
$quote = $string[0];
|
||||
if ($quote === '\'') {
|
||||
return str_replace(['\\\\', '\\\''], ['\\', '\''], substr($string, 1, -1));
|
||||
}
|
||||
return self::parseEscapeSequences(substr($string, 1, -1), '"');
|
||||
}
|
||||
/**
|
||||
* Implementation based on https://github.com/nikic/PHP-Parser/blob/b0edd4c41111042d43bb45c6c657b2e0db367d9e/lib/PhpParser/Node/Scalar/String_.php#L90-L130
|
||||
*/
|
||||
private static function parseEscapeSequences(string $str, string $quote) : string
|
||||
{
|
||||
$str = str_replace('\\' . $quote, $quote, $str);
|
||||
return preg_replace_callback('~\\\\([\\\\nrtfve]|[xX][0-9a-fA-F]{1,2}|[0-7]{1,3}|u\\{([0-9a-fA-F]+)\\})~', static function ($matches) {
|
||||
$str = $matches[1];
|
||||
if (isset(self::REPLACEMENTS[$str])) {
|
||||
return self::REPLACEMENTS[$str];
|
||||
}
|
||||
if ($str[0] === 'x' || $str[0] === 'X') {
|
||||
return chr((int) hexdec(substr($str, 1)));
|
||||
}
|
||||
if ($str[0] === 'u') {
|
||||
return self::codePointToUtf8((int) hexdec($matches[2]));
|
||||
}
|
||||
return chr((int) octdec($str));
|
||||
}, $str);
|
||||
}
|
||||
/**
|
||||
* Implementation based on https://github.com/nikic/PHP-Parser/blob/b0edd4c41111042d43bb45c6c657b2e0db367d9e/lib/PhpParser/Node/Scalar/String_.php#L132-L154
|
||||
*/
|
||||
private static function codePointToUtf8(int $num) : string
|
||||
{
|
||||
if ($num <= 0x7f) {
|
||||
return chr($num);
|
||||
}
|
||||
if ($num <= 0x7ff) {
|
||||
return chr(($num >> 6) + 0xc0) . chr(($num & 0x3f) + 0x80);
|
||||
}
|
||||
if ($num <= 0xffff) {
|
||||
return chr(($num >> 12) + 0xe0) . chr(($num >> 6 & 0x3f) + 0x80) . chr(($num & 0x3f) + 0x80);
|
||||
}
|
||||
if ($num <= 0x1fffff) {
|
||||
return chr(($num >> 18) + 0xf0) . chr(($num >> 12 & 0x3f) + 0x80) . chr(($num >> 6 & 0x3f) + 0x80) . chr(($num & 0x3f) + 0x80);
|
||||
}
|
||||
// Invalid UTF-8 codepoint escape sequence: Codepoint too large
|
||||
return "<EFBFBD>";
|
||||
}
|
||||
}
|
287
dependencies/phpstan/phpdoc-parser/src/Parser/TokenIterator.php
vendored
Normal file
287
dependencies/phpstan/phpdoc-parser/src/Parser/TokenIterator.php
vendored
Normal file
@ -0,0 +1,287 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace WP_Ultimo\Dependencies\PHPStan\PhpDocParser\Parser;
|
||||
|
||||
use LogicException;
|
||||
use WP_Ultimo\Dependencies\PHPStan\PhpDocParser\Lexer\Lexer;
|
||||
use function array_pop;
|
||||
use function assert;
|
||||
use function count;
|
||||
use function in_array;
|
||||
use function strlen;
|
||||
use function substr;
|
||||
class TokenIterator
|
||||
{
|
||||
/** @var list<array{string, int, int}> */
|
||||
private $tokens;
|
||||
/** @var int */
|
||||
private $index;
|
||||
/** @var int[] */
|
||||
private $savePoints = [];
|
||||
/** @var list<int> */
|
||||
private $skippedTokenTypes = [Lexer::TOKEN_HORIZONTAL_WS];
|
||||
/** @var string|null */
|
||||
private $newline = null;
|
||||
/**
|
||||
* @param list<array{string, int, int}> $tokens
|
||||
*/
|
||||
public function __construct(array $tokens, int $index = 0)
|
||||
{
|
||||
$this->tokens = $tokens;
|
||||
$this->index = $index;
|
||||
$this->skipIrrelevantTokens();
|
||||
}
|
||||
/**
|
||||
* @return list<array{string, int, int}>
|
||||
*/
|
||||
public function getTokens() : array
|
||||
{
|
||||
return $this->tokens;
|
||||
}
|
||||
public function getContentBetween(int $startPos, int $endPos) : string
|
||||
{
|
||||
if ($startPos < 0 || $endPos > count($this->tokens)) {
|
||||
throw new LogicException();
|
||||
}
|
||||
$content = '';
|
||||
for ($i = $startPos; $i < $endPos; $i++) {
|
||||
$content .= $this->tokens[$i][Lexer::VALUE_OFFSET];
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
public function getTokenCount() : int
|
||||
{
|
||||
return count($this->tokens);
|
||||
}
|
||||
public function currentTokenValue() : string
|
||||
{
|
||||
return $this->tokens[$this->index][Lexer::VALUE_OFFSET];
|
||||
}
|
||||
public function currentTokenType() : int
|
||||
{
|
||||
return $this->tokens[$this->index][Lexer::TYPE_OFFSET];
|
||||
}
|
||||
public function currentTokenOffset() : int
|
||||
{
|
||||
$offset = 0;
|
||||
for ($i = 0; $i < $this->index; $i++) {
|
||||
$offset += strlen($this->tokens[$i][Lexer::VALUE_OFFSET]);
|
||||
}
|
||||
return $offset;
|
||||
}
|
||||
public function currentTokenLine() : int
|
||||
{
|
||||
return $this->tokens[$this->index][Lexer::LINE_OFFSET];
|
||||
}
|
||||
public function currentTokenIndex() : int
|
||||
{
|
||||
return $this->index;
|
||||
}
|
||||
public function endIndexOfLastRelevantToken() : int
|
||||
{
|
||||
$endIndex = $this->currentTokenIndex();
|
||||
$endIndex--;
|
||||
while (in_array($this->tokens[$endIndex][Lexer::TYPE_OFFSET], $this->skippedTokenTypes, \true)) {
|
||||
if (!isset($this->tokens[$endIndex - 1])) {
|
||||
break;
|
||||
}
|
||||
$endIndex--;
|
||||
}
|
||||
return $endIndex;
|
||||
}
|
||||
public function isCurrentTokenValue(string $tokenValue) : bool
|
||||
{
|
||||
return $this->tokens[$this->index][Lexer::VALUE_OFFSET] === $tokenValue;
|
||||
}
|
||||
public function isCurrentTokenType(int ...$tokenType) : bool
|
||||
{
|
||||
return in_array($this->tokens[$this->index][Lexer::TYPE_OFFSET], $tokenType, \true);
|
||||
}
|
||||
public function isPrecededByHorizontalWhitespace() : bool
|
||||
{
|
||||
return ($this->tokens[$this->index - 1][Lexer::TYPE_OFFSET] ?? -1) === Lexer::TOKEN_HORIZONTAL_WS;
|
||||
}
|
||||
/**
|
||||
* @throws ParserException
|
||||
*/
|
||||
public function consumeTokenType(int $tokenType) : void
|
||||
{
|
||||
if ($this->tokens[$this->index][Lexer::TYPE_OFFSET] !== $tokenType) {
|
||||
$this->throwError($tokenType);
|
||||
}
|
||||
if ($tokenType === Lexer::TOKEN_PHPDOC_EOL) {
|
||||
if ($this->newline === null) {
|
||||
$this->detectNewline();
|
||||
}
|
||||
}
|
||||
$this->index++;
|
||||
$this->skipIrrelevantTokens();
|
||||
}
|
||||
/**
|
||||
* @throws ParserException
|
||||
*/
|
||||
public function consumeTokenValue(int $tokenType, string $tokenValue) : void
|
||||
{
|
||||
if ($this->tokens[$this->index][Lexer::TYPE_OFFSET] !== $tokenType || $this->tokens[$this->index][Lexer::VALUE_OFFSET] !== $tokenValue) {
|
||||
$this->throwError($tokenType, $tokenValue);
|
||||
}
|
||||
$this->index++;
|
||||
$this->skipIrrelevantTokens();
|
||||
}
|
||||
/** @phpstan-impure */
|
||||
public function tryConsumeTokenValue(string $tokenValue) : bool
|
||||
{
|
||||
if ($this->tokens[$this->index][Lexer::VALUE_OFFSET] !== $tokenValue) {
|
||||
return \false;
|
||||
}
|
||||
$this->index++;
|
||||
$this->skipIrrelevantTokens();
|
||||
return \true;
|
||||
}
|
||||
/** @phpstan-impure */
|
||||
public function tryConsumeTokenType(int $tokenType) : bool
|
||||
{
|
||||
if ($this->tokens[$this->index][Lexer::TYPE_OFFSET] !== $tokenType) {
|
||||
return \false;
|
||||
}
|
||||
if ($tokenType === Lexer::TOKEN_PHPDOC_EOL) {
|
||||
if ($this->newline === null) {
|
||||
$this->detectNewline();
|
||||
}
|
||||
}
|
||||
$this->index++;
|
||||
$this->skipIrrelevantTokens();
|
||||
return \true;
|
||||
}
|
||||
private function detectNewline() : void
|
||||
{
|
||||
$value = $this->currentTokenValue();
|
||||
if (substr($value, 0, 2) === "\r\n") {
|
||||
$this->newline = "\r\n";
|
||||
} elseif (substr($value, 0, 1) === "\n") {
|
||||
$this->newline = "\n";
|
||||
}
|
||||
}
|
||||
public function getSkippedHorizontalWhiteSpaceIfAny() : string
|
||||
{
|
||||
if ($this->index > 0 && $this->tokens[$this->index - 1][Lexer::TYPE_OFFSET] === Lexer::TOKEN_HORIZONTAL_WS) {
|
||||
return $this->tokens[$this->index - 1][Lexer::VALUE_OFFSET];
|
||||
}
|
||||
return '';
|
||||
}
|
||||
/** @phpstan-impure */
|
||||
public function joinUntil(int ...$tokenType) : string
|
||||
{
|
||||
$s = '';
|
||||
while (!in_array($this->tokens[$this->index][Lexer::TYPE_OFFSET], $tokenType, \true)) {
|
||||
$s .= $this->tokens[$this->index++][Lexer::VALUE_OFFSET];
|
||||
}
|
||||
return $s;
|
||||
}
|
||||
public function next() : void
|
||||
{
|
||||
$this->index++;
|
||||
$this->skipIrrelevantTokens();
|
||||
}
|
||||
private function skipIrrelevantTokens() : void
|
||||
{
|
||||
if (!isset($this->tokens[$this->index])) {
|
||||
return;
|
||||
}
|
||||
while (in_array($this->tokens[$this->index][Lexer::TYPE_OFFSET], $this->skippedTokenTypes, \true)) {
|
||||
if (!isset($this->tokens[$this->index + 1])) {
|
||||
break;
|
||||
}
|
||||
$this->index++;
|
||||
}
|
||||
}
|
||||
public function addEndOfLineToSkippedTokens() : void
|
||||
{
|
||||
$this->skippedTokenTypes = [Lexer::TOKEN_HORIZONTAL_WS, Lexer::TOKEN_PHPDOC_EOL];
|
||||
}
|
||||
public function removeEndOfLineFromSkippedTokens() : void
|
||||
{
|
||||
$this->skippedTokenTypes = [Lexer::TOKEN_HORIZONTAL_WS];
|
||||
}
|
||||
/** @phpstan-impure */
|
||||
public function forwardToTheEnd() : void
|
||||
{
|
||||
$lastToken = count($this->tokens) - 1;
|
||||
$this->index = $lastToken;
|
||||
}
|
||||
public function pushSavePoint() : void
|
||||
{
|
||||
$this->savePoints[] = $this->index;
|
||||
}
|
||||
public function dropSavePoint() : void
|
||||
{
|
||||
array_pop($this->savePoints);
|
||||
}
|
||||
public function rollback() : void
|
||||
{
|
||||
$index = array_pop($this->savePoints);
|
||||
assert($index !== null);
|
||||
$this->index = $index;
|
||||
}
|
||||
/**
|
||||
* @throws ParserException
|
||||
*/
|
||||
private function throwError(int $expectedTokenType, ?string $expectedTokenValue = null) : void
|
||||
{
|
||||
throw new ParserException($this->currentTokenValue(), $this->currentTokenType(), $this->currentTokenOffset(), $expectedTokenType, $expectedTokenValue, $this->currentTokenLine());
|
||||
}
|
||||
/**
|
||||
* Check whether the position is directly preceded by a certain token type.
|
||||
*
|
||||
* During this check TOKEN_HORIZONTAL_WS and TOKEN_PHPDOC_EOL are skipped
|
||||
*/
|
||||
public function hasTokenImmediatelyBefore(int $pos, int $expectedTokenType) : bool
|
||||
{
|
||||
$tokens = $this->tokens;
|
||||
$pos--;
|
||||
for (; $pos >= 0; $pos--) {
|
||||
$token = $tokens[$pos];
|
||||
$type = $token[Lexer::TYPE_OFFSET];
|
||||
if ($type === $expectedTokenType) {
|
||||
return \true;
|
||||
}
|
||||
if (!in_array($type, [Lexer::TOKEN_HORIZONTAL_WS, Lexer::TOKEN_PHPDOC_EOL], \true)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return \false;
|
||||
}
|
||||
/**
|
||||
* Check whether the position is directly followed by a certain token type.
|
||||
*
|
||||
* During this check TOKEN_HORIZONTAL_WS and TOKEN_PHPDOC_EOL are skipped
|
||||
*/
|
||||
public function hasTokenImmediatelyAfter(int $pos, int $expectedTokenType) : bool
|
||||
{
|
||||
$tokens = $this->tokens;
|
||||
$pos++;
|
||||
for ($c = count($tokens); $pos < $c; $pos++) {
|
||||
$token = $tokens[$pos];
|
||||
$type = $token[Lexer::TYPE_OFFSET];
|
||||
if ($type === $expectedTokenType) {
|
||||
return \true;
|
||||
}
|
||||
if (!in_array($type, [Lexer::TOKEN_HORIZONTAL_WS, Lexer::TOKEN_PHPDOC_EOL], \true)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return \false;
|
||||
}
|
||||
public function getDetectedNewline() : ?string
|
||||
{
|
||||
return $this->newline;
|
||||
}
|
||||
/**
|
||||
* Whether the given position is immediately surrounded by parenthesis.
|
||||
*/
|
||||
public function hasParentheses(int $startPos, int $endPos) : bool
|
||||
{
|
||||
return $this->hasTokenImmediatelyBefore($startPos, Lexer::TOKEN_OPEN_PARENTHESES) && $this->hasTokenImmediatelyAfter($endPos, Lexer::TOKEN_CLOSE_PARENTHESES);
|
||||
}
|
||||
}
|
624
dependencies/phpstan/phpdoc-parser/src/Parser/TypeParser.php
vendored
Normal file
624
dependencies/phpstan/phpdoc-parser/src/Parser/TypeParser.php
vendored
Normal file
@ -0,0 +1,624 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace WP_Ultimo\Dependencies\PHPStan\PhpDocParser\Parser;
|
||||
|
||||
use LogicException;
|
||||
use WP_Ultimo\Dependencies\PHPStan\PhpDocParser\Ast;
|
||||
use WP_Ultimo\Dependencies\PHPStan\PhpDocParser\Lexer\Lexer;
|
||||
use function in_array;
|
||||
use function str_replace;
|
||||
use function strpos;
|
||||
use function trim;
|
||||
class TypeParser
|
||||
{
|
||||
/** @var ConstExprParser|null */
|
||||
private $constExprParser;
|
||||
/** @var bool */
|
||||
private $quoteAwareConstExprString;
|
||||
/** @var bool */
|
||||
private $useLinesAttributes;
|
||||
/** @var bool */
|
||||
private $useIndexAttributes;
|
||||
/**
|
||||
* @param array{lines?: bool, indexes?: bool} $usedAttributes
|
||||
*/
|
||||
public function __construct(?ConstExprParser $constExprParser = null, bool $quoteAwareConstExprString = \false, array $usedAttributes = [])
|
||||
{
|
||||
$this->constExprParser = $constExprParser;
|
||||
$this->quoteAwareConstExprString = $quoteAwareConstExprString;
|
||||
$this->useLinesAttributes = $usedAttributes['lines'] ?? \false;
|
||||
$this->useIndexAttributes = $usedAttributes['indexes'] ?? \false;
|
||||
}
|
||||
/** @phpstan-impure */
|
||||
public function parse(TokenIterator $tokens) : Ast\Type\TypeNode
|
||||
{
|
||||
$startLine = $tokens->currentTokenLine();
|
||||
$startIndex = $tokens->currentTokenIndex();
|
||||
if ($tokens->isCurrentTokenType(Lexer::TOKEN_NULLABLE)) {
|
||||
$type = $this->parseNullable($tokens);
|
||||
} else {
|
||||
$type = $this->parseAtomic($tokens);
|
||||
if ($tokens->isCurrentTokenType(Lexer::TOKEN_UNION)) {
|
||||
$type = $this->parseUnion($tokens, $type);
|
||||
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_INTERSECTION)) {
|
||||
$type = $this->parseIntersection($tokens, $type);
|
||||
}
|
||||
}
|
||||
return $this->enrichWithAttributes($tokens, $type, $startLine, $startIndex);
|
||||
}
|
||||
/**
|
||||
* @internal
|
||||
* @template T of Ast\Node
|
||||
* @param T $type
|
||||
* @return T
|
||||
*/
|
||||
public function enrichWithAttributes(TokenIterator $tokens, Ast\Node $type, int $startLine, int $startIndex) : Ast\Node
|
||||
{
|
||||
if ($this->useLinesAttributes) {
|
||||
$type->setAttribute(Ast\Attribute::START_LINE, $startLine);
|
||||
$type->setAttribute(Ast\Attribute::END_LINE, $tokens->currentTokenLine());
|
||||
}
|
||||
if ($this->useIndexAttributes) {
|
||||
$type->setAttribute(Ast\Attribute::START_INDEX, $startIndex);
|
||||
$type->setAttribute(Ast\Attribute::END_INDEX, $tokens->endIndexOfLastRelevantToken());
|
||||
}
|
||||
return $type;
|
||||
}
|
||||
/** @phpstan-impure */
|
||||
private function subParse(TokenIterator $tokens) : Ast\Type\TypeNode
|
||||
{
|
||||
$startLine = $tokens->currentTokenLine();
|
||||
$startIndex = $tokens->currentTokenIndex();
|
||||
if ($tokens->isCurrentTokenType(Lexer::TOKEN_NULLABLE)) {
|
||||
$type = $this->parseNullable($tokens);
|
||||
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_VARIABLE)) {
|
||||
$type = $this->parseConditionalForParameter($tokens, $tokens->currentTokenValue());
|
||||
} else {
|
||||
$type = $this->parseAtomic($tokens);
|
||||
if ($tokens->isCurrentTokenValue('is')) {
|
||||
$type = $this->parseConditional($tokens, $type);
|
||||
} else {
|
||||
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
||||
if ($tokens->isCurrentTokenType(Lexer::TOKEN_UNION)) {
|
||||
$type = $this->subParseUnion($tokens, $type);
|
||||
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_INTERSECTION)) {
|
||||
$type = $this->subParseIntersection($tokens, $type);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->enrichWithAttributes($tokens, $type, $startLine, $startIndex);
|
||||
}
|
||||
/** @phpstan-impure */
|
||||
private function parseAtomic(TokenIterator $tokens) : Ast\Type\TypeNode
|
||||
{
|
||||
$startLine = $tokens->currentTokenLine();
|
||||
$startIndex = $tokens->currentTokenIndex();
|
||||
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) {
|
||||
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
||||
$type = $this->subParse($tokens);
|
||||
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES);
|
||||
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
|
||||
$type = $this->tryParseArrayOrOffsetAccess($tokens, $type);
|
||||
}
|
||||
return $this->enrichWithAttributes($tokens, $type, $startLine, $startIndex);
|
||||
}
|
||||
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_THIS_VARIABLE)) {
|
||||
$type = $this->enrichWithAttributes($tokens, new Ast\Type\ThisTypeNode(), $startLine, $startIndex);
|
||||
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
|
||||
$type = $this->tryParseArrayOrOffsetAccess($tokens, $type);
|
||||
}
|
||||
return $this->enrichWithAttributes($tokens, $type, $startLine, $startIndex);
|
||||
}
|
||||
$currentTokenValue = $tokens->currentTokenValue();
|
||||
$tokens->pushSavePoint();
|
||||
// because of ConstFetchNode
|
||||
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_IDENTIFIER)) {
|
||||
$type = $this->enrichWithAttributes($tokens, new Ast\Type\IdentifierTypeNode($currentTokenValue), $startLine, $startIndex);
|
||||
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_DOUBLE_COLON)) {
|
||||
$tokens->dropSavePoint();
|
||||
// because of ConstFetchNode
|
||||
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET)) {
|
||||
$tokens->pushSavePoint();
|
||||
$isHtml = $this->isHtml($tokens);
|
||||
$tokens->rollback();
|
||||
if ($isHtml) {
|
||||
return $type;
|
||||
}
|
||||
$type = $this->parseGeneric($tokens, $type);
|
||||
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
|
||||
$type = $this->tryParseArrayOrOffsetAccess($tokens, $type);
|
||||
}
|
||||
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) {
|
||||
$type = $this->tryParseCallable($tokens, $type);
|
||||
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
|
||||
$type = $this->tryParseArrayOrOffsetAccess($tokens, $type);
|
||||
} elseif (in_array($type->name, ['array', 'list', 'object'], \true) && $tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_CURLY_BRACKET) && !$tokens->isPrecededByHorizontalWhitespace()) {
|
||||
if ($type->name === 'object') {
|
||||
$type = $this->parseObjectShape($tokens);
|
||||
} else {
|
||||
$type = $this->parseArrayShape($tokens, $type, $type->name);
|
||||
}
|
||||
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
|
||||
$type = $this->tryParseArrayOrOffsetAccess($tokens, $this->enrichWithAttributes($tokens, $type, $startLine, $startIndex));
|
||||
}
|
||||
}
|
||||
return $this->enrichWithAttributes($tokens, $type, $startLine, $startIndex);
|
||||
} else {
|
||||
$tokens->rollback();
|
||||
// because of ConstFetchNode
|
||||
}
|
||||
} else {
|
||||
$tokens->dropSavePoint();
|
||||
// because of ConstFetchNode
|
||||
}
|
||||
$currentTokenValue = $tokens->currentTokenValue();
|
||||
$currentTokenType = $tokens->currentTokenType();
|
||||
$currentTokenOffset = $tokens->currentTokenOffset();
|
||||
$currentTokenLine = $tokens->currentTokenLine();
|
||||
if ($this->constExprParser === null) {
|
||||
throw new ParserException($currentTokenValue, $currentTokenType, $currentTokenOffset, Lexer::TOKEN_IDENTIFIER, null, $currentTokenLine);
|
||||
}
|
||||
try {
|
||||
$constExpr = $this->constExprParser->parse($tokens, \true);
|
||||
if ($constExpr instanceof Ast\ConstExpr\ConstExprArrayNode) {
|
||||
throw new ParserException($currentTokenValue, $currentTokenType, $currentTokenOffset, Lexer::TOKEN_IDENTIFIER, null, $currentTokenLine);
|
||||
}
|
||||
return $this->enrichWithAttributes($tokens, new Ast\Type\ConstTypeNode($constExpr), $startLine, $startIndex);
|
||||
} catch (LogicException $e) {
|
||||
throw new ParserException($currentTokenValue, $currentTokenType, $currentTokenOffset, Lexer::TOKEN_IDENTIFIER, null, $currentTokenLine);
|
||||
}
|
||||
}
|
||||
/** @phpstan-impure */
|
||||
private function parseUnion(TokenIterator $tokens, Ast\Type\TypeNode $type) : Ast\Type\TypeNode
|
||||
{
|
||||
$types = [$type];
|
||||
while ($tokens->tryConsumeTokenType(Lexer::TOKEN_UNION)) {
|
||||
$types[] = $this->parseAtomic($tokens);
|
||||
}
|
||||
return new Ast\Type\UnionTypeNode($types);
|
||||
}
|
||||
/** @phpstan-impure */
|
||||
private function subParseUnion(TokenIterator $tokens, Ast\Type\TypeNode $type) : Ast\Type\TypeNode
|
||||
{
|
||||
$types = [$type];
|
||||
while ($tokens->tryConsumeTokenType(Lexer::TOKEN_UNION)) {
|
||||
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
||||
$types[] = $this->parseAtomic($tokens);
|
||||
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
||||
}
|
||||
return new Ast\Type\UnionTypeNode($types);
|
||||
}
|
||||
/** @phpstan-impure */
|
||||
private function parseIntersection(TokenIterator $tokens, Ast\Type\TypeNode $type) : Ast\Type\TypeNode
|
||||
{
|
||||
$types = [$type];
|
||||
while ($tokens->tryConsumeTokenType(Lexer::TOKEN_INTERSECTION)) {
|
||||
$types[] = $this->parseAtomic($tokens);
|
||||
}
|
||||
return new Ast\Type\IntersectionTypeNode($types);
|
||||
}
|
||||
/** @phpstan-impure */
|
||||
private function subParseIntersection(TokenIterator $tokens, Ast\Type\TypeNode $type) : Ast\Type\TypeNode
|
||||
{
|
||||
$types = [$type];
|
||||
while ($tokens->tryConsumeTokenType(Lexer::TOKEN_INTERSECTION)) {
|
||||
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
||||
$types[] = $this->parseAtomic($tokens);
|
||||
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
||||
}
|
||||
return new Ast\Type\IntersectionTypeNode($types);
|
||||
}
|
||||
/** @phpstan-impure */
|
||||
private function parseConditional(TokenIterator $tokens, Ast\Type\TypeNode $subjectType) : Ast\Type\TypeNode
|
||||
{
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
|
||||
$negated = \false;
|
||||
if ($tokens->isCurrentTokenValue('not')) {
|
||||
$negated = \true;
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
|
||||
}
|
||||
$targetType = $this->parse($tokens);
|
||||
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_NULLABLE);
|
||||
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
||||
$ifType = $this->parse($tokens);
|
||||
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_COLON);
|
||||
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
||||
$elseType = $this->subParse($tokens);
|
||||
return new Ast\Type\ConditionalTypeNode($subjectType, $targetType, $ifType, $elseType, $negated);
|
||||
}
|
||||
/** @phpstan-impure */
|
||||
private function parseConditionalForParameter(TokenIterator $tokens, string $parameterName) : Ast\Type\TypeNode
|
||||
{
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_VARIABLE);
|
||||
$tokens->consumeTokenValue(Lexer::TOKEN_IDENTIFIER, 'is');
|
||||
$negated = \false;
|
||||
if ($tokens->isCurrentTokenValue('not')) {
|
||||
$negated = \true;
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
|
||||
}
|
||||
$targetType = $this->parse($tokens);
|
||||
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_NULLABLE);
|
||||
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
||||
$ifType = $this->parse($tokens);
|
||||
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_COLON);
|
||||
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
||||
$elseType = $this->subParse($tokens);
|
||||
return new Ast\Type\ConditionalTypeForParameterNode($parameterName, $targetType, $ifType, $elseType, $negated);
|
||||
}
|
||||
/** @phpstan-impure */
|
||||
private function parseNullable(TokenIterator $tokens) : Ast\Type\TypeNode
|
||||
{
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_NULLABLE);
|
||||
$type = $this->parseAtomic($tokens);
|
||||
return new Ast\Type\NullableTypeNode($type);
|
||||
}
|
||||
/** @phpstan-impure */
|
||||
public function isHtml(TokenIterator $tokens) : bool
|
||||
{
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET);
|
||||
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_IDENTIFIER)) {
|
||||
return \false;
|
||||
}
|
||||
$htmlTagName = $tokens->currentTokenValue();
|
||||
$tokens->next();
|
||||
if (!$tokens->tryConsumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET)) {
|
||||
return \false;
|
||||
}
|
||||
while (!$tokens->isCurrentTokenType(Lexer::TOKEN_END)) {
|
||||
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET) && strpos($tokens->currentTokenValue(), '/' . $htmlTagName . '>') !== \false) {
|
||||
return \true;
|
||||
}
|
||||
$tokens->next();
|
||||
}
|
||||
return \false;
|
||||
}
|
||||
/** @phpstan-impure */
|
||||
public function parseGeneric(TokenIterator $tokens, Ast\Type\IdentifierTypeNode $baseType) : Ast\Type\GenericTypeNode
|
||||
{
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET);
|
||||
$startLine = $baseType->getAttribute(Ast\Attribute::START_LINE);
|
||||
$startIndex = $baseType->getAttribute(Ast\Attribute::START_INDEX);
|
||||
$genericTypes = [];
|
||||
$variances = [];
|
||||
$isFirst = \true;
|
||||
while ($isFirst || $tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)) {
|
||||
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
||||
// trailing comma case
|
||||
if (!$isFirst && $tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET)) {
|
||||
break;
|
||||
}
|
||||
$isFirst = \false;
|
||||
[$genericTypes[], $variances[]] = $this->parseGenericTypeArgument($tokens);
|
||||
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
||||
}
|
||||
$type = new Ast\Type\GenericTypeNode($baseType, $genericTypes, $variances);
|
||||
if ($startLine !== null && $startIndex !== null) {
|
||||
$type = $this->enrichWithAttributes($tokens, $type, $startLine, $startIndex);
|
||||
}
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET);
|
||||
return $type;
|
||||
}
|
||||
/**
|
||||
* @phpstan-impure
|
||||
* @return array{Ast\Type\TypeNode, Ast\Type\GenericTypeNode::VARIANCE_*}
|
||||
*/
|
||||
public function parseGenericTypeArgument(TokenIterator $tokens) : array
|
||||
{
|
||||
$startLine = $tokens->currentTokenLine();
|
||||
$startIndex = $tokens->currentTokenIndex();
|
||||
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_WILDCARD)) {
|
||||
return [$this->enrichWithAttributes($tokens, new Ast\Type\IdentifierTypeNode('mixed'), $startLine, $startIndex), Ast\Type\GenericTypeNode::VARIANCE_BIVARIANT];
|
||||
}
|
||||
if ($tokens->tryConsumeTokenValue('contravariant')) {
|
||||
$variance = Ast\Type\GenericTypeNode::VARIANCE_CONTRAVARIANT;
|
||||
} elseif ($tokens->tryConsumeTokenValue('covariant')) {
|
||||
$variance = Ast\Type\GenericTypeNode::VARIANCE_COVARIANT;
|
||||
} else {
|
||||
$variance = Ast\Type\GenericTypeNode::VARIANCE_INVARIANT;
|
||||
}
|
||||
$type = $this->parse($tokens);
|
||||
return [$type, $variance];
|
||||
}
|
||||
/** @phpstan-impure */
|
||||
private function parseCallable(TokenIterator $tokens, Ast\Type\IdentifierTypeNode $identifier) : Ast\Type\TypeNode
|
||||
{
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES);
|
||||
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
||||
$parameters = [];
|
||||
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PARENTHESES)) {
|
||||
$parameters[] = $this->parseCallableParameter($tokens);
|
||||
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
||||
while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)) {
|
||||
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
||||
if ($tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PARENTHESES)) {
|
||||
break;
|
||||
}
|
||||
$parameters[] = $this->parseCallableParameter($tokens);
|
||||
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
||||
}
|
||||
}
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES);
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_COLON);
|
||||
$startLine = $tokens->currentTokenLine();
|
||||
$startIndex = $tokens->currentTokenIndex();
|
||||
$returnType = $this->enrichWithAttributes($tokens, $this->parseCallableReturnType($tokens), $startLine, $startIndex);
|
||||
return new Ast\Type\CallableTypeNode($identifier, $parameters, $returnType);
|
||||
}
|
||||
/** @phpstan-impure */
|
||||
private function parseCallableParameter(TokenIterator $tokens) : Ast\Type\CallableTypeParameterNode
|
||||
{
|
||||
$startLine = $tokens->currentTokenLine();
|
||||
$startIndex = $tokens->currentTokenIndex();
|
||||
$type = $this->parse($tokens);
|
||||
$isReference = $tokens->tryConsumeTokenType(Lexer::TOKEN_REFERENCE);
|
||||
$isVariadic = $tokens->tryConsumeTokenType(Lexer::TOKEN_VARIADIC);
|
||||
if ($tokens->isCurrentTokenType(Lexer::TOKEN_VARIABLE)) {
|
||||
$parameterName = $tokens->currentTokenValue();
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_VARIABLE);
|
||||
} else {
|
||||
$parameterName = '';
|
||||
}
|
||||
$isOptional = $tokens->tryConsumeTokenType(Lexer::TOKEN_EQUAL);
|
||||
return $this->enrichWithAttributes($tokens, new Ast\Type\CallableTypeParameterNode($type, $isReference, $isVariadic, $parameterName, $isOptional), $startLine, $startIndex);
|
||||
}
|
||||
/** @phpstan-impure */
|
||||
private function parseCallableReturnType(TokenIterator $tokens) : Ast\Type\TypeNode
|
||||
{
|
||||
$startLine = $tokens->currentTokenLine();
|
||||
$startIndex = $tokens->currentTokenIndex();
|
||||
if ($tokens->isCurrentTokenType(Lexer::TOKEN_NULLABLE)) {
|
||||
return $this->parseNullable($tokens);
|
||||
} elseif ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) {
|
||||
$type = $this->subParse($tokens);
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES);
|
||||
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
|
||||
$type = $this->tryParseArrayOrOffsetAccess($tokens, $type);
|
||||
}
|
||||
return $type;
|
||||
} elseif ($tokens->tryConsumeTokenType(Lexer::TOKEN_THIS_VARIABLE)) {
|
||||
$type = new Ast\Type\ThisTypeNode();
|
||||
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
|
||||
$type = $this->tryParseArrayOrOffsetAccess($tokens, $this->enrichWithAttributes($tokens, $type, $startLine, $startIndex));
|
||||
}
|
||||
return $type;
|
||||
} else {
|
||||
$currentTokenValue = $tokens->currentTokenValue();
|
||||
$tokens->pushSavePoint();
|
||||
// because of ConstFetchNode
|
||||
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_IDENTIFIER)) {
|
||||
$type = new Ast\Type\IdentifierTypeNode($currentTokenValue);
|
||||
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_DOUBLE_COLON)) {
|
||||
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET)) {
|
||||
$type = $this->parseGeneric($tokens, $this->enrichWithAttributes($tokens, $type, $startLine, $startIndex));
|
||||
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
|
||||
$type = $this->tryParseArrayOrOffsetAccess($tokens, $this->enrichWithAttributes($tokens, $type, $startLine, $startIndex));
|
||||
}
|
||||
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
|
||||
$type = $this->tryParseArrayOrOffsetAccess($tokens, $this->enrichWithAttributes($tokens, $type, $startLine, $startIndex));
|
||||
} elseif (in_array($type->name, ['array', 'list', 'object'], \true) && $tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_CURLY_BRACKET) && !$tokens->isPrecededByHorizontalWhitespace()) {
|
||||
if ($type->name === 'object') {
|
||||
$type = $this->parseObjectShape($tokens);
|
||||
} else {
|
||||
$type = $this->parseArrayShape($tokens, $this->enrichWithAttributes($tokens, $type, $startLine, $startIndex), $type->name);
|
||||
}
|
||||
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
|
||||
$type = $this->tryParseArrayOrOffsetAccess($tokens, $this->enrichWithAttributes($tokens, $type, $startLine, $startIndex));
|
||||
}
|
||||
}
|
||||
return $type;
|
||||
} else {
|
||||
$tokens->rollback();
|
||||
// because of ConstFetchNode
|
||||
}
|
||||
} else {
|
||||
$tokens->dropSavePoint();
|
||||
// because of ConstFetchNode
|
||||
}
|
||||
}
|
||||
$currentTokenValue = $tokens->currentTokenValue();
|
||||
$currentTokenType = $tokens->currentTokenType();
|
||||
$currentTokenOffset = $tokens->currentTokenOffset();
|
||||
$currentTokenLine = $tokens->currentTokenLine();
|
||||
if ($this->constExprParser === null) {
|
||||
throw new ParserException($currentTokenValue, $currentTokenType, $currentTokenOffset, Lexer::TOKEN_IDENTIFIER, null, $currentTokenLine);
|
||||
}
|
||||
try {
|
||||
$constExpr = $this->constExprParser->parse($tokens, \true);
|
||||
if ($constExpr instanceof Ast\ConstExpr\ConstExprArrayNode) {
|
||||
throw new ParserException($currentTokenValue, $currentTokenType, $currentTokenOffset, Lexer::TOKEN_IDENTIFIER, null, $currentTokenLine);
|
||||
}
|
||||
$type = new Ast\Type\ConstTypeNode($constExpr);
|
||||
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
|
||||
$type = $this->tryParseArrayOrOffsetAccess($tokens, $this->enrichWithAttributes($tokens, $type, $startLine, $startIndex));
|
||||
}
|
||||
return $type;
|
||||
} catch (LogicException $e) {
|
||||
throw new ParserException($currentTokenValue, $currentTokenType, $currentTokenOffset, Lexer::TOKEN_IDENTIFIER, null, $currentTokenLine);
|
||||
}
|
||||
}
|
||||
/** @phpstan-impure */
|
||||
private function tryParseCallable(TokenIterator $tokens, Ast\Type\IdentifierTypeNode $identifier) : Ast\Type\TypeNode
|
||||
{
|
||||
try {
|
||||
$tokens->pushSavePoint();
|
||||
$type = $this->parseCallable($tokens, $identifier);
|
||||
$tokens->dropSavePoint();
|
||||
} catch (ParserException $e) {
|
||||
$tokens->rollback();
|
||||
$type = $identifier;
|
||||
}
|
||||
return $type;
|
||||
}
|
||||
/** @phpstan-impure */
|
||||
private function tryParseArrayOrOffsetAccess(TokenIterator $tokens, Ast\Type\TypeNode $type) : Ast\Type\TypeNode
|
||||
{
|
||||
$startLine = $type->getAttribute(Ast\Attribute::START_LINE);
|
||||
$startIndex = $type->getAttribute(Ast\Attribute::START_INDEX);
|
||||
try {
|
||||
while ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
|
||||
$tokens->pushSavePoint();
|
||||
$canBeOffsetAccessType = !$tokens->isPrecededByHorizontalWhitespace();
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET);
|
||||
if ($canBeOffsetAccessType && !$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_SQUARE_BRACKET)) {
|
||||
$offset = $this->parse($tokens);
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_SQUARE_BRACKET);
|
||||
$tokens->dropSavePoint();
|
||||
$type = new Ast\Type\OffsetAccessTypeNode($type, $offset);
|
||||
if ($startLine !== null && $startIndex !== null) {
|
||||
$type = $this->enrichWithAttributes($tokens, $type, $startLine, $startIndex);
|
||||
}
|
||||
} else {
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_SQUARE_BRACKET);
|
||||
$tokens->dropSavePoint();
|
||||
$type = new Ast\Type\ArrayTypeNode($type);
|
||||
if ($startLine !== null && $startIndex !== null) {
|
||||
$type = $this->enrichWithAttributes($tokens, $type, $startLine, $startIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ParserException $e) {
|
||||
$tokens->rollback();
|
||||
}
|
||||
return $type;
|
||||
}
|
||||
/**
|
||||
* @phpstan-impure
|
||||
* @param Ast\Type\ArrayShapeNode::KIND_* $kind
|
||||
*/
|
||||
private function parseArrayShape(TokenIterator $tokens, Ast\Type\TypeNode $type, string $kind) : Ast\Type\ArrayShapeNode
|
||||
{
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_OPEN_CURLY_BRACKET);
|
||||
$items = [];
|
||||
$sealed = \true;
|
||||
do {
|
||||
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
||||
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET)) {
|
||||
return new Ast\Type\ArrayShapeNode($items, \true, $kind);
|
||||
}
|
||||
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_VARIADIC)) {
|
||||
$sealed = \false;
|
||||
$tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA);
|
||||
break;
|
||||
}
|
||||
$items[] = $this->parseArrayShapeItem($tokens);
|
||||
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
||||
} while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA));
|
||||
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET);
|
||||
return new Ast\Type\ArrayShapeNode($items, $sealed, $kind);
|
||||
}
|
||||
/** @phpstan-impure */
|
||||
private function parseArrayShapeItem(TokenIterator $tokens) : Ast\Type\ArrayShapeItemNode
|
||||
{
|
||||
$startLine = $tokens->currentTokenLine();
|
||||
$startIndex = $tokens->currentTokenIndex();
|
||||
try {
|
||||
$tokens->pushSavePoint();
|
||||
$key = $this->parseArrayShapeKey($tokens);
|
||||
$optional = $tokens->tryConsumeTokenType(Lexer::TOKEN_NULLABLE);
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_COLON);
|
||||
$value = $this->parse($tokens);
|
||||
$tokens->dropSavePoint();
|
||||
return $this->enrichWithAttributes($tokens, new Ast\Type\ArrayShapeItemNode($key, $optional, $value), $startLine, $startIndex);
|
||||
} catch (ParserException $e) {
|
||||
$tokens->rollback();
|
||||
$value = $this->parse($tokens);
|
||||
return $this->enrichWithAttributes($tokens, new Ast\Type\ArrayShapeItemNode(null, \false, $value), $startLine, $startIndex);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @phpstan-impure
|
||||
* @return Ast\ConstExpr\ConstExprIntegerNode|Ast\ConstExpr\ConstExprStringNode|Ast\Type\IdentifierTypeNode
|
||||
*/
|
||||
private function parseArrayShapeKey(TokenIterator $tokens)
|
||||
{
|
||||
$startIndex = $tokens->currentTokenIndex();
|
||||
$startLine = $tokens->currentTokenLine();
|
||||
if ($tokens->isCurrentTokenType(Lexer::TOKEN_INTEGER)) {
|
||||
$key = new Ast\ConstExpr\ConstExprIntegerNode(str_replace('_', '', $tokens->currentTokenValue()));
|
||||
$tokens->next();
|
||||
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_SINGLE_QUOTED_STRING)) {
|
||||
if ($this->quoteAwareConstExprString) {
|
||||
$key = new Ast\ConstExpr\QuoteAwareConstExprStringNode(StringUnescaper::unescapeString($tokens->currentTokenValue()), Ast\ConstExpr\QuoteAwareConstExprStringNode::SINGLE_QUOTED);
|
||||
} else {
|
||||
$key = new Ast\ConstExpr\ConstExprStringNode(trim($tokens->currentTokenValue(), "'"));
|
||||
}
|
||||
$tokens->next();
|
||||
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_DOUBLE_QUOTED_STRING)) {
|
||||
if ($this->quoteAwareConstExprString) {
|
||||
$key = new Ast\ConstExpr\QuoteAwareConstExprStringNode(StringUnescaper::unescapeString($tokens->currentTokenValue()), Ast\ConstExpr\QuoteAwareConstExprStringNode::DOUBLE_QUOTED);
|
||||
} else {
|
||||
$key = new Ast\ConstExpr\ConstExprStringNode(trim($tokens->currentTokenValue(), '"'));
|
||||
}
|
||||
$tokens->next();
|
||||
} else {
|
||||
$key = new Ast\Type\IdentifierTypeNode($tokens->currentTokenValue());
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
|
||||
}
|
||||
return $this->enrichWithAttributes($tokens, $key, $startLine, $startIndex);
|
||||
}
|
||||
/**
|
||||
* @phpstan-impure
|
||||
*/
|
||||
private function parseObjectShape(TokenIterator $tokens) : Ast\Type\ObjectShapeNode
|
||||
{
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_OPEN_CURLY_BRACKET);
|
||||
$items = [];
|
||||
do {
|
||||
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
||||
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET)) {
|
||||
return new Ast\Type\ObjectShapeNode($items);
|
||||
}
|
||||
$items[] = $this->parseObjectShapeItem($tokens);
|
||||
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
||||
} while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA));
|
||||
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET);
|
||||
return new Ast\Type\ObjectShapeNode($items);
|
||||
}
|
||||
/** @phpstan-impure */
|
||||
private function parseObjectShapeItem(TokenIterator $tokens) : Ast\Type\ObjectShapeItemNode
|
||||
{
|
||||
$startLine = $tokens->currentTokenLine();
|
||||
$startIndex = $tokens->currentTokenIndex();
|
||||
$key = $this->parseObjectShapeKey($tokens);
|
||||
$optional = $tokens->tryConsumeTokenType(Lexer::TOKEN_NULLABLE);
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_COLON);
|
||||
$value = $this->parse($tokens);
|
||||
return $this->enrichWithAttributes($tokens, new Ast\Type\ObjectShapeItemNode($key, $optional, $value), $startLine, $startIndex);
|
||||
}
|
||||
/**
|
||||
* @phpstan-impure
|
||||
* @return Ast\ConstExpr\ConstExprStringNode|Ast\Type\IdentifierTypeNode
|
||||
*/
|
||||
private function parseObjectShapeKey(TokenIterator $tokens)
|
||||
{
|
||||
$startLine = $tokens->currentTokenLine();
|
||||
$startIndex = $tokens->currentTokenIndex();
|
||||
if ($tokens->isCurrentTokenType(Lexer::TOKEN_SINGLE_QUOTED_STRING)) {
|
||||
if ($this->quoteAwareConstExprString) {
|
||||
$key = new Ast\ConstExpr\QuoteAwareConstExprStringNode(StringUnescaper::unescapeString($tokens->currentTokenValue()), Ast\ConstExpr\QuoteAwareConstExprStringNode::SINGLE_QUOTED);
|
||||
} else {
|
||||
$key = new Ast\ConstExpr\ConstExprStringNode(trim($tokens->currentTokenValue(), "'"));
|
||||
}
|
||||
$tokens->next();
|
||||
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_DOUBLE_QUOTED_STRING)) {
|
||||
if ($this->quoteAwareConstExprString) {
|
||||
$key = new Ast\ConstExpr\QuoteAwareConstExprStringNode(StringUnescaper::unescapeString($tokens->currentTokenValue()), Ast\ConstExpr\QuoteAwareConstExprStringNode::DOUBLE_QUOTED);
|
||||
} else {
|
||||
$key = new Ast\ConstExpr\ConstExprStringNode(trim($tokens->currentTokenValue(), '"'));
|
||||
}
|
||||
$tokens->next();
|
||||
} else {
|
||||
$key = new Ast\Type\IdentifierTypeNode($tokens->currentTokenValue());
|
||||
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
|
||||
}
|
||||
return $this->enrichWithAttributes($tokens, $key, $startLine, $startIndex);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user