Skip to content

Commit d833633

Browse files
author
Kirill Nesmeyanov
committed
Add template tag support
1 parent 10997dd commit d833633

File tree

11 files changed

+195
-18
lines changed

11 files changed

+195
-18
lines changed

src/DocBlock/Extractor/TagTypeExtractor.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,18 @@ public function extractTypeOrMixed(string $body): array
5050
}
5151
}
5252

53+
/**
54+
* @return array{TypeStatement|null, non-empty-string|null}
55+
*/
56+
public function extractTypeOrNull(string $body): array
57+
{
58+
try {
59+
return $this->extractTypeOrFail($body);
60+
} catch (InvalidTagTypeException) {
61+
return [null, $body ?: null];
62+
}
63+
}
64+
5365
protected function createMixedType(): NamedTypeNode
5466
{
5567
return self::$mixed ??= new NamedTypeNode(

src/DocBlock/Tag/InvalidTag.php

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,21 @@
77
/**
88
* This tag is created if a parsing error occurs while parsing the original tag.
99
*/
10-
final class InvalidTag extends Tag implements InvalidTagInterface {}
10+
final class InvalidTag extends Tag implements InvalidTagInterface
11+
{
12+
/**
13+
* @param non-empty-string $name
14+
*/
15+
public function __construct(
16+
string $name,
17+
private readonly \Throwable $reason,
18+
\Stringable|string|null $description = null,
19+
) {
20+
parent::__construct($name, $description);
21+
}
22+
23+
public function getReason(): \Throwable
24+
{
25+
return $this->reason;
26+
}
27+
}

src/DocBlock/Tag/InvalidTagInterface.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,10 @@
88
* Marks all tags that were not correctly recognized by the parser or for
99
* some other reason were not processed and classified.
1010
*/
11-
interface InvalidTagInterface extends TagInterface {}
11+
interface InvalidTagInterface extends TagInterface
12+
{
13+
/**
14+
* Returns {@see InvalidTagInterface} invalid tag creation reason.
15+
*/
16+
public function getReason(): \Throwable;
17+
}

src/DocBlock/Tag/InvalidTypedTag.php

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,22 @@
1010
* This tag is created if a parsing error occurs while parsing the original
1111
* tag that contain {@see TypeStatement} reference.
1212
*/
13-
final class InvalidTypedTag extends TypedTag implements InvalidTagInterface {}
13+
final class InvalidTypedTag extends TypedTag implements InvalidTagInterface
14+
{
15+
/**
16+
* @param non-empty-string $name
17+
*/
18+
public function __construct(
19+
string $name,
20+
TypeStatement $type,
21+
private readonly \Throwable $reason,
22+
\Stringable|string|null $description = null
23+
) {
24+
parent::__construct($name, $type, $description);
25+
}
26+
27+
public function getReason(): \Throwable
28+
{
29+
return $this->reason;
30+
}
31+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TypeLang\PhpDocParser\DocBlock\Tag;
6+
7+
use TypeLang\Parser\Node\Stmt\TypeStatement;
8+
9+
interface OptionalTypeProviderInterface extends TagInterface
10+
{
11+
/**
12+
* @psalm-immutable
13+
*/
14+
public function getType(): ?TypeStatement;
15+
}

src/DocBlock/Tag/TemplateTag.php

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TypeLang\PhpDocParser\DocBlock\Tag;
6+
7+
use TypeLang\Parser\Node\Stmt\TypeStatement;
8+
9+
final class TemplateTag extends Tag implements OptionalTypeProviderInterface
10+
{
11+
/**
12+
* @param non-empty-string $typeName
13+
* @param TypeStatement|null $type
14+
* @param \Stringable|string|null $description
15+
*/
16+
public function __construct(
17+
private readonly string $typeName,
18+
private readonly ?TypeStatement $type = null,
19+
\Stringable|string|null $description = null
20+
) {
21+
parent::__construct('template', $description);
22+
}
23+
24+
/**
25+
* @return non-empty-string
26+
*/
27+
public function getTypeName(): string
28+
{
29+
return $this->typeName;
30+
}
31+
32+
public function getType(): ?TypeStatement
33+
{
34+
return $this->type;
35+
}
36+
37+
/**
38+
* @psalm-immutable
39+
*/
40+
public function __toString(): string
41+
{
42+
if ($this->type === null) {
43+
return \rtrim(\vsprintf('@%s %s %s', [
44+
$this->name,
45+
$this->typeName,
46+
(string)$this->description,
47+
]));
48+
}
49+
50+
return \rtrim(\vsprintf('@%s %s of %s %s', [
51+
$this->name,
52+
$this->typeName,
53+
TypedTag::getTypeAsString($this->type),
54+
(string)$this->description,
55+
]));
56+
}
57+
}

src/DocBlock/Tag/TypeProviderInterface.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
use TypeLang\Parser\Node\Stmt\TypeStatement;
88

9-
interface TypeProviderInterface extends TagInterface
9+
interface TypeProviderInterface extends OptionalTypeProviderInterface
1010
{
1111
/**
1212
* @psalm-immutable

src/DocBlock/Tag/TypedTag.php

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,25 +27,30 @@ public function getType(): TypeStatement
2727
}
2828

2929
/**
30-
* @psalm-immutable
30+
* @return non-empty-string
3131
*/
32-
public function __toString(): string
32+
public static function getTypeAsString(TypeStatement $type): string
3333
{
34-
$type = $this->type::class;
34+
if (\class_exists(PrettyPrinter::class)) {
35+
return (new PrettyPrinter())->print($type);
36+
}
3537

3638
/** @psalm-suppress UndefinedPropertyFetch : Psalm false-positive */
37-
if (\property_exists($this->type, 'name')
38-
&& $this->type->name instanceof Name) {
39-
$type = (string)$this->type->name;
39+
if (\property_exists($type, 'name') && $type->name instanceof Name) {
40+
return (string)$type->name;
4041
}
4142

42-
if (\class_exists(PrettyPrinter::class)) {
43-
$type = (new PrettyPrinter())->print($this->type);
44-
}
43+
return $type::class;
44+
}
4545

46+
/**
47+
* @psalm-immutable
48+
*/
49+
public function __toString(): string
50+
{
4651
return \rtrim(\vsprintf('@%s %s %s', [
4752
$this->name,
48-
$type,
53+
self::getTypeAsString($this->type),
4954
(string)$this->description,
5055
]));
5156
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TypeLang\PhpDocParser\DocBlock\TagFactory;
6+
7+
use TypeLang\PhpDocParser\DocBlock\Tag\TemplateTag;
8+
9+
/**
10+
* @template-extends TypedTagFactory<TemplateTag>
11+
*/
12+
final class TemplateTagFactory extends TypedTagFactory
13+
{
14+
public function create(string $tag): TemplateTag
15+
{
16+
if (\trim($tag) === '') {
17+
throw new \InvalidArgumentException('Template parameter name required');
18+
}
19+
20+
$suffix = \strpbrk($tag, " \t\n\r\0\x0B");
21+
22+
if ($suffix === false) {
23+
return new TemplateTag($tag);
24+
}
25+
26+
$typeName = \substr($tag, 0, -\strlen($suffix));
27+
28+
\preg_match('/^\s+of\s+(.+?)$/isum', $suffix, $matches);
29+
30+
if ($matches === []) {
31+
return new TemplateTag(
32+
typeName: $typeName,
33+
description: $this->createDescription($suffix),
34+
);
35+
}
36+
37+
[$type, $description] = $this->types->extractTypeOrNull($matches[1]);
38+
39+
return new TemplateTag(
40+
typeName: $typeName,
41+
type: $type,
42+
description: $this->createDescription($description),
43+
);
44+
}
45+
}

src/DocBlock/TagFactorySelector.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,12 @@ public function create(string $tag): TagInterface
6666
$body = $this->createDescription(\substr($body, $e->getTypeOffset()));
6767

6868
if ($type === null) {
69-
return new InvalidTag($name, $body);
69+
return new InvalidTag($name, $e, $body);
7070
}
7171

72-
return new InvalidTypedTag($name, $type, $body);
73-
} catch (\InvalidArgumentException) {
74-
return new InvalidTag($name, $this->createDescription($body));
72+
return new InvalidTypedTag($name, $type, $e, $body);
73+
} catch (\InvalidArgumentException $e) {
74+
return new InvalidTag($name, $e, $this->createDescription($body));
7575
}
7676
}
7777

0 commit comments

Comments
 (0)