Skip to content

Commit 9f13ce1

Browse files
authored
Merge pull request #139 from voku/int_range
add support for "int<min,max>", "negative-int" and "numeric"
2 parents 67665c5 + 9c0394b commit 9f13ce1

10 files changed

+489
-3
lines changed

src/PseudoTypes/IntegerRange.php

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of phpDocumentor.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*
11+
* @link http://phpdoc.org
12+
*/
13+
14+
namespace phpDocumentor\Reflection\PseudoTypes;
15+
16+
use phpDocumentor\Reflection\PseudoType;
17+
use phpDocumentor\Reflection\Type;
18+
use phpDocumentor\Reflection\Types\Integer;
19+
20+
/**
21+
* Value Object representing the type 'int'.
22+
*
23+
* @psalm-immutable
24+
*/
25+
final class IntegerRange extends Integer implements PseudoType
26+
{
27+
/** @var string */
28+
private $minValue;
29+
30+
/** @var string */
31+
private $maxValue;
32+
33+
public function __construct(string $minValue, string $maxValue)
34+
{
35+
$this->minValue = $minValue;
36+
$this->maxValue = $maxValue;
37+
}
38+
39+
public function underlyingType(): Type
40+
{
41+
return new Integer();
42+
}
43+
44+
public function getMinValue(): string
45+
{
46+
return $this->minValue;
47+
}
48+
49+
public function getMaxValue(): string
50+
{
51+
return $this->maxValue;
52+
}
53+
54+
/**
55+
* Returns a rendered output of the Type as it would be used in a DocBlock.
56+
*/
57+
public function __toString(): string
58+
{
59+
return 'int<' . $this->minValue . ', ' . $this->maxValue . '>';
60+
}
61+
}

src/PseudoTypes/NegativeInteger.php

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of phpDocumentor.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*
11+
* @link http://phpdoc.org
12+
*/
13+
14+
namespace phpDocumentor\Reflection\PseudoTypes;
15+
16+
use phpDocumentor\Reflection\PseudoType;
17+
use phpDocumentor\Reflection\Type;
18+
use phpDocumentor\Reflection\Types\Integer;
19+
20+
/**
21+
* Value Object representing the type 'int'.
22+
*
23+
* @psalm-immutable
24+
*/
25+
final class NegativeInteger extends Integer implements PseudoType
26+
{
27+
public function underlyingType(): Type
28+
{
29+
return new Integer();
30+
}
31+
32+
/**
33+
* Returns a rendered output of the Type as it would be used in a DocBlock.
34+
*/
35+
public function __toString(): string
36+
{
37+
return 'negative-int';
38+
}
39+
}

src/PseudoTypes/Numeric_.php

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of phpDocumentor.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*
11+
* @link http://phpdoc.org
12+
*/
13+
14+
namespace phpDocumentor\Reflection\PseudoTypes;
15+
16+
use phpDocumentor\Reflection\PseudoType;
17+
use phpDocumentor\Reflection\Type;
18+
use phpDocumentor\Reflection\Types\AggregatedType;
19+
use phpDocumentor\Reflection\Types\Compound;
20+
use phpDocumentor\Reflection\Types\Float_;
21+
use phpDocumentor\Reflection\Types\Integer;
22+
23+
/**
24+
* Value Object representing the 'numeric' pseudo-type, which is either a numeric-string, integer or float.
25+
*
26+
* @psalm-immutable
27+
*/
28+
final class Numeric_ extends AggregatedType implements PseudoType
29+
{
30+
public function __construct()
31+
{
32+
AggregatedType::__construct([new NumericString(), new Integer(), new Float_()], '|');
33+
}
34+
35+
public function underlyingType(): Type
36+
{
37+
return new Compound([new NumericString(), new Integer(), new Float_()]);
38+
}
39+
40+
/**
41+
* Returns a rendered output of the Type as it would be used in a DocBlock.
42+
*/
43+
public function __toString(): string
44+
{
45+
return 'numeric';
46+
}
47+
}

src/PseudoTypes/PositiveInteger.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
use phpDocumentor\Reflection\Types\Integer;
1919

2020
/**
21-
* Value Object representing the type 'string'.
21+
* Value Object representing the type 'int'.
2222
*
2323
* @psalm-immutable
2424
*/

src/TypeResolver.php

