Skip to content

Commit 6220c55

Browse files
committed
ConstExprParser - attributes
1 parent b5fede3 commit 6220c55

File tree

5 files changed

+184
-20
lines changed

5 files changed

+184
-20
lines changed

src/Parser/ConstExprParser.php

+124-16
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,53 @@ class ConstExprParser
1616
/** @var bool */
1717
private $quoteAwareConstExprString;
1818

19-
public function __construct(bool $unescapeStrings = false, bool $quoteAwareConstExprString = false)
19+
/** @var bool */
20+
private $useLinesAttributes;
21+
22+
/** @var bool */
23+
private $useIndexAttributes;
24+
25+
/**
26+
* @param array{lines?: bool, indexes?: bool} $usedAttributes
27+
*/
28+
public function __construct(
29+
bool $unescapeStrings = false,
30+
bool $quoteAwareConstExprString = false,
31+
array $usedAttributes = []
32+
)
2033
{
2134
$this->unescapeStrings = $unescapeStrings;
2235
$this->quoteAwareConstExprString = $quoteAwareConstExprString;
36+
$this->useLinesAttributes = $usedAttributes['lines'] ?? false;
37+
$this->useIndexAttributes = $usedAttributes['indexes'] ?? false;
2338
}
2439

2540
public function parse(TokenIterator $tokens, bool $trimStrings = false): Ast\ConstExpr\ConstExprNode
2641
{
42+
$startLine = $tokens->currentTokenLine();
43+
$startIndex = $tokens->currentTokenIndex();
2744
if ($tokens->isCurrentTokenType(Lexer::TOKEN_FLOAT)) {
2845
$value = $tokens->currentTokenValue();
2946
$tokens->next();
30-
return new Ast\ConstExpr\ConstExprFloatNode($value);
47+
48+
return $this->enrichWithAttributes(
49+
$tokens,
50+
new Ast\ConstExpr\ConstExprFloatNode($value),
51+
$startLine,
52+
$startIndex
53+
);
3154
}
3255

3356
if ($tokens->isCurrentTokenType(Lexer::TOKEN_INTEGER)) {
3457
$value = $tokens->currentTokenValue();
3558
$tokens->next();
36-
return new Ast\ConstExpr\ConstExprIntegerNode($value);
59+
60+
return $this->enrichWithAttributes(
61+
$tokens,
62+
new Ast\ConstExpr\ConstExprIntegerNode($value),
63+
$startLine,
64+
$startIndex
65+
);
3766
}
3867

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

5180
if ($this->quoteAwareConstExprString) {
52-
return new Ast\ConstExpr\QuoteAwareConstExprStringNode(
53-
$value,
54-
$type === Lexer::TOKEN_SINGLE_QUOTED_STRING
55-
? Ast\ConstExpr\QuoteAwareConstExprStringNode::SINGLE_QUOTED
56-
: Ast\ConstExpr\QuoteAwareConstExprStringNode::DOUBLE_QUOTED
81+
return $this->enrichWithAttributes(
82+
$tokens,
83+
new Ast\ConstExpr\QuoteAwareConstExprStringNode(
84+
$value,
85+
$type === Lexer::TOKEN_SINGLE_QUOTED_STRING
86+
? Ast\ConstExpr\QuoteAwareConstExprStringNode::SINGLE_QUOTED
87+
: Ast\ConstExpr\QuoteAwareConstExprStringNode::DOUBLE_QUOTED
88+
),
89+
$startLine,
90+
$startIndex
5791
);
5892
}
5993

60-
return new Ast\ConstExpr\ConstExprStringNode($value);
94+
return $this->enrichWithAttributes(
95+
$tokens,
96+
new Ast\ConstExpr\ConstExprStringNode($value),
97+
$startLine,
98+
$startIndex
99+
);
61100

62101
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_IDENTIFIER)) {
63102
$identifier = $tokens->currentTokenValue();
64103
$tokens->next();
65104

66105
switch (strtolower($identifier)) {
67106
case 'true':
68-
return new Ast\ConstExpr\ConstExprTrueNode();
107+
return $this->enrichWithAttributes(
108+
$tokens,
109+
new Ast\ConstExpr\ConstExprTrueNode(),
110+
$startLine,
111+
$startIndex
112+
);
69113
case 'false':
70-
return new Ast\ConstExpr\ConstExprFalseNode();
114+
return $this->enrichWithAttributes(
115+
$tokens,
116+
new Ast\ConstExpr\ConstExprFalseNode(),
117+
$startLine,
118+
$startIndex
119+
);
71120
case 'null':
72-
return new Ast\ConstExpr\ConstExprNullNode();
121+
return $this->enrichWithAttributes(
122+
$tokens,
123+
new Ast\ConstExpr\ConstExprNullNode(),
124+
$startLine,
125+
$startIndex
126+
);
73127
case 'array':
74128
$tokens->consumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES);
75129
return $this->parseArray($tokens, Lexer::TOKEN_CLOSE_PARENTHESES);
@@ -106,11 +160,21 @@ public function parse(TokenIterator $tokens, bool $trimStrings = false): Ast\Con
106160
break;
107161
}
108162

