From 5e7cd94ec116f2c93cc67085df4bed1c122ffe65 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sat, 6 Apr 2024 18:17:12 +0200 Subject: [PATCH] Refactor --- composer.json | 11 +--- src/PhpDoc.php | 41 ++++++++++----- src/PhpDoc/ClassDoc.php | 61 +++++++++------------- src/PhpDoc/GenericDoc.php | 82 ++++++++++------------------- src/PhpDoc/MethodDoc.php | 106 ++++++++++++-------------------------- src/PhpDocBuilder.php | 4 +- 6 files changed, 114 insertions(+), 191 deletions(-) diff --git a/composer.json b/composer.json index ab0f4c7..de74233 100644 --- a/composer.json +++ b/composer.json @@ -13,15 +13,13 @@ "require": { "php": "^8.0", "danog/class-finder": "^0.4", - "phpdocumentor/reflection-docblock": "^5.2", "phpstan/phpdoc-parser": "^1.2", "symfony/yaml": "^6.0" }, "require-dev": { "vimeo/psalm": "dev-master", "amphp/php-cs-fixer-config": "dev-master", - "friendsofphp/php-cs-fixer": "^2", - "phabel/phabel": "^1" + "friendsofphp/php-cs-fixer": "^2" }, "authors": [ { @@ -51,12 +49,7 @@ "config": { "allow-plugins": { "composer/package-versions-deprecated": true, - "phabel/phabel": true - } - }, - "extra": { - "phabel": { - "revision": 0 + "phabel/phabel": false } } } diff --git a/src/PhpDoc.php b/src/PhpDoc.php index bdf723d..c392040 100644 --- a/src/PhpDoc.php +++ b/src/PhpDoc.php @@ -22,8 +22,12 @@ use danog\ClassFinder\ClassFinder; use danog\PhpDoc\PhpDoc\ClassDoc; use danog\PhpDoc\PhpDoc\FunctionDoc; use danog\PhpDoc\PhpDoc\GenericDoc; -use phpDocumentor\Reflection\DocBlock\Tags\Author; -use phpDocumentor\Reflection\DocBlockFactory; +use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode; +use PHPStan\PhpDocParser\Lexer\Lexer; +use PHPStan\PhpDocParser\Parser\ConstExprParser; +use PHPStan\PhpDocParser\Parser\PhpDocParser; +use PHPStan\PhpDocParser\Parser\TokenIterator; +use PHPStan\PhpDocParser\Parser\TypeParser; use ReflectionClass; use ReflectionFunction; use Symfony\Component\Yaml\Escaper; @@ -44,9 +48,13 @@ class PhpDoc */ private int $mode = ClassFinder::ALLOW_ALL | ClassFinder::RECURSIVE_MODE; /** - * Docblock factory. + * PHPDOC parser. */ - private DocBlockFactory $factory; + private PhpDocParser $parser; + /** + * Lexer. + */ + private Lexer $lexer; /** * Authors. */ @@ -119,7 +127,14 @@ class PhpDoc */ private function __construct(string $namespace) { - $this->factory = DocBlockFactory::createInstance(); + $this->lexer = new Lexer(); + $constExprParser = new ConstExprParser(); + $typeParser = new TypeParser($constExprParser); + $this->parser = new PhpDocParser( + $typeParser, + $constExprParser, + textBetweenTagsBelongsToDescription: true + ); $this->namespace = $namespace; $appRoot = new \danog\ClassFinder\AppConfig; @@ -131,7 +146,7 @@ class PhpDoc $this->name = $json['name'] ?? ''; $this->description = $json['description'] ?? ''; foreach ($authors as $author) { - $this->authors []= new Author($author['name'], $author['email']); + $this->authors []= "{$author['name']} <{$author['email']}>"; } if (!$this->namespace) { @@ -445,7 +460,7 @@ class PhpDoc } if ($reflectionClass instanceof ReflectionClass) { - array_map($this->addTypeAliases(...), $reflectionClass->getTraitNames()); + \array_map($this->addTypeAliases(...), $reflectionClass->getTraitNames()); } } /** @@ -487,15 +502,13 @@ class PhpDoc } /** - * Get docblock factory. + * Parse phpdoc. * * @internal - * - * @return DocBlockFactory */ - public function getFactory(): DocBlockFactory + public function parse(string $phpdoc): PhpDocNode { - return $this->factory; + return $this->parser->parse(new TokenIterator($this->lexer->tokenize($phpdoc))); } /** @@ -511,7 +524,7 @@ class PhpDoc /** * Get authors. * - * @return Author[] + * @return string[] */ public function getAuthors(): array { @@ -521,7 +534,7 @@ class PhpDoc /** * Set authors. * - * @param Author[] $authors Authors + * @param string[] $authors Authors * * @return self */ diff --git a/src/PhpDoc/ClassDoc.php b/src/PhpDoc/ClassDoc.php index 5db1f96..e8b250a 100644 --- a/src/PhpDoc/ClassDoc.php +++ b/src/PhpDoc/ClassDoc.php @@ -3,10 +3,9 @@ namespace danog\PhpDoc\PhpDoc; use danog\PhpDoc\PhpDoc; -use phpDocumentor\Reflection\DocBlock\Tags\Generic; -use phpDocumentor\Reflection\DocBlock\Tags\InvalidTag; -use phpDocumentor\Reflection\DocBlock\Tags\Property; -use phpDocumentor\Reflection\DocBlock\Tags\Var_; +use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTextNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\PropertyTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode; use ReflectionClass; use ReflectionClassConstant; use ReflectionMethod; @@ -40,7 +39,7 @@ class ClassDoc extends GenericDoc $this->builder = $builder; $this->name = $reflectionClass->getName(); $doc = $reflectionClass->getDocComment() ?: '/** */'; - $doc = $this->builder->getFactory()->create($doc); + $doc = $this->builder->parse($doc); parent::__construct($doc, $reflectionClass); @@ -50,13 +49,15 @@ class ClassDoc extends GenericDoc $type = (string) $type; $name = $property->getName(); $comment = ''; - foreach ($this->builder->getFactory()->create($property->getDocComment() ?: '/** */')->getTags() as $tag) { - if ($tag instanceof Var_) { - $comment = $tag->getDescription(); - $type = $tag->getType() ?? 'mixed'; + foreach ($this->builder->parse($property->getDocComment() ?: '/** */')->getTags() as $tag) { + if ($tag->name === '@var') { + $tag = $tag->value; + \assert($tag instanceof VarTagValueNode); + $type = (string) $tag->type; + $comment = $tag->description; break; } - if ($tag instanceof Generic && $tag->getName() === 'internal') { + if ($tag->name === 'internal') { continue 2; } } @@ -66,27 +67,17 @@ class ClassDoc extends GenericDoc $docReflection .= " * @property $type \$$name $comment\n"; } $docReflection .= " */\n"; - $docReflection = $this->builder->getFactory()->create($docReflection); + $docReflection = $this->builder->parse($docReflection); $tags = \array_merge($docReflection->getTags(), $doc->getTags()); foreach ($tags as $tag) { - if ($tag instanceof Property && $tag->getVariableName()) { + if ($tag->name === 'property') { + $tag = $tag->value; + \assert($tag instanceof PropertyTagValueNode); /** @psalm-suppress InvalidPropertyAssignmentValue */ - $this->properties[$tag->getVariableName()] = [ - $tag->getType(), - $tag->getDescription() - ]; - } - if ($tag instanceof InvalidTag && $tag->getName() === 'property') { - [$type, $description] = \explode(" $", $tag->render(), 2); - $description .= ' '; - [$varName, $description] = \explode(" ", $description, 2); - $type = \str_replace('@property ', '', $type); - $description ??= ''; - /** @psalm-suppress InvalidPropertyAssignmentValue */ - $this->properties[$varName] = [ - $type, - $description + $this->properties[$tag->propertyName] = [ + (string) $tag->type, + $tag->description ]; } } @@ -97,19 +88,17 @@ class ClassDoc extends GenericDoc } $description = ''; if ($refl->getDocComment()) { - $docConst = $this->builder->getFactory()->create($refl->getDocComment()); if ($this->builder->shouldIgnore($refl->getDeclaringClass()->getName())) { continue; } - foreach ($docConst->getTags() as $tag) { - if ($tag instanceof Generic && $tag->getName() === 'internal') { - continue 2; - } + $docConst = $this->builder->parse($refl->getDocComment()); + if ($docConst->getTagsByName('internal')) { + continue; } - $description .= $docConst->getSummary(); - if ($docConst->getDescription()) { - $description .= "\n\n"; - $description .= $docConst->getDescription(); + foreach ($docConst->children as $node) { + if ($node instanceof PhpDocTextNode) { + $description .= $node->text."\n"; + } } } $description = \trim($description); diff --git a/src/PhpDoc/GenericDoc.php b/src/PhpDoc/GenericDoc.php index 74071bc..391496a 100644 --- a/src/PhpDoc/GenericDoc.php +++ b/src/PhpDoc/GenericDoc.php @@ -3,15 +3,9 @@ namespace danog\PhpDoc\PhpDoc; use danog\PhpDoc\PhpDoc; -use phpDocumentor\Reflection\DocBlock; -use phpDocumentor\Reflection\DocBlock\Description; -use phpDocumentor\Reflection\DocBlock\Tags\Author; -use phpDocumentor\Reflection\DocBlock\Tags\Deprecated; -use phpDocumentor\Reflection\DocBlock\Tags\Generic; -use phpDocumentor\Reflection\DocBlock\Tags\Reference\Fqsen; -use phpDocumentor\Reflection\DocBlock\Tags\Reference\Url; -use phpDocumentor\Reflection\DocBlock\Tags\See; -use phpDocumentor\Reflection\Fqsen as ReflectionFqsen; +use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTextNode; use ReflectionClass; use ReflectionFunction; @@ -37,17 +31,17 @@ abstract class GenericDoc /** * Description. */ - protected Description $description; + protected string $description; /** * See also array. * - * @var array + * @var array */ protected array $seeAlso = []; /** * Authors. * - * @var Author[] + * @var string[] */ protected array $authors; /** @@ -69,34 +63,39 @@ abstract class GenericDoc /** * Constructor. * - * @param DocBlock $doc * @param ReflectionClass|ReflectionFunction $reflectionClass */ - public function __construct(DocBlock $doc, $reflectionClass) + public function __construct(PhpDocNode $doc, $reflectionClass) { $empty = []; $this->className = $reflectionClass->getName(); $this->resolvedClassName = $this->builder->resolveTypeAlias($this->className, $this->className, $empty); $this->namespace = \str_replace('/', '\\', \dirname(\str_replace('\\', '/', $this->className))); - $this->title = $doc->getSummary(); - $this->description = $doc->getDescription(); + $description = ''; + foreach ($doc->children as $child) { + if ($child instanceof PhpDocTextNode) { + $description .= $child->text."\n"; + } + } + [$this->title, $this->description] = \explode("\n", \trim($description)."\n", 2); $tags = $doc->getTags(); $this->authors = $this->builder->getAuthors(); foreach ($tags as $tag) { - if ($tag instanceof Author) { - $this->authors []= $tag; + if ($tag->name === '@author') { + $tag = $tag->value; + \assert($tag instanceof GenericTagValueNode); + $this->authors []= $tag->value; + continue; } - if ($tag instanceof Deprecated) { + if ($tag->name === '@deprecated' || $tag->name === '@internal') { $this->ignore = true; break; } - if ($tag instanceof Generic && $tag->getName() === 'internal') { - $this->ignore = true; - break; - } - if ($tag instanceof See) { - $this->seeAlso[$tag->getReference()->__toString()] = $tag; + if ($tag->name === '@see') { + $tag = $tag->value; + \assert($tag instanceof GenericTagValueNode); + $this->seeAlso[$tag->value] = $tag; } } $this->authors = \array_unique($this->authors); @@ -111,38 +110,9 @@ abstract class GenericDoc { $namespace = \explode('\\', $namespace); - $empty = []; $seeAlso = ''; foreach ($this->seeAlso as $see) { - $ref = $see->getReference(); - if ($ref instanceof Fqsen) { - $ref = (string) $ref; - $ref = $this->builder->resolveTypeAlias($this->className, $ref, $empty); - - $to = \explode("\\", $ref.".md"); - if (\count($to) === 2 || !$this->builder->hasClass($ref)) { - $seeAlso .= "* `$ref`\n"; - continue; - } - - \array_shift($to); - \array_unshift($to, ...\array_fill(0, \count($namespace), '..')); - $relPath = $to; - $path = \implode('/', $relPath); - - if (!$desc = $see->getDescription()) { - if ($desc = $this->builder->getTitle($ref)) { - $desc = "`$ref`: $desc"; - } else { - $desc = $ref; - } - } - $seeAlso .= "* [$desc]($path)\n"; - } - if ($ref instanceof Url) { - $desc = $see->getDescription() ?: $ref; - $seeAlso .= "* [$desc]($ref)\n"; - } + $seeAlso .= "* ".$see->value."\n"; } if ($seeAlso) { $seeAlso = "\n#### See also: \n$seeAlso\n\n"; @@ -208,7 +178,7 @@ abstract class GenericDoc continue; } try { - $this->seeAlso[$type] = new See(new Fqsen(new ReflectionFqsen($type))); + $this->seeAlso[$type] = new GenericTagValueNode($type); } catch (\Throwable $e) { } } diff --git a/src/PhpDoc/MethodDoc.php b/src/PhpDoc/MethodDoc.php index 922a768..0fbf01d 100644 --- a/src/PhpDoc/MethodDoc.php +++ b/src/PhpDoc/MethodDoc.php @@ -3,10 +3,7 @@ namespace danog\PhpDoc\PhpDoc; use danog\PhpDoc\PhpDoc; -use phpDocumentor\Reflection\DocBlock\Description; -use phpDocumentor\Reflection\DocBlock\Tags\Generic; -use phpDocumentor\Reflection\DocBlock\Tags\Param; -use phpDocumentor\Reflection\DocBlock\Tags\Return_; +use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode; use ReflectionFunctionAbstract; use ReflectionMethod; @@ -17,10 +14,8 @@ use ReflectionMethod; */ class MethodDoc extends GenericDoc { - private Return_ $return; - private string $psalmReturn; + private ReturnTagValueNode $return; private array $params = []; - private array $psalmParams = []; /** * Constructor. * @@ -32,16 +27,17 @@ class MethodDoc extends GenericDoc $this->builder = $phpDocBuilder; $this->name = $method->getName(); $doc = $method->getDocComment() ?: '/** */'; - $doc = $this->builder->getFactory()->create($doc); + $doc = $this->builder->parse($doc); parent::__construct($doc, $method instanceof ReflectionMethod ? $method->getDeclaringClass() : $method); $order = []; $optional = []; + $params = []; $docReflection = "/**\n"; foreach ($method->getParameters() as $param) { - $order []= $param->getName(); + $order []= '$'.$param->getName(); $opt = $param->isOptional() && !$param->isVariadic(); $default = ''; if ($opt) { @@ -51,54 +47,38 @@ class MethodDoc extends GenericDoc $default = \str_replace([PHP_EOL, 'array (', ')'], ['', '[', ']'], \var_export($param->getDefaultValue(), true)); } } - $optional[$param->getName()] = [$opt, $default]; + $optional['$'.$param->getName()] = [$opt, $default]; $type = (string) ($param->getType() ?? 'mixed'); - $variadic = $param->isVariadic() ? '...' : ''; - $docReflection .= " * @param $type $variadic\$".$param->getName()."\n"; + $params['$'.$param->getName()] = [ + $type, + '', + $param->isVariadic(), + $optional['$'.$param->getName()] + ]; } - $docReflection .= ' * @return '.($method->getReturnType() ?? 'mixed')."\n*/"; - $docReflection = $this->builder->getFactory()->create($docReflection); - $params = []; - $psalmParams = []; - - foreach ([...$doc->getTags(), ...$docReflection->getTags()] as $tag) { - if ($tag instanceof Param && !isset($params[$tag->getVariableName()])) { - $params[$tag->getVariableName()] = [ - $tag->getType(), - $tag->getDescription(), - $tag->isVariadic(), - $optional[$tag->getVariableName()] + foreach (['@param', '@psalm-param', '@phpstan-param'] as $t) { + foreach ($doc->getParamTagValues($t) as $tag) { + $params[$tag->parameterName] ??= [ + $tag->type, + $tag->description, + $tag->isVariadic, + $optional[$tag->parameterName] ]; - } elseif ($tag instanceof Return_ && !isset($this->return) && $this->name !== '__construct') { - $this->return = $tag; - } elseif ($tag instanceof Generic && $tag->getName() === 'psalm-return') { - $this->psalmReturn = $tag; - } elseif ($tag instanceof Generic && $tag->getName() === 'psalm-param') { - $desc = $tag->getDescription(); - $dollar = \strrpos($desc, '$'); - $type = \substr($tag, 0, $dollar-1); - $description = \substr($tag, $dollar+1); - $description .= ' '; - [$varName, $description] = \explode(" ", $description, 2); - if (!$description && isset($params[$varName])) { - $description = (string) $params[$varName][1]; - } else { - $description = new Description($description); + $params[$tag->parameterName][0] = $tag->type; + $params[$tag->parameterName][1] = $tag->description; + } + } + if ($this->name !== '__construct') { + foreach (['@return', '@psalm-return', '@phpstan-return'] as $t) { + foreach ($doc->getReturnTagValues($t) as $tag) { + $this->return = $tag; } - $psalmParams[$varName] = [ - $type, - $description, - $optional[$varName] - ]; } } foreach ($order as $param) { $this->params[$param] = $params[$param]; - if (isset($psalmParams[$param])) { - $this->psalmParams[$param] = $psalmParams[$param]; - } } foreach ($this->params as &$param) { @@ -106,14 +86,6 @@ class MethodDoc extends GenericDoc $param[0] = $this->resolveTypeAlias($param[0]); } } - foreach ($this->psalmParams as &$param) { - if (isset($param[0])) { - $param[0] = $this->resolveTypeAlias($param[0]); - } - } - if (isset($this->psalmReturn)) { - $this->psalmReturn = $this->resolveTypeAlias($this->psalmReturn); - } } /** @@ -130,7 +102,7 @@ class MethodDoc extends GenericDoc if ($variadic) { $sig .= '...'; } - $sig .= "$".$var; + $sig .= $var; if ($optional) { $sig .= " = ".$default; } @@ -140,7 +112,7 @@ class MethodDoc extends GenericDoc $sig .= ')'; if (isset($this->return)) { $sig .= ': '; - $sig .= $this->resolveTypeAlias($this->return); + $sig .= $this->resolveTypeAlias((string) $this->return->type); } return $sig; } @@ -182,28 +154,16 @@ class MethodDoc extends GenericDoc $sig .= "\n"; $sig .= \str_replace("\n", " \n", $this->description); $sig .= "\n"; - if ($this->psalmParams || $this->params) { + if ($this->params) { $sig .= "\nParameters:\n\n"; foreach ($this->params as $name => [$type, $description, $variadic]) { $variadic = $variadic ? '...' : ''; - $sig .= "* `$variadic\$$name`: `$type` $description \n"; - if (isset($this->psalmParams[$name])) { - [$psalmType] = $this->psalmParams[$name]; - $psalmType = \trim(\str_replace("\n", "\n ", $psalmType)); - - $sig .= " Full type:\n"; - $sig .= " ```\n"; - $sig .= " $psalmType\n"; - $sig .= " ```\n"; - } + $sig .= "* `$variadic$name`: `$type` $description \n"; } $sig .= "\n"; } - if (isset($this->return) && $this->return->getDescription() && $this->return->getDescription()->render()) { - $sig .= "\nReturn value: ".$this->return->getDescription()."\n"; - } - if (isset($this->psalmReturn)) { - $sig .= "\nFully typed return value:\n```\n".$this->psalmReturn."\n```"; + if (isset($this->return) && $this->return->description) { + $sig .= "\nReturn value: ".$this->return->description."\n"; } $sig .= $this->seeAlso($namespace ?? $this->namespace); $sig .= "\n"; diff --git a/src/PhpDocBuilder.php b/src/PhpDocBuilder.php index 535781b..3864530 100644 --- a/src/PhpDocBuilder.php +++ b/src/PhpDocBuilder.php @@ -18,8 +18,6 @@ namespace danog\PhpDoc; -use phpDocumentor\Reflection\DocBlock\Tags\Author; - /** * PHP documentation builder. */ @@ -52,7 +50,7 @@ final class PhpDocBuilder /** * Set authors. * - * @param Author[] $authors Authors + * @param string[] $authors Authors * * @return self */