+75
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
use ArrayIterator;
1717
use InvalidArgumentException;
18+
use phpDocumentor\Reflection\PseudoTypes\IntegerRange;
1819
use phpDocumentor\Reflection\PseudoTypes\List_;
1920
use phpDocumentor\Reflection\Types\Array_;
2021
use phpDocumentor\Reflection\Types\ArrayKey;
@@ -41,6 +42,7 @@
4142
use function current;
4243
use function end;
4344
use function in_array;
45+
use function is_numeric;
4446
use function key;
4547
use function preg_split;
4648
use function strpos;
@@ -83,10 +85,12 @@ final class TypeResolver
8385
'non-empty-lowercase-string' => PseudoTypes\NonEmptyLowercaseString::class,
8486
'non-empty-string' => PseudoTypes\NonEmptyString::class,
8587
'numeric-string' => PseudoTypes\NumericString::class,
88+
'numeric' => PseudoTypes\Numeric_::class,
8689
'trait-string' => PseudoTypes\TraitString::class,
8790
'int' => Types\Integer::class,
8891
'integer' => Types\Integer::class,
8992
'positive-int' => PseudoTypes\PositiveInteger::class,
93+
'negative-int' => PseudoTypes\NegativeInteger::class,
9094
'bool' => Types\Boolean::class,
9195
'boolean' => Types\Boolean::class,
9296
'real' => Types\Float_::class,
@@ -258,6 +262,8 @@ private function parseTypes(ArrayIterator $tokens, Context $context, int $parser
258262
if ($classType !== null) {
259263
if ((string) $classType === 'class-string') {
260264
$types[] = $this->resolveClassString($tokens, $context);
265+
} elseif ((string) $classType === 'int') {
266+
$types[] = $this->resolveIntRange($tokens);
261267
} elseif ((string) $classType === 'interface-string') {
262268
$types[] = $this->resolveInterfaceString($tokens, $context);
263269
} else {
@@ -484,6 +490,75 @@ private function resolveClassString(ArrayIterator $tokens, Context $context): Ty
484490
return new ClassString($classType->getFqsen());
485491
}
486492

493+
/**
494+
* Resolves integer ranges
495+
*
496+
* @param ArrayIterator<int, (string|null)> $tokens
497+
*/
498+
private function resolveIntRange(ArrayIterator $tokens): Type
499+
{
500+
$tokens->next();
501+
502+
$token = '';
503+
$minValue = null;
504+
$maxValue = null;
505+
$commaFound = false;
506+
$tokenCounter = 0;
507+
while ($tokens->valid()) {
508+
$tokenCounter++;
509+
$token = $tokens->current();
510+
if ($token === null) {
511+
throw new RuntimeException(
512+
'Unexpected nullable character'
513+
);
514+
}
515+
516+
$token = trim($token);
517+
518+
if ($token === '>') {
519+
break;
520+
}
521+
522+
if ($token === ',') {
523+
$commaFound = true;
524+
}
525+
526+
if ($commaFound === false && $minValue === null) {
527+
if (is_numeric($token) || $token === 'max' || $token === 'min') {
528+
$minValue = $token;
529+
}
530+
}
531+
532+
if ($commaFound === true && $maxValue === null) {
533+
if (is_numeric($token) || $token === 'max' || $token === 'min') {
534+
$maxValue = $token;
535+
}
536+
}
537+
538+
$tokens->next();
539+
}
540+
541+
if ($token !== '>') {
542+
if (empty($token)) {
543+
throw new RuntimeException(
544+
'interface-string: ">" is missing'
545+
);
546+
}
547+
548+
throw new RuntimeException(
549+
'Unexpected character "' . $token . '", ">" is missing'
550+
);
551+
}
552+
553+
if (!$minValue || !$maxValue || $tokenCounter > 4) {
554+
throw new RuntimeException(
555+
'int<min,max> has not the correct format'
556+
);
557+
}
558+
559+
return new IntegerRange($minValue, $maxValue);
560+
}
561+
487562
/**
488563
* Resolves class string
489564
*

tests/unit/CollectionResolverTest.php

+8-2
Original file line numberDiff line numberDiff line change
@@ -231,10 +231,16 @@ public function testBadArrayCollectionKey(): void
231231
public function testGoodArrayCollectionKey(): void
232232
{
233233
$fixture = new TypeResolver();
234-
$fixture->resolve('array<array-key,string>', new Context(''));
234+
$resolvedType = $fixture->resolve('array<array-key,string>', new Context(''));
235+
236+
$this->assertInstanceOf(Array_::class, $resolvedType);
237+
$this->assertSame('array<array-key,string>', (string) $resolvedType);
235238

236239
$fixture = new TypeResolver();
237-
$fixture->resolve('array<class-string,string>', new Context(''));
240+
$resolvedType = $fixture->resolve('array<class-string,string>', new Context(''));
241+
242+
$this->assertInstanceOf(Array_::class, $resolvedType);
243+
$this->assertSame('array<class-string,string>', (string) $resolvedType);
238244
}
239245

240246
/**

0 commit comments

Comments
 (0)