Skip to content

Commit 3372a76

Browse files
wip
1 parent af8fe00 commit 3372a76

File tree

3 files changed

+163
-15
lines changed

3 files changed

+163
-15
lines changed

src/Analyzer/Docblock.php

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Arkitect\Analyzer;
6+
7+
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
8+
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
9+
use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
10+
use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
11+
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
12+
13+
class Docblock
14+
{
15+
private PhpDocNode $phpDocNode;
16+
17+
public function __construct(PhpDocNode $phpDocNode)
18+
{
19+
$this->phpDocNode = $phpDocNode;
20+
}
21+
22+
public function getParamTagTypesByName(string $name): ?string
23+
{
24+
foreach ($this->phpDocNode->getParamTagValues() as $paramTag) {
25+
if ($paramTag->parameterName === $name) {
26+
return $this->getType($paramTag->type);
27+
}
28+
}
29+
30+
return null;
31+
}
32+
33+
public function getReturnTagTypes(): array
34+
{
35+
$returnTypes = array_map(
36+
fn (ReturnTagValueNode $returnTag) => $this->getType($returnTag->type),
37+
$this->phpDocNode->getReturnTagValues()
38+
);
39+
40+
// remove null values
41+
return array_filter($returnTypes);
42+
}
43+
44+
private function getType(TypeNode $typeNode): ?string
45+
{
46+
if ($typeNode instanceof GenericTypeNode) {
47+
// this handles list<ClassName>
48+
if (1 === \count($typeNode->genericTypes)) {
49+
return (string) $typeNode->genericTypes[0];
50+
}
51+
52+
// this handles array<int, ClassName>
53+
if (2 === \count($typeNode->genericTypes)) {
54+
return (string) $typeNode->genericTypes[1];
55+
}
56+
}
57+
58+
// this handles ClassName[]
59+
if ($typeNode instanceof ArrayTypeNode) {
60+
return (string) $typeNode->type;
61+
}
62+
63+
return null;
64+
}
65+
}

src/Analyzer/DocblockTypesResolver.php

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -133,32 +133,37 @@ private function resolveFunctionTypes(Node $node): void
133133
return;
134134
}
135135

136+
$docblock = new Docblock($phpDocNode);
137+
138+
// extract param types from param tags
136139
foreach ($node->params as $param) {
137-
if (!$this->isNodeOfTypeArray($param)) { // not an array, nothing to do
140+
if (!$this->isTypeArray($param->type)) { // not an array, nothing to do
138141
continue;
139142
}
140143

141-
foreach ($phpDocNode->getParamTagValues() as $phpDocParam) {
142-
if ($param->var instanceof Expr\Variable && \is_string($param->var->name) && $phpDocParam->parameterName === ('$'.$param->var->name)) {
143-
$arrayItemType = $this->getArrayItemType($phpDocParam->type);
144+
if (!($param->var instanceof Expr\Variable) || !\is_string($param->var->name)) {
145+
continue;
146+
}
144147

145-
if (null !== $arrayItemType) {
146-
$param->type = $this->resolveName(new Name($arrayItemType), Stmt\Use_::TYPE_NORMAL);
147-
}
148-
}
148+
$type = $docblock->getParamTagTypesByName('$'.$param->var->name);
149+
150+
if (null === $type) {
151+
continue;
149152
}
153+
154+
$param->type = $this->resolveName(new Name($type), Stmt\Use_::TYPE_NORMAL);
150155
}
151156

152-
if ($node->returnType instanceof Node\Identifier && 'array' === $node->returnType->name) {
153-
$arrayItemType = null;
157+
// extract return type from return tag
158+
if ($this->isTypeArray($node->returnType)) {
159+
$type = $docblock->getReturnTagTypes();
160+
$type = array_pop($type);
154161

155-
foreach ($phpDocNode->getReturnTagValues() as $tagValue) {
156-
$arrayItemType = $this->getArrayItemType($tagValue->type);
162+
if (null === $type) {
163+
return;
157164
}
158165

159-
if (null !== $arrayItemType) {
160-
$node->returnType = $this->resolveName(new Name($arrayItemType), Stmt\Use_::TYPE_NORMAL);
161-
}
166+
$node->returnType = $this->resolveName(new Name($type), Stmt\Use_::TYPE_NORMAL);
162167
}
163168
}
164169

@@ -238,6 +243,14 @@ private function isNodeOfTypeArray($node): bool
238243
return null !== $node->type && isset($node->type->name) && 'array' === $node->type->name;
239244
}
240245

246+
/**
247+
* @param Node\Identifier|Name|Node\ComplexType|null $type
248+
*/
249+
private function isTypeArray($type): bool
250+
{
251+
return null !== $type && isset($type->name) && 'array' === $type->name;
252+
}
253+
241254
private function getArrayItemType(TypeNode $typeNode): ?string
242255
{
243256
$arrayItemType = null;
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Arkitect\Tests\Unit\Analyzer;
6+
7+
use Arkitect\Analyzer\Docblock;
8+
use Arkitect\Analyzer\DocblockParserFactory;
9+
use PHPUnit\Framework\TestCase;
10+
11+
class DocblockParserTest extends TestCase
12+
{
13+
public function test_it_should_exctract_types_from_param_tag(): void
14+
{
15+
$parser = DocblockParserFactory::create();
16+
17+
$code = <<< 'PHP'
18+
/**
19+
* @param MyDto[] $dtoList
20+
* @param list<MyOtherDto> $dtoList2
21+
* @param array<int, ValueObject> $voList
22+
* @param array<User> $user
23+
* @param array<User> $user
24+
* @param int $aValue
25+
* @param MyPlainDto $plainDto
26+
*/
27+
}
28+
PHP;
29+
30+
$phpdoc = $parser->parse($code);
31+
32+
$db = new Docblock($phpdoc);
33+
34+
self::assertEquals('MyDto', $db->getParamTagTypesByName('$dtoList'));
35+
self::assertEquals('MyOtherDto', $db->getParamTagTypesByName('$dtoList2'));
36+
self::assertEquals('ValueObject', $db->getParamTagTypesByName('$voList'));
37+
self::assertEquals('User', $db->getParamTagTypesByName('$user'));
38+
39+
self::assertNull($db->getParamTagTypesByName('$aValue'));
40+
self::assertNull($db->getParamTagTypesByName('$plainDto'));
41+
}
42+
43+
public function test_it_should_extract_return_type_from_return_tag(): void
44+
{
45+
$parser = DocblockParserFactory::create();
46+
47+
$code = <<< 'PHP'
48+
/**
49+
* @return MyDto[]
50+
* @return list<MyOtherDto>
51+
* @return array<int, ValueObject>
52+
* @return array<User>
53+
* @return int
54+
* @return MyPlainDto
55+
*/
56+
}
57+
PHP;
58+
59+
$phpdoc = $parser->parse($code);
60+
61+
$db = new Docblock($phpdoc);
62+
63+
$returnTypes = $db->getReturnTagTypes();
64+
self::assertCount(4, $returnTypes);
65+
self::assertEquals('MyDto', $returnTypes[0]);
66+
self::assertEquals('MyOtherDto', $returnTypes[1]);
67+
self::assertEquals('ValueObject', $returnTypes[2]);
68+
self::assertEquals('User', $returnTypes[3]);
69+
}
70+
}

0 commit comments

Comments
 (0)