diff --git a/psalm.xml b/psalm.xml
index 7f491f7f..d4b47bfa 100644
--- a/psalm.xml
+++ b/psalm.xml
@@ -44,6 +44,7 @@
+
diff --git a/src/DocBlock/Tags/Factory/AbstractPHPStanFactory.php b/src/DocBlock/Tags/Factory/AbstractPHPStanFactory.php
index 7a5f8e2c..2c3b43f7 100644
--- a/src/DocBlock/Tags/Factory/AbstractPHPStanFactory.php
+++ b/src/DocBlock/Tags/Factory/AbstractPHPStanFactory.php
@@ -23,6 +23,8 @@
use PHPStan\PhpDocParser\Parser\TypeParser;
use RuntimeException;
+use function property_exists;
+
/**
* Factory class creating tags using phpstan's parser
*
@@ -48,8 +50,11 @@ public function __construct(PHPStanFactory ...$factories)
public function create(string $tagLine, ?TypeContext $context = null): Tag
{
- $tokens = $this->lexer->tokenize($tagLine);
- $ast = $this->parser->parseTag(new TokenIterator($tokens));
+ $tokens = new TokenIterator($this->lexer->tokenize($tagLine));
+ $ast = $this->parser->parseTag($tokens);
+ if (property_exists($ast->value, 'description') === true) {
+ $ast->value->setAttribute('description', $ast->value->description . $tokens->joinUntil(Lexer::TOKEN_END));
+ }
if ($context === null) {
$context = new TypeContext('');
diff --git a/src/DocBlock/Tags/Factory/ParamFactory.php b/src/DocBlock/Tags/Factory/ParamFactory.php
index 8e9bcaac..7d680d9c 100644
--- a/src/DocBlock/Tags/Factory/ParamFactory.php
+++ b/src/DocBlock/Tags/Factory/ParamFactory.php
@@ -19,6 +19,7 @@
use PHPStan\PhpDocParser\Ast\Type\OffsetAccessTypeNode;
use Webmozart\Assert\Assert;
+use function is_string;
use function sprintf;
use function trim;
@@ -68,11 +69,16 @@ public function create(PhpDocTagNode $node, Context $context): Tag
);
}
+ $description = $tagValue->getAttribute('description');
+ if (is_string($description) === false) {
+ $description = $tagValue->description;
+ }
+
return new Param(
trim($tagValue->parameterName, '$'),
$this->typeResolver->createType($tagValue->type ?? new IdentifierTypeNode('mixed'), $context),
$tagValue->isVariadic,
- $this->descriptionFactory->create($tagValue->description, $context),
+ $this->descriptionFactory->create($description, $context),
$tagValue->isReference
);
}
diff --git a/src/DocBlock/Tags/Factory/PropertyFactory.php b/src/DocBlock/Tags/Factory/PropertyFactory.php
index 83a18e8a..b744ed08 100644
--- a/src/DocBlock/Tags/Factory/PropertyFactory.php
+++ b/src/DocBlock/Tags/Factory/PropertyFactory.php
@@ -13,6 +13,7 @@
use PHPStan\PhpDocParser\Ast\PhpDoc\PropertyTagValueNode;
use Webmozart\Assert\Assert;
+use function is_string;
use function trim;
/**
@@ -34,10 +35,15 @@ public function create(PhpDocTagNode $node, Context $context): Tag
$tagValue = $node->value;
Assert::isInstanceOf($tagValue, PropertyTagValueNode::class);
+ $description = $tagValue->getAttribute('description');
+ if (is_string($description) === false) {
+ $description = $tagValue->description;
+ }
+
return new Property(
trim($tagValue->propertyName, '$'),
$this->typeResolver->createType($tagValue->type, $context),
- $this->descriptionFactory->create($tagValue->description, $context)
+ $this->descriptionFactory->create($description, $context)
);
}
diff --git a/src/DocBlock/Tags/Factory/PropertyReadFactory.php b/src/DocBlock/Tags/Factory/PropertyReadFactory.php
index be443ab6..b0898aa7 100644
--- a/src/DocBlock/Tags/Factory/PropertyReadFactory.php
+++ b/src/DocBlock/Tags/Factory/PropertyReadFactory.php
@@ -13,6 +13,7 @@
use PHPStan\PhpDocParser\Ast\PhpDoc\PropertyTagValueNode;
use Webmozart\Assert\Assert;
+use function is_string;
use function trim;
/**
@@ -34,10 +35,15 @@ public function create(PhpDocTagNode $node, Context $context): Tag
$tagValue = $node->value;
Assert::isInstanceOf($tagValue, PropertyTagValueNode::class);
+ $description = $tagValue->getAttribute('description');
+ if (is_string($description) === false) {
+ $description = $tagValue->description;
+ }
+
return new PropertyRead(
trim($tagValue->propertyName, '$'),
$this->typeResolver->createType($tagValue->type, $context),
- $this->descriptionFactory->create($tagValue->description, $context)
+ $this->descriptionFactory->create($description, $context)
);
}
diff --git a/src/DocBlock/Tags/Factory/PropertyWriteFactory.php b/src/DocBlock/Tags/Factory/PropertyWriteFactory.php
index 08cf7534..749b1eda 100644
--- a/src/DocBlock/Tags/Factory/PropertyWriteFactory.php
+++ b/src/DocBlock/Tags/Factory/PropertyWriteFactory.php
@@ -13,6 +13,7 @@
use PHPStan\PhpDocParser\Ast\PhpDoc\PropertyTagValueNode;
use Webmozart\Assert\Assert;
+use function is_string;
use function trim;
/**
@@ -34,10 +35,15 @@ public function create(PhpDocTagNode $node, Context $context): Tag
$tagValue = $node->value;
Assert::isInstanceOf($tagValue, PropertyTagValueNode::class);
+ $description = $tagValue->getAttribute('description');
+ if (is_string($description) === false) {
+ $description = $tagValue->description;
+ }
+
return new PropertyWrite(
trim($tagValue->propertyName, '$'),
$this->typeResolver->createType($tagValue->type, $context),
- $this->descriptionFactory->create($tagValue->description, $context)
+ $this->descriptionFactory->create($description, $context)
);
}
diff --git a/src/DocBlock/Tags/Factory/ReturnFactory.php b/src/DocBlock/Tags/Factory/ReturnFactory.php
index 8ebe5ad4..4a17dc24 100644
--- a/src/DocBlock/Tags/Factory/ReturnFactory.php
+++ b/src/DocBlock/Tags/Factory/ReturnFactory.php
@@ -13,6 +13,8 @@
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
use Webmozart\Assert\Assert;
+use function is_string;
+
/**
* @internal This class is not part of the BC promise of this library.
*/
@@ -32,9 +34,14 @@ public function create(PhpDocTagNode $node, Context $context): Tag
$tagValue = $node->value;
Assert::isInstanceOf($tagValue, ReturnTagValueNode::class);
+ $description = $tagValue->getAttribute('description');
+ if (is_string($description) === false) {
+ $description = $tagValue->description;
+ }
+
return new Return_(
$this->typeResolver->createType($tagValue->type, $context),
- $this->descriptionFactory->create($tagValue->description, $context)
+ $this->descriptionFactory->create($description, $context)
);
}
diff --git a/src/DocBlock/Tags/Factory/VarFactory.php b/src/DocBlock/Tags/Factory/VarFactory.php
index 79ffdf12..479ceb27 100644
--- a/src/DocBlock/Tags/Factory/VarFactory.php
+++ b/src/DocBlock/Tags/Factory/VarFactory.php
@@ -13,6 +13,7 @@
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
use Webmozart\Assert\Assert;
+use function is_string;
use function trim;
/**
@@ -34,10 +35,15 @@ public function create(PhpDocTagNode $node, Context $context): Tag
$tagValue = $node->value;
Assert::isInstanceOf($tagValue, VarTagValueNode::class);
+ $description = $tagValue->getAttribute('description');
+ if (is_string($description) === false) {
+ $description = $tagValue->description;
+ }
+
return new Var_(
trim($tagValue->variableName, '$'),
$this->typeResolver->createType($tagValue->type, $context),
- $this->descriptionFactory->create($tagValue->description, $context)
+ $this->descriptionFactory->create($description, $context)
);
}
diff --git a/tests/integration/InterpretingDocBlocksTest.php b/tests/integration/InterpretingDocBlocksTest.php
index 51f3e8f0..e9685e97 100644
--- a/tests/integration/InterpretingDocBlocksTest.php
+++ b/tests/integration/InterpretingDocBlocksTest.php
@@ -17,16 +17,21 @@
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\StandardTagFactory;
use phpDocumentor\Reflection\DocBlock\Tag;
+use phpDocumentor\Reflection\DocBlock\Tags\Generic;
use phpDocumentor\Reflection\DocBlock\Tags\InvalidTag;
use phpDocumentor\Reflection\DocBlock\Tags\Method;
use phpDocumentor\Reflection\DocBlock\Tags\MethodParameter;
use phpDocumentor\Reflection\DocBlock\Tags\Param;
use phpDocumentor\Reflection\DocBlock\Tags\Return_;
use phpDocumentor\Reflection\DocBlock\Tags\See;
+use phpDocumentor\Reflection\DocBlock\Tags\Since;
use phpDocumentor\Reflection\PseudoTypes\ConstExpression;
use phpDocumentor\Reflection\Types\Array_;
+use phpDocumentor\Reflection\Types\Compound;
+use phpDocumentor\Reflection\Types\Context;
use phpDocumentor\Reflection\Types\Integer;
use phpDocumentor\Reflection\Types\Mixed_;
+use phpDocumentor\Reflection\Types\Object_;
use phpDocumentor\Reflection\Types\Self_;
use phpDocumentor\Reflection\Types\String_;
use phpDocumentor\Reflection\Types\Void_;
@@ -123,8 +128,8 @@ public function testInterpretingTags(): void
$this->assertInstanceOf(See::class, $seeTags[0]);
$seeTag = $seeTags[0];
- $this->assertSame('\\' . StandardTagFactory::class, (string) $seeTag->getReference());
- $this->assertSame('', (string) $seeTag->getDescription());
+ $this->assertSame('\\' . StandardTagFactory::class, (string)$seeTag->getReference());
+ $this->assertSame('', (string)$seeTag->getDescription());
}
public function testDescriptionsCanEscapeAtSignsAndClosingBraces(): void
@@ -268,4 +273,95 @@ public function testConstantReferenceTypes(): void
$docblock->getTags()
);
}
+
+ public function testRegressionWordpressDocblocks(): void
+ {
+ $docCommment = <<create($docCommment);
+
+ self::assertEquals(
+ new DocBlock(
+ 'Install a package.',
+ new Description(
+ 'Copies the contents of a package from a source directory, and installs them in' . PHP_EOL .
+ 'a destination directory. Optionally removes the source. It can also optionally' . PHP_EOL .
+ 'clear out the destination folder if it already exists.'
+ ),
+ [
+ new Since('2.8.0', new Description('')),
+ new Since('6.2.0', new Description('Use move_dir() instead of copy_dir() when possible.')),
+ new Generic(
+ 'global',
+ new Description('WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.')
+ ),
+ new Generic(
+ 'global',
+ new Description('array $wp_theme_directories')
+ ),
+ new Param(
+ 'args',
+ new Compound([new Array_(new Mixed_()), new String_()]),
+ false,
+ new Description(
+ '{' . "\n" .
+ 'Optional. Array or string of arguments for installing a package. Default empty array.' . "\n" .
+ "\n" .
+ ' @type string $source Required path to the package source. Default empty.' . "\n" .
+ ' @type string $destination Required path to a folder to install the package in.' . "\n" .
+ ' Default empty.' . "\n" .
+ ' @type bool $clear_destination Whether to delete any files already in the destination' . "\n" .
+ ' folder. Default false.' . "\n" .
+ ' @type bool $clear_working Whether to delete the files from the working directory' . "\n" .
+ ' after copying them to the destination. Default false.' . "\n" .
+ ' @type bool $abort_if_destination_exists Whether to abort the installation if' . "\n" .
+ ' the destination folder already exists. Default true.' . "\n" .
+ ' @type array $hook_extra Extra arguments to pass to the filter hooks called by' . "\n" .
+ ' WP_Upgrader::install_package(). Default empty array.' . "\n" .
+ '}'
+ )
+ ),
+ new Return_(
+ new Compound([new Array_(new Mixed_()), new Object_(new Fqsen('\WP_Error'))]),
+ new Description('The result (also stored in `WP_Upgrader::$result`), or a WP_Error on failure.')
+ ),
+ ],
+ new Context('\\')
+ ),
+ $docblock
+ );
+ }
}
diff --git a/tests/unit/DocBlock/Tags/Factory/TagFactoryTestCase.php b/tests/unit/DocBlock/Tags/Factory/TagFactoryTestCase.php
index ec905654..d97c7f1d 100644
--- a/tests/unit/DocBlock/Tags/Factory/TagFactoryTestCase.php
+++ b/tests/unit/DocBlock/Tags/Factory/TagFactoryTestCase.php
@@ -26,6 +26,8 @@
use PHPStan\PhpDocParser\Parser\TypeParser;
use PHPUnit\Framework\TestCase;
+use function property_exists;
+
abstract class TagFactoryTestCase extends TestCase
{
public function parseTag(string $tag): PhpDocTagNode
@@ -34,7 +36,12 @@ public function parseTag(string $tag): PhpDocTagNode
$tokens = $lexer->tokenize($tag);
$constParser = new ConstExprParser();
- return (new PhpDocParser(new TypeParser($constParser), $constParser))->parseTag(new TokenIterator($tokens));
+ $tagNode = (new PhpDocParser(new TypeParser($constParser), $constParser))->parseTag(new TokenIterator($tokens));
+ if (property_exists($tagNode->value, 'description') === true) {
+ $tagNode->value->setAttribute('description', $tagNode->value->description);
+ }
+
+ return $tagNode;
}
public function giveTypeResolver(): TypeResolver