Skip to content

Commit 44df90a

Browse files
committed
Add optional type and value content parsers
1 parent 4cb76d4 commit 44df90a

6 files changed

+190
-0
lines changed

Diff for: src/Tag/Content.php

+38
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@
44

55
namespace TypeLang\PHPDoc\Tag;
66

7+
use TypeLang\Parser\Exception\ParserExceptionInterface;
78
use TypeLang\Parser\Node\Stmt\TypeStatement;
89
use TypeLang\Parser\ParserInterface as TypesParserInterface;
910
use TypeLang\PHPDoc\Exception\InvalidTagException;
1011
use TypeLang\PHPDoc\Parser\Description\DescriptionParserInterface;
12+
use TypeLang\PHPDoc\Tag\Content\OptionalTypeParserApplicator;
13+
use TypeLang\PHPDoc\Tag\Content\ValueApplicator;
14+
use TypeLang\PHPDoc\Tag\Content\OptionalValueApplicator;
1115
use TypeLang\PHPDoc\Tag\Content\OptionalVariableNameApplicator;
1216
use TypeLang\PHPDoc\Tag\Content\TypeParserApplicator;
1317
use TypeLang\PHPDoc\Tag\Content\VariableNameApplicator;
@@ -67,6 +71,14 @@ public function nextType(string $tag, TypesParserInterface $parser): TypeStateme
6771
return $this->apply(new TypeParserApplicator($tag, $parser));
6872
}
6973

74+
/**
75+
* @api
76+
*/
77+
public function nextOptionalType(TypesParserInterface $parser): ?TypeStatement
78+
{
79+
return $this->apply(new OptionalTypeParserApplicator($parser));
80+
}
81+
7082
/**
7183
* @api
7284
* @param non-empty-string $tag
@@ -86,6 +98,32 @@ public function nextOptionalVariable(): ?string
8698
return $this->apply(new OptionalVariableNameApplicator());
8799
}
88100

101+
/**
102+
* @template T of non-empty-string
103+
*
104+
* @api
105+
* @param non-empty-string $tag
106+
* @param T $value
107+
* @return T
108+
*/
109+
public function nextValue(string $tag, string $value): string
110+
{
111+
return $this->apply(new ValueApplicator($tag, $value));
112+
}
113+
114+
/**
115+
* @template T of non-empty-string
116+
*
117+
* @api
118+
* @param non-empty-string $tag
119+
* @param T $value
120+
* @return T|null
121+
*/
122+
public function nextOptionalValue(string $value): ?string
123+
{
124+
return $this->apply(new OptionalValueApplicator($value));
125+
}
126+
89127
/**
90128
* @template T of mixed
91129
* @param callable(Content):T $applicator

Diff for: src/Tag/Content/OptionalTypeParserApplicator.php

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TypeLang\PHPDoc\Tag\Content;
6+
7+
use TypeLang\Parser\Exception\ParserExceptionInterface;
8+
use TypeLang\Parser\Node\Stmt\TypeStatement;
9+
use TypeLang\Parser\ParserInterface as TypesParserInterface;
10+
use TypeLang\PHPDoc\Exception\InvalidTagException;
11+
use TypeLang\PHPDoc\Tag\Content;
12+
13+
/**
14+
* @template-extends Applicator<TypeStatement|null>
15+
*/
16+
final class OptionalTypeParserApplicator extends Applicator
17+
{
18+
/**
19+
* @param non-empty-string $tag
20+
*/
21+
public function __construct(
22+
private readonly TypesParserInterface $parser,
23+
) {}
24+
25+
/**
26+
* {@inheritDoc}
27+
*
28+
* @throws \Throwable
29+
* @throws InvalidTagException
30+
*/
31+
public function __invoke(Content $lexer): ?TypeStatement
32+
{
33+
try {
34+
$type = $this->parser->parse($lexer->value);
35+
} catch (ParserExceptionInterface $e) {
36+
return null;
37+
}
38+
39+
/**
40+
* @psalm-suppress MixedArgument
41+
* @psalm-suppress NoInterfaceProperties
42+
*/
43+
$lexer->shift($this->parser->lastProcessedTokenOffset);
44+
45+
return $type;
46+
}
47+
}

