Skip to content

Commit f9ecd17

Browse files
committed
Set start and end lines to node attributes
1 parent b508006 commit f9ecd17

11 files changed

+429
-84
lines changed

src/Ast/Attribute.php

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\PhpDocParser\Ast;
4+
5+
final class Attribute
6+
{
7+
8+
public const START_LINE = 'startLine';
9+
public const END_LINE = 'endLine';
10+
11+
}

src/Ast/PhpDoc/InvalidTagValueNode.php

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public function __construct(string $value, ParserException $exception)
3131
$exception->getCurrentOffset(),
3232
$exception->getExpectedTokenType(),
3333
$exception->getExpectedTokenValue(),
34+
$exception->getCurrentTokenLine(),
3435
];
3536
}
3637

src/Ast/Type/InvalidTypeNode.php

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public function __construct(ParserException $exception)
2121
$exception->getCurrentOffset(),
2222
$exception->getExpectedTokenType(),
2323
$exception->getExpectedTokenValue(),
24+
$exception->getCurrentTokenLine(),
2425
];
2526
}
2627

src/Lexer/Lexer.php

+11-3
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,13 @@ class Lexer
8888

8989
public const VALUE_OFFSET = 0;
9090
public const TYPE_OFFSET = 1;
91+
public const LINE_OFFSET = 2;
9192

9293
/** @var string|null */
9394
private $regexp;
9495

9596
/**
96-
* @return list<array{string, int}>
97+
* @return list<array{string, int, int}>
9798
*/
9899
public function tokenize(string $s): array
99100
{
@@ -104,11 +105,18 @@ public function tokenize(string $s): array
104105
preg_match_all($this->regexp, $s, $matches, PREG_SET_ORDER);
105106

106107
$tokens = [];
108+
$line = 1;
107109
foreach ($matches as $match) {
108-
$tokens[] = [$match[0], (int) $match['MARK']];
110+
$type = (int) $match['MARK'];
111+
$tokens[] = [$match[0], $type, $line];
112+
if ($type !== self::TOKEN_PHPDOC_EOL) {
113+
continue;
114+
}
115+
116+
$line++;
109117
}
110118

111-
$tokens[] = ['', self::TOKEN_END];
119+
$tokens[] = ['', self::TOKEN_END, $line];
112120

113121
return $tokens;
114122
}

src/Parser/ConstExprParser.php

+3-1
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,9 @@ public function parse(TokenIterator $tokens, bool $trimStrings = false): Ast\Con
120120
$tokens->currentTokenValue(),
121121
$tokens->currentTokenType(),
122122
$tokens->currentTokenOffset(),
123-
Lexer::TOKEN_IDENTIFIER
123+
Lexer::TOKEN_IDENTIFIER,
124+
null,
125+
$tokens->currentTokenLine()
124126
);
125127
}
126128

src/Parser/ParserException.php

+15-3
Original file line numberDiff line numberDiff line change
@@ -29,26 +29,32 @@ class ParserException extends Exception
2929
/** @var string|null */
3030
private $expectedTokenValue;
3131

32+
/** @var int|null */
33+
private $currentTokenLine;
34+
3235
public function __construct(
3336
string $currentTokenValue,
3437
int $currentTokenType,
3538
int $currentOffset,
3639
int $expectedTokenType,
37-
?string $expectedTokenValue = null
40+
?string $expectedTokenValue = null,
41+
?int $currentTokenLine = null
3842
)
3943
{
4044
$this->currentTokenValue = $currentTokenValue;
4145
$this->currentTokenType = $currentTokenType;
4246
$this->currentOffset = $currentOffset;
4347
$this->expectedTokenType = $expectedTokenType;
4448
$this->expectedTokenValue = $expectedTokenValue;
49+
$this->currentTokenLine = $currentTokenLine;
4550

4651
parent::__construct(sprintf(
47-
'Unexpected token %s, expected %s%s at offset %d',
52+
'Unexpected token %s, expected %s%s at offset %d%s',
4853
$this->formatValue($currentTokenValue),
4954
Lexer::TOKEN_LABELS[$expectedTokenType],
5055
$expectedTokenValue !== null ? sprintf(' (%s)', $this->formatValue($expectedTokenValue)) : '',
51-
$currentOffset
56+
$currentOffset,
57+
$currentTokenLine === null ? '' : sprintf(' on line %d', $currentTokenLine)
5258
));
5359
}
5460

