Skip to content

Commit 7192e67

Browse files
committed
First POC steps
1 parent 203354b commit 7192e67

File tree

8 files changed

+378
-4
lines changed

8 files changed

+378
-4
lines changed

composer.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@
1414
}
1515
],
1616
"require": {
17-
"php": "^7.2 || ^8.0",
17+
"php": "^7.4 || ^8.0",
1818
"phpdocumentor/type-resolver": "^1.3",
1919
"webmozart/assert": "^1.9.1",
2020
"phpdocumentor/reflection-common": "^2.2",
21-
"ext-filter": "*"
21+
"ext-filter": "*",
22+
"phpstan/phpdoc-parser": "^1.7"
2223
},
2324
"require-dev": {
2425
"mockery/mockery": "~1.3.5",

composer.lock

Lines changed: 46 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/DocBlockFactoryInterface.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ interface DocBlockFactoryInterface
1414
*
1515
* @param array<string, class-string<Tag>> $additionalTags
1616
*/
17-
public static function createInstance(array $additionalTags = []): DocBlockFactory;
17+
public static function createInstance(array $additionalTags = []): self;
1818

1919
/**
2020
* @param string|object $docblock

src/PhpStan/TagFactory.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace phpDocumentor\Reflection\PhpStan;
6+
7+
use phpDocumentor\Reflection\DocBlock\Tag;
8+
use phpDocumentor\Reflection\DocBlock\TagFactory as TagFactoryInterface;
9+
use phpDocumentor\Reflection\DocBlock\Tags\InvalidTag;
10+
use phpDocumentor\Reflection\Types\Context as TypeContext;
11+
12+
class TagFactory implements TagFactoryInterface
13+
{
14+
public function addParameter(string $name, $value) : void
15+
{
16+
// TODO: Implement addParameter() method.
17+
}
18+
19+
public function create(string $tagLine, ?TypeContext $context = null) : Tag
20+
{
21+
return InvalidTag::create($tagLine);
22+
}
23+
24+
public function addService(object $service) : void
25+
{
26+
// TODO: Implement addService() method.
27+
}
28+
29+
public function registerTagHandler(string $tagName, string $handler) : void
30+
{
31+
// TODO: Implement registerTagHandler() method.
32+
}
33+
}

src/PhpStanDocblockFactory.php

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
<?php
2+
/**
3+
* This file is part of phpDocumentor.
4+
*
5+
* For the full copyright and license information, please view the LICENSE
6+
* file that was distributed with this source code.
7+
*
8+
* @link http://phpdoc.org
9+
*
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace phpDocumentor\Reflection;
15+
16+
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
17+
use phpDocumentor\Reflection\DocBlock\StandardTagFactory;
18+
use phpDocumentor\Reflection\DocBlock\Tags\Param;
19+
use phpDocumentor\Reflection\DocBlock\Tags\Return_;
20+
use phpDocumentor\Reflection\PhpStan\TagFactory;
21+
use phpDocumentor\Reflection\PseudoTypes\ArrayShape;
22+
use phpDocumentor\Reflection\PseudoTypes\ArrayShapeItem;
23+
use phpDocumentor\Reflection\Types\Context;
24+
use PHPStan\PhpDoc\Tag\ParamTag;
25+
use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
26+
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
27+
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTextNode;
28+
use PHPStan\PhpDocParser\Ast\Type\ArrayShapeItemNode;
29+
use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode;
30+
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
31+
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
32+
use PHPStan\PhpDocParser\Lexer\Lexer;
33+
use PHPStan\PhpDocParser\Parser\ConstExprParser;
34+
use PHPStan\PhpDocParser\Parser\PhpDocParser;
35+
use PHPStan\PhpDocParser\Parser\TokenIterator;
36+
use PHPStan\PhpDocParser\Parser\TypeParser;
37+
use Webmozart\Assert\Assert;
38+
use InvalidArgumentException;
39+
use LogicException;
40+
41+
final class PhpStanDocblockFactory implements DocBlockFactoryInterface
42+
{
43+
private PhpDocParser $parser;
44+
private Lexer $lexer;
45+
private DescriptionFactory $descriptionFactory;
46+
private TypeResolver $typeResolver;
47+
48+
private function __construct()
49+
{
50+
}
51+
52+
public static function createInstance(array $additionalTags = []): DocBlockFactoryInterface
53+
{
54+
$fqsenResolver = new FqsenResolver();
55+
$constExprParser = new ConstExprParser();
56+
$self = new self();
57+
$self->lexer = new Lexer();
58+
$self->parser = new PhpDocParser(
59+
new TypeParser($constExprParser),
60+
$constExprParser
61+
);
62+
63+
$self->descriptionFactory = new DescriptionFactory(new TagFactory());
64+
$self->typeResolver = new TypeResolver($fqsenResolver);
65+
66+
return $self;
67+
}
68+
69+
public function create($docblock, ?Types\Context $context = null, ?Location $location = null): DocBlock
70+
{
71+
if (is_object($docblock)) {
72+
if (!method_exists($docblock, 'getDocComment')) {
73+
$exceptionMessage = 'Invalid object passed; the given object must support the getDocComment method';
74+
75+
throw new InvalidArgumentException($exceptionMessage);
76+
}
77+
78+
$docblock = $docblock->getDocComment();
79+
Assert::string($docblock);
80+
}
81+
82+
Assert::stringNotEmpty($docblock);
83+
84+
$tokens = $this->lexer->tokenize($docblock);
85+
$ast = $this->parser->parse(new TokenIterator($tokens));
86+
87+
$textNodes = [];
88+
89+
foreach ($ast->children as $child) {
90+
if ($child instanceof PhpDocTextNode) {
91+
$textNodes[] = $child->text;
92+
continue;
93+
}
94+
95+
//If node is not a text node this is the end of description;
96+
break;
97+
}
98+
99+
$tags = [];
100+
foreach ($ast->getTags() as $node) {
101+
switch ($node->name) {
102+
case '@param':
103+
$tag = $node->value;
104+
$tags[] = new Param(
105+
ltrim($tag->parameterName, '$'),
106+
$this->createType($tag->type, $context),
107+
$tag->isVariadic,
108+
$this->descriptionFactory->create($tag->description),
109+
$tag->isReference
110+
);
111+
break;
112+
case '@return':
113+
$tag = $node->value;
114+
$tags[] = new Return_(
115+
$this->createType($tag->type, $context),
116+
$this->descriptionFactory->create($tag->description)
117+
);
118+
}
119+
}
120+
121+
return new DocBlock(
122+
'',
123+
$this->descriptionFactory->create(
124+
implode("\n", $textNodes)
125+
),
126+
$tags,
127+
null,
128+
$location
129+
);
130+
}
131+
132+
private function createType(TypeNode $type, Context $context)
133+
{
134+
switch (get_class($type)) {
135+
case IdentifierTypeNode::class:
136+
return $this->typeResolver->resolve($type->name, $context);
137+
case ArrayShapeNode::class:
138+
return new ArrayShape(
139+
... array_map(
140+
fn(ArrayShapeItemNode $item) => new ArrayShapeItem(
141+
(string) $item->keyName,
142+
$this->createType($item->valueType, $context),
143+
$item->optional
144+
),
145+
$type->items
146+
)
147+
);
148+
default:
149+
return null;
150+
}
151+
}
152+
}

src/PseudoTypes/ArrayShape.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace phpDocumentor\Reflection\PseudoTypes;
6+
7+
use phpDocumentor\Reflection\PseudoType;
8+
use phpDocumentor\Reflection\Type;
9+
use phpDocumentor\Reflection\Types\Array_;
10+
use phpDocumentor\Reflection\Types\ArrayKey;
11+
use phpDocumentor\Reflection\Types\Mixed_;
12+
13+
class ArrayShape implements PseudoType
14+
{
15+
/** @var ArrayShapeItem[] */
16+
private array $items;
17+
18+
public function __construct(ArrayShapeItem ...$items)
19+
{
20+
$this->items = $items;
21+
}
22+
23+
public function underlyingType() : Type
24+
{
25+
return new Array_(new Mixed_(), new ArrayKey());
26+
}
27+
28+
public function __toString(): string
29+
{
30+
return 'array{' . implode(', ', $this->items) . '}';
31+
}
32+
}

src/PseudoTypes/ArrayShapeItem.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace phpDocumentor\Reflection\PseudoTypes;
6+
7+
use phpDocumentor\Reflection\Type;
8+
9+
final class ArrayShapeItem
10+
{
11+
private ?string $key;
12+
private Type $value;
13+
private bool $optional;
14+
15+
public function __construct(?string $key, Type $value, bool $optional)
16+
{
17+
$this->key = $key;
18+
$this->value = $value;
19+
$this->optional = $optional;
20+
}
21+
22+
public function getKey() : ?string
23+
{
24+
return $this->key;
25+
}
26+
27+
public function getValue() : Type
28+
{
29+
return $this->value;
30+
}
31+
32+
public function isOptional() : bool
33+
{
34+
return $this->optional;
35+
}
36+
37+
public function __toString()
38+
{
39+
if ($this->key !== null) {
40+
return sprintf(
41+
'%s%s: %s',
42+
$this->key,
43+
$this->optional ? '?' : '',
44+
$this->value
45+
);
46+
}
47+
48+
return (string) $this->value;
49+
}
50+
}

0 commit comments

Comments
 (0)