Skip to content

Commit

Permalink
New option to attach text between tags as description to the tag above
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Jul 23, 2023
1 parent f9311f0 commit 5164f16
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 5 deletions.
21 changes: 16 additions & 5 deletions src/Parser/PhpDocParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ class PhpDocParser
/** @var bool */
private $useIndexAttributes;

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

/**
* @param array{lines?: bool, indexes?: bool} $usedAttributes
*/
Expand All @@ -58,7 +61,8 @@ public function __construct(
bool $requireWhitespaceBeforeDescription = false,
bool $preserveTypeAliasesWithInvalidTypes = false,
array $usedAttributes = [],
bool $parseDoctrineAnnotations = false
bool $parseDoctrineAnnotations = false,
bool $textBetweenTagsBelongsToDescription = false
)
{
$this->typeParser = $typeParser;
Expand All @@ -68,6 +72,7 @@ public function __construct(
$this->parseDoctrineAnnotations = $parseDoctrineAnnotations;
$this->useLinesAttributes = $usedAttributes['lines'] ?? false;
$this->useIndexAttributes = $usedAttributes['indexes'] ?? false;
$this->textBetweenTagsBelongsToDescription = $textBetweenTagsBelongsToDescription;
}


Expand Down Expand Up @@ -215,10 +220,13 @@ 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];
}

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

// stop if we're not at EOL - meaning it's the end of PHPDoc
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL)) {
Expand Down Expand Up @@ -250,10 +258,13 @@ private function parseOptionalDescriptionAfterDoctrineTag(TokenIterator $tokens)
$text = '';

$endTokens = [Lexer::TOKEN_PHPDOC_EOL, Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END];
if ($this->textBetweenTagsBelongsToDescription) {
$endTokens = [Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END];
}

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

// stop if we're not at EOL - meaning it's the end of PHPDoc
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL)) {
Expand Down
99 changes: 99 additions & 0 deletions tests/PHPStan/Parser/PhpDocParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6682,4 +6682,103 @@ public function testDoctrine(
$this->assertEquals($expectedAnnotations, $parser->parse($input, $label), $label);
}

/**
* @return iterable<array{string, PhpDocNode}>
*/
public function dataTextBetweenTagsBelongsToDescription(): iterable
{
yield [
'/**' . PHP_EOL .
' * Real description' . PHP_EOL .
' * @param int $a' . PHP_EOL .
' * paramA description' . PHP_EOL .
' * @param int $b' . PHP_EOL .
' * paramB description' . PHP_EOL .
' */',
new PhpDocNode([
new PhpDocTextNode('Real description'),
new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('int'), false, '$a', PHP_EOL . ' paramA description')),
new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('int'), false, '$b', PHP_EOL . ' paramB description')),
]),
];

yield [
'/**' . PHP_EOL .
' * Real description' . PHP_EOL .
' * @param int $a aaaa' . PHP_EOL .
' * bbbb' . PHP_EOL .
' *' . PHP_EOL .
' * ccc' . PHP_EOL .
' */',
new PhpDocNode([
new PhpDocTextNode('Real description'),
new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('int'), false, '$a', 'aaaa' . PHP_EOL . ' bbbb' . PHP_EOL . PHP_EOL . 'ccc')),
]),
];

yield [
'/**' . PHP_EOL .
' * Real description' . PHP_EOL .
' * @ORM\Column()' . PHP_EOL .
' * bbbb' . PHP_EOL .
' *' . PHP_EOL .
' * ccc' . PHP_EOL .
' */',
new PhpDocNode([
new PhpDocTextNode('Real description'),
new PhpDocTagNode('@ORM\Column', new DoctrineTagValueNode(new DoctrineAnnotation('@ORM\Column', []), PHP_EOL . ' bbbb' . PHP_EOL . PHP_EOL . 'ccc')),
]),
];

yield [
'/**' . PHP_EOL .
' * Real description' . PHP_EOL .
' * @ORM\Column() aaaa' . PHP_EOL .
' * bbbb' . PHP_EOL .
' *' . PHP_EOL .
' * ccc' . PHP_EOL .
' */',
new PhpDocNode([
new PhpDocTextNode('Real description'),
new PhpDocTagNode('@ORM\Column', new DoctrineTagValueNode(new DoctrineAnnotation('@ORM\Column', []), 'aaaa' . PHP_EOL . ' bbbb' . PHP_EOL . PHP_EOL . 'ccc')),
]),
];

yield [
'/**' . PHP_EOL .
' * Real description' . PHP_EOL .
' * @ORM\Column() aaaa' . PHP_EOL .
' * bbbb' . PHP_EOL .
' *' . PHP_EOL .
' * ccc' . PHP_EOL .
' * @param int $b' . PHP_EOL .
' */',
new PhpDocNode([
new PhpDocTextNode('Real description'),
new PhpDocTagNode('@ORM\Column', new DoctrineTagValueNode(new DoctrineAnnotation('@ORM\Column', []), 'aaaa' . PHP_EOL . ' bbbb' . PHP_EOL . PHP_EOL . 'ccc')),
new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('int'), false, '$b', '')),
]),
];
}

/**
* @dataProvider dataTextBetweenTagsBelongsToDescription
*/
public function testTextBetweenTagsBelongsToDescription(
string $input,
PhpDocNode $expectedPhpDocNode
): void
{
$constExprParser = new ConstExprParser();
$typeParser = new TypeParser($constExprParser);
$phpDocParser = new PhpDocParser($typeParser, $constExprParser, true, true, [], true, true);

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

$this->assertEquals($expectedPhpDocNode, $actualPhpDocNode);
$this->assertSame((string) $expectedPhpDocNode, (string) $actualPhpDocNode);
$this->assertSame(Lexer::TOKEN_END, $tokens->currentTokenType());
}

}

0 comments on commit 5164f16

Please sign in to comment.