109-
return new Ast\ConstExpr\ConstFetchNode($identifier, $classConstantName);
163+
return $this->enrichWithAttributes(
164+
$tokens,
165+
new Ast\ConstExpr\ConstFetchNode($identifier, $classConstantName),
166+
$startLine,
167+
$startIndex
168+
);
110169

111170
}
112171

113-
return new Ast\ConstExpr\ConstFetchNode('', $identifier);
172+
return $this->enrichWithAttributes(
173+
$tokens,
174+
new Ast\ConstExpr\ConstFetchNode('', $identifier),
175+
$startLine,
176+
$startIndex
177+
);
114178

115179
} elseif ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
116180
return $this->parseArray($tokens, Lexer::TOKEN_CLOSE_SQUARE_BRACKET);
@@ -131,19 +195,30 @@ private function parseArray(TokenIterator $tokens, int $endToken): Ast\ConstExpr
131195
{
132196
$items = [];
133197

198+
$startLine = $tokens->currentTokenLine();
199+
$startIndex = $tokens->currentTokenIndex();
200+
134201
if (!$tokens->tryConsumeTokenType($endToken)) {
135202
do {
136203
$items[] = $this->parseArrayItem($tokens);
137204
} while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA) && !$tokens->isCurrentTokenType($endToken));
138205
$tokens->consumeTokenType($endToken);
139206
}
140207

141-
return new Ast\ConstExpr\ConstExprArrayNode($items);
208+
return $this->enrichWithAttributes(
209+
$tokens,
210+
new Ast\ConstExpr\ConstExprArrayNode($items),
211+
$startLine,
212+
$startIndex
213+
);
142214
}
143215

144216

145217
private function parseArrayItem(TokenIterator $tokens): Ast\ConstExpr\ConstExprArrayItemNode
146218
{
219+
$startLine = $tokens->currentTokenLine();
220+
$startIndex = $tokens->currentTokenIndex();
221+
147222
$expr = $this->parse($tokens);
148223

149224
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_DOUBLE_ARROW)) {
@@ -155,7 +230,40 @@ private function parseArrayItem(TokenIterator $tokens): Ast\ConstExpr\ConstExprA
155230
$value = $expr;
156231
}
157232

158-
return new Ast\ConstExpr\ConstExprArrayItemNode($key, $value);
233+
return $this->enrichWithAttributes(
234+
$tokens,
235+
new Ast\ConstExpr\ConstExprArrayItemNode($key, $value),
236+
$startLine,
237+
$startIndex
238+
);
239+
}
240+
241+
/**
242+
* @template T of Ast\ConstExpr\ConstExprNode
243+
* @param T $node
244+
* @return T
245+
*/
246+
private function enrichWithAttributes(TokenIterator $tokens, Ast\ConstExpr\ConstExprNode $node, int $startLine, int $startIndex): Ast\ConstExpr\ConstExprNode
247+
{
248+
$endLine = $tokens->currentTokenLine();
249+
$endIndex = $tokens->currentTokenIndex();
250+
if ($this->useLinesAttributes) {
251+
$node->setAttribute(Ast\Attribute::START_LINE, $startLine);
252+
$node->setAttribute(Ast\Attribute::END_LINE, $endLine);
253+
}
254+
255+
if ($this->useIndexAttributes) {
256+
$tokensArray = $tokens->getTokens();
257+
$endIndex--;
258+
if ($tokensArray[$endIndex][Lexer::TYPE_OFFSET] === Lexer::TOKEN_HORIZONTAL_WS) {
259+
$endIndex--;
260+
}
261+
262+
$node->setAttribute(Ast\Attribute::START_INDEX, $startIndex);
263+
$node->setAttribute(Ast\Attribute::END_INDEX, $endIndex);
264+
}
265+
266+
return $node;
159267
}
160268

161269
}

tests/PHPStan/Parser/ConstExprParserTest.php

+34
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace PHPStan\PhpDocParser\Parser;
44

55
use Iterator;
6+
use PHPStan\PhpDocParser\Ast\Attribute;
67
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprArrayItemNode;
78
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprArrayNode;
89
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprFalseNode;
@@ -13,6 +14,7 @@
1314
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode;
1415
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprTrueNode;
1516
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode;
17+
use PHPStan\PhpDocParser\Ast\NodeTraverser;
1618
use PHPStan\PhpDocParser\Lexer\Lexer;
1719
use PHPUnit\Framework\TestCase;
1820

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

