Skip to content

Commit fdb8bc4

Browse files
committed
Text between tags always belongs to description
1 parent 010d10a commit fdb8bc4

File tree

2 files changed

+101
-79
lines changed

2 files changed

+101
-79
lines changed

src/Parser/PhpDocParser.php

+54-32
Original file line numberDiff line numberDiff line change
@@ -39,24 +39,20 @@ class PhpDocParser
3939

4040
private bool $useIndexAttributes;
4141

42-
private bool $textBetweenTagsBelongsToDescription;
43-
4442
/**
4543
* @param array{lines?: bool, indexes?: bool} $usedAttributes
4644
*/
4745
public function __construct(
4846
TypeParser $typeParser,
4947
ConstExprParser $constantExprParser,
50-
array $usedAttributes = [],
51-
bool $textBetweenTagsBelongsToDescription = false
48+
array $usedAttributes = []
5249
)
5350
{
5451
$this->typeParser = $typeParser;
5552
$this->constantExprParser = $constantExprParser;
5653
$this->doctrineConstantExprParser = $constantExprParser->toDoctrine();
5754
$this->useLinesAttributes = $usedAttributes['lines'] ?? false;
5855
$this->useIndexAttributes = $usedAttributes['indexes'] ?? false;
59-
$this->textBetweenTagsBelongsToDescription = $textBetweenTagsBelongsToDescription;
6056
}
6157

6258

@@ -164,7 +160,7 @@ private function parseChild(TokenIterator $tokens): Ast\PhpDoc\PhpDocChildNode
164160

165161
$startLine = $tokens->currentTokenLine();
166162
$startIndex = $tokens->currentTokenIndex();
167-
$text = $this->parseText($tokens);
163+
$text = $this->parsePhpDocTextNode($tokens);
168164

