Skip to content

Commit

Permalink
ConstExprParser - attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Apr 24, 2023
1 parent b5fede3 commit 6220c55
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 20 deletions.
140 changes: 124 additions & 16 deletions src/Parser/ConstExprParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,53 @@ class ConstExprParser
/** @var bool */
private $quoteAwareConstExprString;

public function __construct(bool $unescapeStrings = false, bool $quoteAwareConstExprString = false)
/** @var bool */
private $useLinesAttributes;

/** @var bool */
private $useIndexAttributes;

/**
* @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;
}

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 new Ast\ConstExpr\ConstExprFloatNode($value);

return $this->enrichWithAttributes(
$tokens,
new Ast\ConstExpr\ConstExprFloatNode($value),
$startLine,
$startIndex
);
}

if ($tokens->isCurrentTokenType(Lexer::TOKEN_INTEGER)) {
$value = $tokens->currentTokenValue();
$tokens->next();
return new Ast\ConstExpr\ConstExprIntegerNode($value);

return $this->enrichWithAttributes(
$tokens,
new Ast\ConstExpr\ConstExprIntegerNode($value),
$startLine,
$startIndex
);
}

if ($tokens->isCurrentTokenType(Lexer::TOKEN_SINGLE_QUOTED_STRING, Lexer::TOKEN_DOUBLE_QUOTED_STRING)) {
Expand All @@ -49,27 +78,52 @@ public function parse(TokenIterator $tokens, bool $trimStrings = false): Ast\Con
$tokens->next();

if ($this->quoteAwareConstExprString) {
return new Ast\ConstExpr\QuoteAwareConstExprStringNode(
$value,
$type === Lexer::TOKEN_SINGLE_QUOTED_STRING
? Ast\ConstExpr\QuoteAwareConstExprStringNode::SINGLE_QUOTED
: Ast\ConstExpr\QuoteAwareConstExprStringNode::DOUBLE_QUOTED
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 new Ast\ConstExpr\ConstExprStringNode($value);
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 new Ast\ConstExpr\ConstExprTrueNode();
return $this->enrichWithAttributes(
$tokens,
new Ast\ConstExpr\ConstExprTrueNode(),
$startLine,
$startIndex
);
case 'false':
return new Ast\ConstExpr\ConstExprFalseNode();
return $this->enrichWithAttributes(
$tokens,
new Ast\ConstExpr\ConstExprFalseNode(),
$startLine,
$startIndex
);
case 'null':
return new Ast\ConstExpr\ConstExprNullNode();
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);
Expand Down Expand Up @@ -106,11 +160,21 @@ public function parse(TokenIterator $tokens, bool $trimStrings = false): Ast\Con
break;
}

return new Ast\ConstExpr\ConstFetchNode($identifier, $classConstantName);
return $this->enrichWithAttributes(
$tokens,
new Ast\ConstExpr\ConstFetchNode($identifier, $classConstantName),
$startLine,
$startIndex
);

}

return new Ast\ConstExpr\ConstFetchNode('', $identifier);
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);
Expand All @@ -131,19 +195,30 @@ private function parseArray(TokenIterator $tokens, int $endToken): Ast\ConstExpr
{
$items = [];

$startLine = $tokens->currentTokenLine();
$startIndex = $tokens->currentTokenIndex();

if (!$tokens->tryConsumeTokenType($endToken)) {
do {
$items[] = $this->parseArrayItem($tokens);
} while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA) && !$tokens->isCurrentTokenType($endToken));
$tokens->consumeTokenType($endToken);
}

return new Ast\ConstExpr\ConstExprArrayNode($items);
return $this->enrichWithAttributes(
$tokens,
new Ast\ConstExpr\ConstExprArrayNode($items),
$startLine,
$startIndex
);
}


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)) {
Expand All @@ -155,7 +230,40 @@ private function parseArrayItem(TokenIterator $tokens): Ast\ConstExpr\ConstExprA
$value = $expr;
}

return new Ast\ConstExpr\ConstExprArrayItemNode($key, $value);
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
{
$endLine = $tokens->currentTokenLine();
$endIndex = $tokens->currentTokenIndex();
if ($this->useLinesAttributes) {
$node->setAttribute(Ast\Attribute::START_LINE, $startLine);
$node->setAttribute(Ast\Attribute::END_LINE, $endLine);
}

if ($this->useIndexAttributes) {
$tokensArray = $tokens->getTokens();
$endIndex--;
if ($tokensArray[$endIndex][Lexer::TYPE_OFFSET] === Lexer::TOKEN_HORIZONTAL_WS) {
$endIndex--;
}

$node->setAttribute(Ast\Attribute::START_INDEX, $startIndex);
$node->setAttribute(Ast\Attribute::END_INDEX, $endIndex);
}

return $node;
}

}
34 changes: 34 additions & 0 deletions tests/PHPStan/Parser/ConstExprParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace PHPStan\PhpDocParser\Parser;

use Iterator;
use PHPStan\PhpDocParser\Ast\Attribute;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprArrayItemNode;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprArrayNode;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprFalseNode;
Expand All @@ -13,6 +14,7 @@
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprTrueNode;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode;
use PHPStan\PhpDocParser\Ast\NodeTraverser;
use PHPStan\PhpDocParser\Lexer\Lexer;
use PHPUnit\Framework\TestCase;

Expand Down Expand Up @@ -54,6 +56,38 @@ public function testParse(string $input, ConstExprNode $expectedExpr, int $nextT
}


/**
* @dataProvider provideTrueNodeParseData
* @dataProvider provideFalseNodeParseData
* @dataProvider provideNullNodeParseData
* @dataProvider provideIntegerNodeParseData
* @dataProvider provideFloatNodeParseData
* @dataProvider provideStringNodeParseData
* @dataProvider provideArrayNodeParseData
* @dataProvider provideFetchNodeParseData
*
* @dataProvider provideWithTrimStringsStringNodeParseData
*/
public function testVerifyAttributes(string $input): void
{
$tokens = new TokenIterator($this->lexer->tokenize($input));
$constExprParser = new ConstExprParser(true, true, [
'lines' => true,
'indexes' => true,
]);
$visitor = new NodeCollectingVisitor();
$traverser = new NodeTraverser([$visitor]);
$traverser->traverse([$constExprParser->parse($tokens)]);

foreach ($visitor->nodes as $node) {
$this->assertNotNull($node->getAttribute(Attribute::START_LINE), (string) $node);
$this->assertNotNull($node->getAttribute(Attribute::END_LINE), (string) $node);
$this->assertNotNull($node->getAttribute(Attribute::START_INDEX), (string) $node);
$this->assertNotNull($node->getAttribute(Attribute::END_INDEX), (string) $node);
}
}