5658

59+
/**
60+
* @dataProvider provideTrueNodeParseData
61+
* @dataProvider provideFalseNodeParseData
62+
* @dataProvider provideNullNodeParseData
63+
* @dataProvider provideIntegerNodeParseData
64+
* @dataProvider provideFloatNodeParseData
65+
* @dataProvider provideStringNodeParseData
66+
* @dataProvider provideArrayNodeParseData
67+
* @dataProvider provideFetchNodeParseData
68+
*
69+
* @dataProvider provideWithTrimStringsStringNodeParseData
70+
*/
71+
public function testVerifyAttributes(string $input): void
72+
{
73+
$tokens = new TokenIterator($this->lexer->tokenize($input));
74+
$constExprParser = new ConstExprParser(true, true, [
75+
'lines' => true,
76+
'indexes' => true,
77+
]);
78+
$visitor = new NodeCollectingVisitor();
79+
$traverser = new NodeTraverser([$visitor]);
80+
$traverser->traverse([$constExprParser->parse($tokens)]);
81+
82+
foreach ($visitor->nodes as $node) {
83+
$this->assertNotNull($node->getAttribute(Attribute::START_LINE), (string) $node);
84+
$this->assertNotNull($node->getAttribute(Attribute::END_LINE), (string) $node);
85+
$this->assertNotNull($node->getAttribute(Attribute::START_INDEX), (string) $node);
86+
$this->assertNotNull($node->getAttribute(Attribute::END_INDEX), (string) $node);
87+
}
88+
}
89+
90+
5791
public function provideTrueNodeParseData(): Iterator
5892
{
5993
yield [
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\PhpDocParser\Parser;
4+
5+
use PHPStan\PhpDocParser\Ast\AbstractNodeVisitor;
6+
use PHPStan\PhpDocParser\Ast\Node;
7+
8+
class NodeCollectingVisitor extends AbstractNodeVisitor
9+
{
10+
11+
/** @var list<Node> */
12+
public $nodes = [];
13+
14+
public function enterNode(Node $node)
15+
{
16+
$this->nodes[] = $node;
17+
18+
return null;
19+
}
20+
21+
}

tests/PHPStan/Parser/PhpDocParserTest.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -5620,11 +5620,11 @@ public function dataLinesAndIndexes(): iterable
56205620
public function testLinesAndIndexes(string $phpDoc, array $childrenLines): void
56215621
{
56225622
$tokens = new TokenIterator($this->lexer->tokenize($phpDoc));
5623-
$constExprParser = new ConstExprParser(true, true);
56245623
$usedAttributes = [
56255624
'lines' => true,
56265625
'indexes' => true,
56275626
];
5627+
$constExprParser = new ConstExprParser(true, true, $usedAttributes);
56285628
$typeParser = new TypeParser($constExprParser, true, $usedAttributes);
56295629
$phpDocParser = new PhpDocParser($typeParser, $constExprParser, true, true, $usedAttributes);
56305630
$phpDocNode = $phpDocParser->parse($tokens);
@@ -5691,11 +5691,11 @@ public function dataReturnTypeLinesAndIndexes(): iterable
56915691
public function testReturnTypeLinesAndIndexes(string $phpDoc, array $lines): void
56925692
{
56935693
$tokens = new TokenIterator($this->lexer->tokenize($phpDoc));
5694-
$constExprParser = new ConstExprParser(true, true);
56955694
$usedAttributes = [
56965695
'lines' => true,
56975696
'indexes' => true,
56985697
];
5698+
$constExprParser = new ConstExprParser(true, true, $usedAttributes);
56995699
$typeParser = new TypeParser($constExprParser, true, $usedAttributes);
57005700
$phpDocParser = new PhpDocParser($typeParser, $constExprParser, true, true, $usedAttributes);
57015701
$phpDocNode = $phpDocParser->parse($tokens);

tests/PHPStan/Parser/TypeParserTest.php

+3-2
Original file line numberDiff line numberDiff line change
@@ -2179,10 +2179,11 @@ public function testLinesAndIndexes(string $input, array $assertions): void
21792179
{
21802180
$tokensArray = $this->lexer->tokenize($input);
21812181
$tokens = new TokenIterator($tokensArray);
2182-
$typeParser = new TypeParser(new ConstExprParser(true, true), true, [
2182+
$usedAttributes = [
21832183
'lines' => true,
21842184
'indexes' => true,
2185-
]);
2185+
];
2186+
$typeParser = new TypeParser(new ConstExprParser(true, true), true, $usedAttributes);
21862187
$typeNode = $typeParser->parse($tokens);
21872188

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

0 commit comments

Comments
 (0)