Diff for: src/Tag/Content/OptionalValueApplicator.php

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TypeLang\PHPDoc\Tag\Content;
6+
7+
use TypeLang\PHPDoc\Tag\Content;
8+
9+
/**
10+
* @template T of non-empty-string
11+
* @template-extends Applicator<T|null>
12+
*/
13+
final class OptionalValueApplicator extends Applicator
14+
{
15+
/**
16+
* @param T $value
17+
*/
18+
public function __construct(
19+
private readonly string $value,
20+
) {}
21+
22+
/**
23+
* @return T|null
24+
*/
25+
public function __invoke(Content $lexer): ?string
26+
{
27+
if (!\str_starts_with($lexer->value, $this->value)) {
28+
return null;
29+
}
30+
31+
$expectedLength = \strlen($this->value);
32+
33+
// In case of end of string
34+
if ($expectedLength === \strlen(\rtrim($lexer->value))) {
35+
return $this->apply($lexer);
36+
}
37+
38+
// In case of separated by whitespace
39+
if (\ctype_space($lexer->value[$expectedLength])) {
40+
return $this->apply($lexer);
41+
}
42+
43+
return null;
44+
}
45+
46+
/**
47+
* @return T
48+
*/
49+
private function apply(Content $lexer): string
50+
{
51+
$lexer->shift(\strlen($this->value));
52+
53+
return $this->value;
54+
}
55+
}

Diff for: src/Tag/Content/TypeParserApplicator.php

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public function __invoke(Content $lexer): TypeStatement
4444

4545
/**
4646
* @psalm-suppress MixedArgument
47+
* @psalm-suppress NoInterfaceProperties
4748
*/
4849
$lexer->shift($this->parser->lastProcessedTokenOffset);
4950

Diff for: src/Tag/Content/ValueApplicator.php

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TypeLang\PHPDoc\Tag\Content;
6+
7+
use TypeLang\PHPDoc\Exception\InvalidTagException;
8+
use TypeLang\PHPDoc\Tag\Content;
9+
10+
/**
11+
* @template T of non-empty-string
12+
* @template-extends Applicator<T>
13+
*/
14+
final class ValueApplicator extends Applicator
15+
{
16+
private readonly OptionalValueApplicator $identifier;
17+
18+
/**
19+
* @param non-empty-string $tag
20+
* @param T $value
21+
*/
22+
public function __construct(
23+
private readonly string $tag,
24+
private readonly string $value
25+
) {
26+
$this->identifier = new OptionalValueApplicator($value);
27+
}
28+
29+
/**
30+
* @return T
31+
*
32+
* @throws InvalidTagException
33+
*/
34+
public function __invoke(Content $lexer): string
35+
{
36+
return ($this->identifier)($lexer)
37+
?? throw $lexer->getTagException(\sprintf(
38+
'Tag @%s contains an incorrect identifier value "%s"',
39+
$this->tag,
40+
$this->value,
41+
));
42+
}
43+
}

Diff for: src/Tag/Description.php

+6
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ public function __construct(
2525
$this->bootTagProvider($tags);
2626
}
2727

28+
/**
29+
* @return ($description is DescriptionInterface ? DescriptionInterface : self)
30+
*/
2831
public static function fromStringable(string|\Stringable $description): DescriptionInterface
2932
{
3033
if ($description instanceof DescriptionInterface) {
@@ -34,6 +37,9 @@ public static function fromStringable(string|\Stringable $description): Descript
3437
return new self($description);
3538
}
3639

40+
/**
41+
* @return ($description is DescriptionInterface ? DescriptionInterface : self|null)
42+
*/
3743
public static function fromStringableOrNull(string|\Stringable|null $description): ?DescriptionInterface
3844
{
3945
if ($description === null) {

0 commit comments

Comments
 (0)