169165
return $this->enrichWithAttributes($tokens, $text, $startLine, $startIndex);
170166
}
@@ -194,15 +190,12 @@ private function parseText(TokenIterator $tokens): Ast\PhpDoc\PhpDocTextNode
194190
{
195191
$text = '';
196192

197-
$endTokens = [Lexer::TOKEN_PHPDOC_EOL, Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END];
198-
if ($this->textBetweenTagsBelongsToDescription) {
199-
$endTokens = [Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END];
200-
}
193+
$endTokens = [Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END];
201194

202195
$savepoint = false;
203196

204197
// if the next token is EOL, everything below is skipped and empty string is returned
205-
while ($this->textBetweenTagsBelongsToDescription || !$tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL)) {
198+
while (true) {
206199
$tmpText = $tokens->getSkippedHorizontalWhiteSpaceIfAny() . $tokens->joinUntil(Lexer::TOKEN_PHPDOC_EOL, ...$endTokens);
207200
$text .= $tmpText;
208201

@@ -211,14 +204,12 @@ private function parseText(TokenIterator $tokens): Ast\PhpDoc\PhpDocTextNode
211204
break;
212205
}
213206

214-
if ($this->textBetweenTagsBelongsToDescription) {
215-
if (!$savepoint) {
216-
$tokens->pushSavePoint();
217-
$savepoint = true;
218-
} elseif ($tmpText !== '') {
219-
$tokens->dropSavePoint();
220-
$tokens->pushSavePoint();
221-
}
207+
if (!$savepoint) {
208+
$tokens->pushSavePoint();
209+
$savepoint = true;
210+
} elseif ($tmpText !== '') {
211+
$tokens->dropSavePoint();
212+
$tokens->pushSavePoint();
222213
}
223214

224215
$tokens->pushSavePoint();
@@ -250,15 +241,12 @@ private function parseOptionalDescriptionAfterDoctrineTag(TokenIterator $tokens)
250241
{
251242
$text = '';
252243

253-
$endTokens = [Lexer::TOKEN_PHPDOC_EOL, Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END];
254-
if ($this->textBetweenTagsBelongsToDescription) {
255-
$endTokens = [Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END];
256-
}
244+
$endTokens = [Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END];
257245

258246
$savepoint = false;
259247

260248
// if the next token is EOL, everything below is skipped and empty string is returned
261-
while ($this->textBetweenTagsBelongsToDescription || !$tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL)) {
249+
while (true) {
262250
$tmpText = $tokens->getSkippedHorizontalWhiteSpaceIfAny() . $tokens->joinUntil(Lexer::TOKEN_PHPDOC_TAG, Lexer::TOKEN_DOCTRINE_TAG, Lexer::TOKEN_PHPDOC_EOL, ...$endTokens);
263251
$text .= $tmpText;
264252

@@ -297,14 +285,12 @@ private function parseOptionalDescriptionAfterDoctrineTag(TokenIterator $tokens)
297285
break;
298286
}
299287

300-
if ($this->textBetweenTagsBelongsToDescription) {
301-
if (!$savepoint) {
302-
$tokens->pushSavePoint();
303-
$savepoint = true;
304-
} elseif ($tmpText !== '') {
305-
$tokens->dropSavePoint();
306-
$tokens->pushSavePoint();
307-
}
288+
if (!$savepoint) {
289+
$tokens->pushSavePoint();
290+
$savepoint = true;
291+
} elseif ($tmpText !== '') {
292+
$tokens->dropSavePoint();
293+
$tokens->pushSavePoint();
308294
}
309295

310296
$tokens->pushSavePoint();
@@ -332,6 +318,42 @@ private function parseOptionalDescriptionAfterDoctrineTag(TokenIterator $tokens)
332318
}
333319

334320

321+
private function parsePhpDocTextNode(TokenIterator $tokens): Ast\PhpDoc\PhpDocTextNode
322+
{
323+
$text = '';
324+
325+
$endTokens = [Lexer::TOKEN_PHPDOC_EOL, Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END];
326+
327+
// if the next token is EOL, everything below is skipped and empty string is returned
328+
while (!$tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL)) {
329+
$tmpText = $tokens->getSkippedHorizontalWhiteSpaceIfAny() . $tokens->joinUntil(Lexer::TOKEN_PHPDOC_EOL, ...$endTokens);
330+
$text .= $tmpText;
331+
332+
// stop if we're not at EOL - meaning it's the end of PHPDoc
333+
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL, Lexer::TOKEN_CLOSE_PHPDOC)) {
334+
break;
335+
}
336+
337+
$tokens->pushSavePoint();
338+
$tokens->next();
339+
340+
// if we're at EOL, check what's next
341+
// if next is a PHPDoc tag, EOL, or end of PHPDoc, stop
342+
if ($tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_TAG, Lexer::TOKEN_DOCTRINE_TAG, ...$endTokens)) {
343+
$tokens->rollback();
344+
break;
345+
}
346+
347+
// otherwise if the next is text, continue building the description string
348+
349+
$tokens->dropSavePoint();
350+
$text .= $tokens->getDetectedNewline() ?? "\n";
351+
}
352+
353+
return new Ast\PhpDoc\PhpDocTextNode(trim($text, " \t"));
354+
}
355+
356+
335357
public function parseTag(TokenIterator $tokens): Ast\PhpDoc\PhpDocTagNode
336358
{
337359
$tag = $tokens->currentTokenValue();

tests/PHPStan/Parser/PhpDocParserTest.php

+47-47
Original file line numberDiff line numberDiff line change
@@ -2801,11 +2801,9 @@ public function provideSingleLinePhpDocData(): Iterator
28012801
new PhpDocNode([
28022802
new PhpDocTagNode(
28032803
'@example',
2804-
new GenericTagValueNode(''),
2805-
),
2806-
new PhpDocTextNode(
2807-
'entity_managers:' . PHP_EOL .
2808-
' default:',
2804+
new GenericTagValueNode(PHP_EOL .
2805+
' entity_managers:' . PHP_EOL .
2806+
' default:'),
28092807
),
28102808
]),
28112809
];
@@ -2955,26 +2953,27 @@ public function provideMultiLinePhpDocData(): iterable
29552953
new IdentifierTypeNode('Foo'),
29562954
false,
29572955
'$foo',
2958-
'1st multi world description with empty lines',
2956+
'1st multi world description with empty lines' . PHP_EOL .
2957+
PHP_EOL .
2958+
PHP_EOL .
2959+
'some text in the middle',
29592960
),
29602961
),
29612962
new PhpDocTextNode(''),
29622963
new PhpDocTextNode(''),
2963-
new PhpDocTextNode('some text in the middle'),
2964-
new PhpDocTextNode(''),
2965-
new PhpDocTextNode(''),
29662964
new PhpDocTagNode(
29672965
'@param',
29682966
new ParamTagValueNode(
29692967
new IdentifierTypeNode('Bar'),
29702968
false,
29712969
'$bar',
2972-
'2nd multi world description with empty lines',
2970+
'2nd multi world description with empty lines' . PHP_EOL .
2971+
PHP_EOL .
2972+
PHP_EOL .
2973+
'test',
29732974
),
29742975
),
2975-
new PhpDocTextNode(''),
2976-
new PhpDocTextNode(''),
2977-
new PhpDocTextNode('test'),
2976+
29782977
]),
29792978
],
29802979
[
@@ -3803,10 +3802,18 @@ public function provideMultiLinePhpDocData(): iterable
38033802
' */',
38043803
new PhpDocNode([
38053804
new PhpDocTextNode('Real description'),
3806-
new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('int'), false, '$a', '')),
3807-
new PhpDocTextNode(''),
3808-
new PhpDocTextNode(''),
3809-
new PhpDocTextNode('test'),
3805+
new PhpDocTagNode(
3806+
'@param',
3807+
new ParamTagValueNode(
3808+
new IdentifierTypeNode('int'),
3809+
false,
3810+
'$a',
3811+
PHP_EOL
3812+
. PHP_EOL
3813+
. PHP_EOL
3814+
. 'test',
3815+
),
3816+
),
38103817
]),
38113818
];
38123819
}
@@ -4941,19 +4948,15 @@ public function provideRealWorldExampleData(): Iterator
49414948
new IdentifierTypeNode('\Drupal\Core\Field\FieldStorageDefinitionInterface'),
49424949
false,
49434950
'$field_definition',
4944-
'',
4951+
PHP_EOL . ' The field definition.',
49454952
),
49464953
),
4947-
new PhpDocTextNode('The field definition.'),
49484954
new PhpDocTextNode(''),
49494955
new PhpDocTagNode(
49504956
'@return',
49514957
new ReturnTagValueNode(
49524958
new IdentifierTypeNode('array'),
4953-
'',
4954-
),
4955-
),
4956-
new PhpDocTextNode("An empty array if there is no schema, or an associative array with the
4959+
PHP_EOL . " An empty array if there is no schema, or an associative array with the
49574960
following key/value pairs:
49584961
- columns: An array of Schema API column specifications, keyed by column
49594962
name. The columns need to be a subset of the properties defined in
@@ -4975,7 +4978,9 @@ public function provideRealWorldExampleData(): Iterator
49754978
definitions. Note, however, that the field data is not necessarily
49764979
stored in SQL. Also, the possible usage is limited, as you cannot
49774980
specify another field as related, only existing SQL tables,
4978-
such as {taxonomy_term_data}."),
4981+
such as {taxonomy_term_data}.",
4982+
),
4983+
),
49794984
]),
49804985
];
49814986