@@ -83,6 +89,12 @@ public function getExpectedTokenValue(): ?string
8389
}
8490

8591

92+
public function getCurrentTokenLine(): ?int
93+
{
94+
return $this->currentTokenLine;
95+
}
96+
97+
8698
private function formatValue(string $value): string
8799
{
88100
$json = json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_SUBSTITUTE);

src/Parser/PhpDocParser.php

+45-4
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,25 @@ class PhpDocParser
3131
/** @var bool */
3232
private $preserveTypeAliasesWithInvalidTypes;
3333

34-
public function __construct(TypeParser $typeParser, ConstExprParser $constantExprParser, bool $requireWhitespaceBeforeDescription = false, bool $preserveTypeAliasesWithInvalidTypes = false)
34+
/** @var bool */
35+
private $useLinesAttributes;
36+
37+
/**
38+
* @param array{lines?: bool} $usedAttributes
39+
*/
40+
public function __construct(
41+
TypeParser $typeParser,
42+
ConstExprParser $constantExprParser,
43+
bool $requireWhitespaceBeforeDescription = false,
44+
bool $preserveTypeAliasesWithInvalidTypes = false,
45+
array $usedAttributes = []
46+
)
3547
{
3648
$this->typeParser = $typeParser;
3749
$this->constantExprParser = $constantExprParser;
3850
$this->requireWhitespaceBeforeDescription = $requireWhitespaceBeforeDescription;
3951
$this->preserveTypeAliasesWithInvalidTypes = $preserveTypeAliasesWithInvalidTypes;
52+
$this->useLinesAttributes = $usedAttributes['lines'] ?? false;
4053
}
4154

4255

@@ -77,11 +90,28 @@ public function parse(TokenIterator $tokens): Ast\PhpDoc\PhpDocNode
7790
private function parseChild(TokenIterator $tokens): Ast\PhpDoc\PhpDocChildNode
7891
{
7992
if ($tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_TAG)) {
80-
return $this->parseTag($tokens);
93+
$startLine = $tokens->currentTokenLine();
94+
$tag = $this->parseTag($tokens);
95+
$endLine = $tokens->currentTokenLine();
8196

97+
if ($this->useLinesAttributes) {
98+
$tag->setAttribute(Ast\Attribute::START_LINE, $startLine);
99+
$tag->setAttribute(Ast\Attribute::END_LINE, $endLine);
100+
}
101+
102+
return $tag;
82103
}
83104

84-
return $this->parseText($tokens);
105+
$startLine = $tokens->currentTokenLine();
106+
$text = $this->parseText($tokens);
107+
$endLine = $tokens->currentTokenLine();
108+
109+
if ($this->useLinesAttributes) {
110+
$text->setAttribute(Ast\Attribute::START_LINE, $startLine);
111+
$text->setAttribute(Ast\Attribute::END_LINE, $endLine);
112+
}
113+
114+
return $text;
85115
}
86116

87117

@@ -124,6 +154,8 @@ public function parseTag(TokenIterator $tokens): Ast\PhpDoc\PhpDocTagNode
124154

125155
public function parseTagValue(TokenIterator $tokens, string $tag): Ast\PhpDoc\PhpDocTagValueNode
126156
{
157+
$startLine = $tokens->currentTokenLine();
158+
127159
try {
128160
$tokens->pushSavePoint();
129161

@@ -251,6 +283,13 @@ public function parseTagValue(TokenIterator $tokens, string $tag): Ast\PhpDoc\Ph
251283
$tagValue = new Ast\PhpDoc\InvalidTagValueNode($this->parseOptionalDescription($tokens), $e);
252284
}
253285

286+
$endLine = $tokens->currentTokenLine();
287+
288+
if ($this->useLinesAttributes) {
289+
$tagValue->setAttribute(Ast\Attribute::START_LINE, $startLine);
290+
$tagValue->setAttribute(Ast\Attribute::END_LINE, $endLine);
291+
}
292+
254293
return $tagValue;
255294
}
256295

