mirror of
https://github.com/barryvdh/ReflectionDocBlock.git
synced 2026-08-18 01:57:13 +00:00
First POC steps
This commit is contained in:
+3
-2
@@ -14,11 +14,12 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.2 || ^8.0",
|
||||
"php": "^7.4 || ^8.0",
|
||||
"phpdocumentor/type-resolver": "^1.3",
|
||||
"webmozart/assert": "^1.9.1",
|
||||
"phpdocumentor/reflection-common": "^2.2",
|
||||
"ext-filter": "*"
|
||||
"ext-filter": "*",
|
||||
"phpstan/phpdoc-parser": "^1.7"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "~1.3.5",
|
||||
|
||||
Generated
+46
-1
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "cbf52dda9a68fb6e5d4da2511d6b4c0d",
|
||||
"content-hash": "5ca8bb643a11d17932ef43044dc2f12c",
|
||||
"packages": [
|
||||
{
|
||||
"name": "phpdocumentor/reflection-common",
|
||||
@@ -109,6 +109,51 @@
|
||||
},
|
||||
"time": "2022-03-15T21:29:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpdoc-parser",
|
||||
"version": "1.7.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpdoc-parser.git",
|
||||
"reference": "367a8d9d5f7da2a0136422d27ce8840583926955"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/367a8d9d5f7da2a0136422d27ce8840583926955",
|
||||
"reference": "367a8d9d5f7da2a0136422d27ce8840583926955",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"php-parallel-lint/php-parallel-lint": "^1.2",
|
||||
"phpstan/extension-installer": "^1.0",
|
||||
"phpstan/phpstan": "^1.5",
|
||||
"phpstan/phpstan-phpunit": "^1.1",
|
||||
"phpstan/phpstan-strict-rules": "^1.0",
|
||||
"phpunit/phpunit": "^9.5",
|
||||
"symfony/process": "^5.2"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PHPStan\\PhpDocParser\\": [
|
||||
"src/"
|
||||
]
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "PHPDoc parser with support for nullable, intersection and generic types",
|
||||
"support": {
|
||||
"issues": "https://github.com/phpstan/phpdoc-parser/issues",
|
||||
"source": "https://github.com/phpstan/phpdoc-parser/tree/1.7.0"
|
||||
},
|
||||
"time": "2022-08-09T12:23:23+00:00"
|
||||
},
|
||||
{
|
||||
"name": "webmozart/assert",
|
||||
"version": "1.11.0",
|
||||
|
||||
@@ -14,7 +14,7 @@ interface DocBlockFactoryInterface
|
||||
*
|
||||
* @param array<string, class-string<Tag>> $additionalTags
|
||||
*/
|
||||
public static function createInstance(array $additionalTags = []): DocBlockFactory;
|
||||
public static function createInstance(array $additionalTags = []): self;
|
||||
|
||||
/**
|
||||
* @param string|object $docblock
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace phpDocumentor\Reflection\PhpStan;
|
||||
|
||||
use phpDocumentor\Reflection\DocBlock\Tag;
|
||||
use phpDocumentor\Reflection\DocBlock\TagFactory as TagFactoryInterface;
|
||||
use phpDocumentor\Reflection\DocBlock\Tags\InvalidTag;
|
||||
use phpDocumentor\Reflection\Types\Context as TypeContext;
|
||||
|
||||
class TagFactory implements TagFactoryInterface
|
||||
{
|
||||
public function addParameter(string $name, $value) : void
|
||||
{
|
||||
// TODO: Implement addParameter() method.
|
||||
}
|
||||
|
||||
public function create(string $tagLine, ?TypeContext $context = null) : Tag
|
||||
{
|
||||
return InvalidTag::create($tagLine);
|
||||
}
|
||||
|
||||
public function addService(object $service) : void
|
||||
{
|
||||
// TODO: Implement addService() method.
|
||||
}
|
||||
|
||||
public function registerTagHandler(string $tagName, string $handler) : void
|
||||
{
|
||||
// TODO: Implement registerTagHandler() method.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of phpDocumentor.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
* @link http://phpdoc.org
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace phpDocumentor\Reflection;
|
||||
|
||||
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
|
||||
use phpDocumentor\Reflection\DocBlock\StandardTagFactory;
|
||||
use phpDocumentor\Reflection\DocBlock\Tags\Param;
|
||||
use phpDocumentor\Reflection\DocBlock\Tags\Return_;
|
||||
use phpDocumentor\Reflection\PhpStan\TagFactory;
|
||||
use phpDocumentor\Reflection\PseudoTypes\ArrayShape;
|
||||
use phpDocumentor\Reflection\PseudoTypes\ArrayShapeItem;
|
||||
use phpDocumentor\Reflection\Types\Context;
|
||||
use PHPStan\PhpDoc\Tag\ParamTag;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTextNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\ArrayShapeItemNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
|
||||
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 Webmozart\Assert\Assert;
|
||||
use InvalidArgumentException;
|
||||
use LogicException;
|
||||
|
||||
final class PhpStanDocblockFactory implements DocBlockFactoryInterface
|
||||
{
|
||||
private PhpDocParser $parser;
|
||||
private Lexer $lexer;
|
||||
private DescriptionFactory $descriptionFactory;
|
||||
private TypeResolver $typeResolver;
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public static function createInstance(array $additionalTags = []): DocBlockFactoryInterface
|
||||
{
|
||||
$fqsenResolver = new FqsenResolver();
|
||||
$constExprParser = new ConstExprParser();
|
||||
$self = new self();
|
||||
$self->lexer = new Lexer();
|
||||
$self->parser = new PhpDocParser(
|
||||
new TypeParser($constExprParser),
|
||||
$constExprParser
|
||||
);
|
||||
|
||||
$self->descriptionFactory = new DescriptionFactory(new TagFactory());
|
||||
$self->typeResolver = new TypeResolver($fqsenResolver);
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
public function create($docblock, ?Types\Context $context = null, ?Location $location = null): DocBlock
|
||||
{
|
||||
if (is_object($docblock)) {
|
||||
if (!method_exists($docblock, 'getDocComment')) {
|
||||
$exceptionMessage = 'Invalid object passed; the given object must support the getDocComment method';
|
||||
|
||||
throw new InvalidArgumentException($exceptionMessage);
|
||||
}
|
||||
|
||||
$docblock = $docblock->getDocComment();
|
||||
Assert::string($docblock);
|
||||
}
|
||||
|
||||
Assert::stringNotEmpty($docblock);
|
||||
|
||||
$tokens = $this->lexer->tokenize($docblock);
|
||||
$ast = $this->parser->parse(new TokenIterator($tokens));
|
||||
|
||||
$textNodes = [];
|
||||
|
||||
foreach ($ast->children as $child) {
|
||||
if ($child instanceof PhpDocTextNode) {
|
||||
$textNodes[] = $child->text;
|
||||
continue;
|
||||
}
|
||||
|
||||
//If node is not a text node this is the end of description;
|
||||
break;
|
||||
}
|
||||
|
||||
$tags = [];
|
||||
foreach ($ast->getTags() as $node) {
|
||||
switch ($node->name) {
|
||||
case '@param':
|
||||
$tag = $node->value;
|
||||
$tags[] = new Param(
|
||||
ltrim($tag->parameterName, '$'),
|
||||
$this->createType($tag->type, $context),
|
||||
$tag->isVariadic,
|
||||
$this->descriptionFactory->create($tag->description),
|
||||
$tag->isReference
|
||||
);
|
||||
break;
|
||||
case '@return':
|
||||
$tag = $node->value;
|
||||
$tags[] = new Return_(
|
||||
$this->createType($tag->type, $context),
|
||||
$this->descriptionFactory->create($tag->description)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return new DocBlock(
|
||||
'',
|
||||
$this->descriptionFactory->create(
|
||||
implode("\n", $textNodes)
|
||||
),
|
||||
$tags,
|
||||
null,
|
||||
$location
|
||||
);
|
||||
}
|
||||
|
||||
private function createType(TypeNode $type, Context $context)
|
||||
{
|
||||
switch (get_class($type)) {
|
||||
case IdentifierTypeNode::class:
|
||||
return $this->typeResolver->resolve($type->name, $context);
|
||||
case ArrayShapeNode::class:
|
||||
return new ArrayShape(
|
||||
... array_map(
|
||||
fn(ArrayShapeItemNode $item) => new ArrayShapeItem(
|
||||
(string) $item->keyName,
|
||||
$this->createType($item->valueType, $context),
|
||||
$item->optional
|
||||
),
|
||||
$type->items
|
||||
)
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace phpDocumentor\Reflection\PseudoTypes;
|
||||
|
||||
use phpDocumentor\Reflection\PseudoType;
|
||||
use phpDocumentor\Reflection\Type;
|
||||
use phpDocumentor\Reflection\Types\Array_;
|
||||
use phpDocumentor\Reflection\Types\ArrayKey;
|
||||
use phpDocumentor\Reflection\Types\Mixed_;
|
||||
|
||||
class ArrayShape implements PseudoType
|
||||
{
|
||||
/** @var ArrayShapeItem[] */
|
||||
private array $items;
|
||||
|
||||
public function __construct(ArrayShapeItem ...$items)
|
||||
{
|
||||
$this->items = $items;
|
||||
}
|
||||
|
||||
public function underlyingType() : Type
|
||||
{
|
||||
return new Array_(new Mixed_(), new ArrayKey());
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return 'array{' . implode(', ', $this->items) . '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace phpDocumentor\Reflection\PseudoTypes;
|
||||
|
||||
use phpDocumentor\Reflection\Type;
|
||||
|
||||
final class ArrayShapeItem
|
||||
{
|
||||
private ?string $key;
|
||||
private Type $value;
|
||||
private bool $optional;
|
||||
|
||||
public function __construct(?string $key, Type $value, bool $optional)
|
||||
{
|
||||
$this->key = $key;
|
||||
$this->value = $value;
|
||||
$this->optional = $optional;
|
||||
}
|
||||
|
||||
public function getKey() : ?string
|
||||
{
|
||||
return $this->key;
|
||||
}
|
||||
|
||||
public function getValue() : Type
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function isOptional() : bool
|
||||
{
|
||||
return $this->optional;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
if ($this->key !== null) {
|
||||
return sprintf(
|
||||
'%s%s: %s',
|
||||
$this->key,
|
||||
$this->optional ? '?' : '',
|
||||
$this->value
|
||||
);
|
||||
}
|
||||
|
||||
return (string) $this->value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of phpDocumentor.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
* @link http://phpdoc.org
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace phpDocumentor\Reflection;
|
||||
|
||||
use phpDocumentor\Reflection\DocBlock\Description;
|
||||
use phpDocumentor\Reflection\DocBlock\Tags\Param;
|
||||
use phpDocumentor\Reflection\DocBlock\Tags\Return_;
|
||||
use phpDocumentor\Reflection\PseudoTypes\ArrayShape;
|
||||
use phpDocumentor\Reflection\PseudoTypes\ArrayShapeItem;
|
||||
use phpDocumentor\Reflection\Types\Context;
|
||||
use phpDocumentor\Reflection\Types\String_;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class PhpStanDocblockFactoryTest extends TestCase
|
||||
{
|
||||
public function testDocblockIsParsed()
|
||||
{
|
||||
$docblock = new DocBlock(
|
||||
'test summary',
|
||||
new Description(
|
||||
"This description contains tags \n And is multiline"
|
||||
),
|
||||
[
|
||||
new Param('firstParam', new String_(), false, new Description('Some description'), false),
|
||||
new Return_(new ArrayShape(new ArrayShapeItem('foo', new String_(), false)))
|
||||
]
|
||||
);
|
||||
|
||||
$string = <<<DOC
|
||||
/**
|
||||
* test summary
|
||||
*
|
||||
* THis description contains tags {@see https://phpdoc.org with a description}
|
||||
* And is multi line
|
||||
*
|
||||
* @param string \$firstParam Some description
|
||||
* @return array{foo: string}
|
||||
*/
|
||||
DOC;
|
||||
|
||||
|
||||
$factory = PhpStanDocblockFactory::createInstance();
|
||||
$actual = $factory->create(
|
||||
$string,
|
||||
new Context('/')
|
||||
);
|
||||
|
||||
self::assertEquals($docblock, $actual);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user