@@ -5693,9 +5698,8 @@ public function provideCommentLikeDescriptions(): Iterator
56935698
new IdentifierTypeNode('int'),
56945699
false,
56955700
'$a',
5696-
'',
5701+
PHP_EOL . '// this is a comment',
56975702
)),
5698-
new PhpDocTextNode('// this is a comment'),
56995703
]),
57005704
];
57015705
yield [
@@ -5710,10 +5714,8 @@ public function provideCommentLikeDescriptions(): Iterator
57105714
new IdentifierTypeNode('int'),
57115715
false,
57125716
'$a',
5713-
'',
5717+
PHP_EOL . PHP_EOL . '// this is a comment',
57145718
)),
5715-
new PhpDocTextNode(''),
5716-
new PhpDocTextNode('// this is a comment'),
57175719
]),
57185720
];
57195721
yield [
@@ -5918,10 +5920,10 @@ public function provideDoctrineData(): Iterator
59185920
'@X',
59195921
new DoctrineTagValueNode(
59205922
new DoctrineAnnotation('@X', []),
5921-
'',
5923+
PHP_EOL .
5924+
'Content',
59225925
),
59235926
),
5924-
new PhpDocTextNode('Content'),
59255927
]),
59265928
[new Doctrine\X()],
59275929
];
@@ -6075,9 +6077,8 @@ public function provideDoctrineData(): Iterator
60756077
new PhpDocTagNode('@X', new DoctrineTagValueNode(new DoctrineAnnotation(
60766078
'@X',
60776079
[],
6078-
), 'test')),
6079-
new PhpDocTextNode(''),
6080-
new PhpDocTextNode('test2'),
6080+
), 'test' . PHP_EOL .
6081+
PHP_EOL . 'test2')),
60816082
]),
60826083
[new Doctrine\X()],
60836084
];
@@ -6093,9 +6094,8 @@ public function provideDoctrineData(): Iterator
60936094
new PhpDocTagNode('@X', new DoctrineTagValueNode(new DoctrineAnnotation(
60946095
'@X',
60956096
[],
6096-
), 'test')),
6097-
new PhpDocTextNode(''),
6098-
new PhpDocTextNode('test2'),
6097+
), 'test' . PHP_EOL .
6098+
PHP_EOL . 'test2')),
60996099
new PhpDocTagNode('@Z', new DoctrineTagValueNode(new DoctrineAnnotation(
61006100
'@Z',
61016101
[],
@@ -6134,9 +6134,9 @@ public function provideDoctrineData(): Iterator
61346134
' * test2' . PHP_EOL .
61356135
' */',
61366136
new PhpDocNode([
6137-
new PhpDocTagNode('@X', new GenericTagValueNode('test')),
6138-
new PhpDocTextNode(''),
6139-
new PhpDocTextNode('test2'),
6137+
new PhpDocTagNode('@X', new GenericTagValueNode('test' . PHP_EOL .
6138+
PHP_EOL .
6139+
'test2')),
61406140
]),
61416141
[new Doctrine\X()],
61426142
];
@@ -6149,9 +6149,9 @@ public function provideDoctrineData(): Iterator
61496149
' * @Z' . PHP_EOL .
61506150
' */',
61516151
new PhpDocNode([
6152-
new PhpDocTagNode('@X', new GenericTagValueNode('test')),
6153-
new PhpDocTextNode(''),
6154-
new PhpDocTextNode('test2'),
6152+
new PhpDocTagNode('@X', new GenericTagValueNode('test' . PHP_EOL .
6153+
PHP_EOL .
6154+
'test2')),
61556155
new PhpDocTagNode('@Z', new GenericTagValueNode('')),
61566156
]),
61576157
[new Doctrine\X(), new Doctrine\Z()],
@@ -7309,7 +7309,7 @@ public function dataTextBetweenTagsBelongsToDescription(): iterable
73097309
new DeprecatedTagValueNode('in Drupal 8.6.0 and will be removed before Drupal 9.0.0. In
73107310
Drupal 9 there will be no way to set the status and in Drupal 8 this
73117311
ability has been removed because mb_*() functions are supplied using
7312-
Symfony\'s polyfill.')
7312+
Symfony\'s polyfill.'),
73137313
),
73147314
]),
73157315
];
@@ -7327,8 +7327,8 @@ public function dataTextBetweenTagsBelongsToDescription(): iterable
73277327
'in Drupal 8.6.0 and will be removed before Drupal 9.0.0. In
73287328
Drupal 9 there will be no way to set the status and in Drupal 8 this
73297329
ability has been removed because mb_*() functions are supplied using
7330-
Symfony\'s polyfill.'
7331-
)
7330+
Symfony\'s polyfill.',
7331+
),
73327332
),
73337333
]),
73347334
];
@@ -7344,7 +7344,7 @@ public function testTextBetweenTagsBelongsToDescription(
73447344
{
73457345
$constExprParser = new ConstExprParser();
73467346
$typeParser = new TypeParser($constExprParser);
7347-
$phpDocParser = new PhpDocParser($typeParser, $constExprParser, [], true);
7347+
$phpDocParser = new PhpDocParser($typeParser, $constExprParser, []);
73487348

73497349
$tokens = new TokenIterator($this->lexer->tokenize($input));
73507350
$actualPhpDocNode = $phpDocParser->parse($tokens);

0 commit comments

Comments
 (0)