@@ -466,7 +505,9 @@ private function parseTypeAliasTagValue(TokenIterator $tokens): Ast\PhpDoc\TypeA
466505
$tokens->currentTokenValue(),
467506
$tokens->currentTokenType(),
468507
$tokens->currentTokenOffset(),
469-
Lexer::TOKEN_PHPDOC_EOL
508+
Lexer::TOKEN_PHPDOC_EOL,
509+
null,
510+
$tokens->currentTokenLine()
470511
);
471512
}
472513
}

src/Parser/TokenIterator.php

+10-3
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
class TokenIterator
1313
{
1414

15-
/** @var list<array{string, int}> */
15+
/** @var list<array{string, int, int}> */
1616
private $tokens;
1717

1818
/** @var int */
@@ -22,7 +22,7 @@ class TokenIterator
2222
private $savePoints = [];
2323

2424
/**
25-
* @param list<array{string, int}> $tokens
25+
* @param list<array{string, int, int}> $tokens
2626
*/
2727
public function __construct(array $tokens, int $index = 0)
2828
{
@@ -60,6 +60,12 @@ public function currentTokenOffset(): int
6060
}
6161

6262

63+
public function currentTokenLine(): int
64+
{
65+
return $this->tokens[$this->index][Lexer::LINE_OFFSET];
66+
}
67+
68+
6369
public function isCurrentTokenValue(string $tokenValue): bool
6470
{
6571
return $this->tokens[$this->index][Lexer::VALUE_OFFSET] === $tokenValue;
@@ -220,7 +226,8 @@ private function throwError(int $expectedTokenType, ?string $expectedTokenValue
220226
$this->currentTokenType(),
221227
$this->currentTokenOffset(),
222228
$expectedTokenType,
223-
$expectedTokenValue
229+
$expectedTokenValue,
230+
$this->currentTokenLine()
224231
);
225232
}
226233

src/Parser/TypeParser.php

+22-2
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,27 @@ class TypeParser
1818
/** @var bool */
1919
private $quoteAwareConstExprString;
2020

21-
public function __construct(?ConstExprParser $constExprParser = null, bool $quoteAwareConstExprString = false)
21+
/** @var bool */
22+
private $useLinesAttributes;
23+
24+
/**
25+
* @param array{lines?: bool} $usedAttributes
26+
*/
27+
public function __construct(
28+
?ConstExprParser $constExprParser = null,
29+
bool $quoteAwareConstExprString = false,
30+
array $usedAttributes = []
31+
)
2232
{
2333
$this->constExprParser = $constExprParser;
2434
$this->quoteAwareConstExprString = $quoteAwareConstExprString;
35+
$this->useLinesAttributes = $usedAttributes['lines'] ?? false;
2536
}
2637

2738
/** @phpstan-impure */
2839
public function parse(TokenIterator $tokens): Ast\Type\TypeNode
2940
{
41+
$startLine = $tokens->currentTokenLine();
3042
if ($tokens->isCurrentTokenType(Lexer::TOKEN_NULLABLE)) {
3143
$type = $this->parseNullable($tokens);
3244

@@ -40,6 +52,12 @@ public function parse(TokenIterator $tokens): Ast\Type\TypeNode
4052
$type = $this->parseIntersection($tokens, $type);
4153
}
4254
}
55+
$endLine = $tokens->currentTokenLine();
56+
57+
if ($this->useLinesAttributes) {
58+
$type->setAttribute(Ast\Attribute::START_LINE, $startLine);
59+
$type->setAttribute(Ast\Attribute::END_LINE, $endLine);
60+
}
4361

4462
return $type;
4563
}
@@ -152,7 +170,9 @@ private function parseAtomic(TokenIterator $tokens): Ast\Type\TypeNode
152170
$tokens->currentTokenValue(),
153171
$tokens->currentTokenType(),
154172
$tokens->currentTokenOffset(),
155-
Lexer::TOKEN_IDENTIFIER
173+
Lexer::TOKEN_IDENTIFIER,
174+
null,
175+
$tokens->currentTokenLine()
156176
);
157177

158178
if ($this->constExprParser === null) {

0 commit comments

Comments
 (0)