Skip to content

Commit 411cd20

Browse files
committed
add support for "int<min,max>", "negative-int" and "numeric"
integer ranges: - PHPStan: https://phpstan.org/writing-php-code/phpdoc-types#integer-ranges - PhpStorm: https://youtrack.jetbrains.com/issue/WI-63623
1 parent 2a7ec2a commit 411cd20

8 files changed

+422
-2
lines changed

src/PseudoTypes/IntegerRange.php

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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 'string'.
22+
*
23+
* @psalm-immutable
24+
*/
25+
final class IntegerRange extends Integer implements PseudoType
26+
{
27+
private $minValue;
28+
private $maxValue;
29+
30+
public function __construct(string $minValue, string $maxValue)
31+
{
32+
$this->minValue = $minValue;
33+
$this->maxValue = $maxValue;
34+
}
35+
36+
public function underlyingType(): Type
37+
{
38+
return new Integer();
39+
}
40+
41+
public function getMinValue(): string
42+
{
43+
return $this->minValue;
44+
}
45+
46+
public function getMaxValue(): string
47+
{
48+
return $this->maxValue;
49+
}
50+
51+
/**
52+
* Returns a rendered output of the Type as it would be used in a DocBlock.
53+
*/
54+
public function __toString(): string
55+
{
56+
return 'int<' . $this->minValue . ', ' . $this->maxValue . '>';
57+
}
58+
}

src/PseudoTypes/NegativeInteger.php

Lines changed: 39 additions & 0 deletions
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 'string'.
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/TypeResolver.php

Lines changed: 75 additions & 0 deletions
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;
@@ -40,6 +41,7 @@
4041
use function count;
4142
use function end;
4243
use function in_array;
44+
use function is_numeric;
4345
use function key;
4446
use function preg_split;
4547
use function strpos;
@@ -82,10 +84,12 @@ final class TypeResolver
8284
'non-empty-lowercase-string' => PseudoTypes\NonEmptyLowercaseString::class,
8385
'non-empty-string' => PseudoTypes\NonEmptyString::class,
8486
'numeric-string' => PseudoTypes\NumericString::class,
87+
'numeric' => Types\Numeric::class,
8588
'trait-string' => PseudoTypes\TraitString::class,
8689
'int' => Types\Integer::class,
8790
'integer' => Types\Integer::class,
8891
'positive-int' => PseudoTypes\PositiveInteger::class,
92+
'negative-int' => PseudoTypes\NegativeInteger::class,
8993
'bool' => Types\Boolean::class,
9094
'boolean' => Types\Boolean::class,
9195
'real' => Types\Float_::class,
@@ -257,6 +261,8 @@ private function parseTypes(ArrayIterator $tokens, Context $context, int $parser
257261
if ($classType !== null) {
258262
if ((string) $classType === 'class-string') {
259263
$types[] = $this->resolveClassString($tokens, $context);
264+
} elseif ((string) $classType === 'int') {
265+
$types[] = $this->resolveIntRange($tokens, $context);
260266
} elseif ((string) $classType === 'interface-string') {
261267
$types[] = $this->resolveInterfaceString($tokens, $context);
262268
} else {
@@ -479,6 +485,75 @@ private function resolveClassString(ArrayIterator $tokens, Context $context): Ty
479485
return new ClassString($classType->getFqsen());
480486
}
481487

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

src/Types/Numeric.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+
/**
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\Types;
15+
16+
use phpDocumentor\Reflection\Type;
17+
18+
/**
19+
* Value Object representing the 'numeric' pseudo-type, which is either a numeric-string, integer or float.
20+
*
21+
* @psalm-immutable
22+
*/
23+
final class Numeric implements Type
24+
{
25+
/**
26+
* Returns a rendered output of the Type as it would be used in a DocBlock.
27+
*/
28+
public function __toString(): string
29+
{
30+
return 'numeric';
31+
}
32+
}

tests/unit/CollectionResolverTest.php

Lines changed: 8 additions & 2 deletions
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)