public function provideTrueNodeParseData(): Iterator
{
yield [
Expand Down
21 changes: 21 additions & 0 deletions tests/PHPStan/Parser/NodeCollectingVisitor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php declare(strict_types = 1);

namespace PHPStan\PhpDocParser\Parser;

use PHPStan\PhpDocParser\Ast\AbstractNodeVisitor;
use PHPStan\PhpDocParser\Ast\Node;

class NodeCollectingVisitor extends AbstractNodeVisitor
{

/** @var list<Node> */
public $nodes = [];

public function enterNode(Node $node)
{
$this->nodes[] = $node;

return null;
}

}
4 changes: 2 additions & 2 deletions tests/PHPStan/Parser/PhpDocParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5620,11 +5620,11 @@ public function dataLinesAndIndexes(): iterable
public function testLinesAndIndexes(string $phpDoc, array $childrenLines): void
{
$tokens = new TokenIterator($this->lexer->tokenize($phpDoc));
$constExprParser = new ConstExprParser(true, true);
$usedAttributes = [
'lines' => true,
'indexes' => true,
];
$constExprParser = new ConstExprParser(true, true, $usedAttributes);
$typeParser = new TypeParser($constExprParser, true, $usedAttributes);
$phpDocParser = new PhpDocParser($typeParser, $constExprParser, true, true, $usedAttributes);
$phpDocNode = $phpDocParser->parse($tokens);
Expand Down Expand Up @@ -5691,11 +5691,11 @@ public function dataReturnTypeLinesAndIndexes(): iterable
public function testReturnTypeLinesAndIndexes(string $phpDoc, array $lines): void
{
$tokens = new TokenIterator($this->lexer->tokenize($phpDoc));
$constExprParser = new ConstExprParser(true, true);
$usedAttributes = [
'lines' => true,
'indexes' => true,
];
$constExprParser = new ConstExprParser(true, true, $usedAttributes);
$typeParser = new TypeParser($constExprParser, true, $usedAttributes);
$phpDocParser = new PhpDocParser($typeParser, $constExprParser, true, true, $usedAttributes);
$phpDocNode = $phpDocParser->parse($tokens);
Expand Down
5 changes: 3 additions & 2 deletions tests/PHPStan/Parser/TypeParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2179,10 +2179,11 @@ public function testLinesAndIndexes(string $input, array $assertions): void
{
$tokensArray = $this->lexer->tokenize($input);
$tokens = new TokenIterator($tokensArray);
$typeParser = new TypeParser(new ConstExprParser(true, true), true, [
$usedAttributes = [
'lines' => true,
'indexes' => true,
]);
];
$typeParser = new TypeParser(new ConstExprParser(true, true), true, $usedAttributes);
$typeNode = $typeParser->parse($tokens);

foreach ($assertions as [$callable, $expectedContent, $startLine, $endLine, $startIndex, $endIndex]) {
Expand Down

0 comments on commit 6220c55

Please sign in to comment.