Skip to content

Commit

Permalink
Add support for object and list shape types
Browse files Browse the repository at this point in the history
  • Loading branch information
jaapio committed Mar 22, 2024
1 parent 153ae66 commit c92bb85
Show file tree
Hide file tree
Showing 12 changed files with 265 additions and 52 deletions.
49 changes: 1 addition & 48 deletions src/PseudoTypes/ArrayShapeItem.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,53 +13,6 @@

namespace phpDocumentor\Reflection\PseudoTypes;

use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\Types\Mixed_;

use function sprintf;

final class ArrayShapeItem
class ArrayShapeItem extends ShapeItem
{
/** @var string|null */
private $key;
/** @var Type */
private $value;
/** @var bool */
private $optional;

public function __construct(?string $key, ?Type $value, bool $optional)
{
$this->key = $key;
$this->value = $value ?? new Mixed_();
$this->optional = $optional;
}

public function getKey(): ?string
{
return $this->key;
}

public function getValue(): Type
{
return $this->value;
}

public function isOptional(): bool
{
return $this->optional;
}

public function __toString(): string
{
if ($this->key !== null) {
return sprintf(
'%s%s: %s',
$this->key,
$this->optional ? '?' : '',
(string) $this->value
);
}

return (string) $this->value;
}
}
16 changes: 16 additions & 0 deletions src/PseudoTypes/ListShape.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace phpDocumentor\Reflection\PseudoTypes;

use function implode;

/** @psalm-immutable */
final class ListShape extends ArrayShape
{
public function __toString(): string
{
return 'list{' . implode(', ', $this->getItems()) . '}';
}
}
9 changes: 9 additions & 0 deletions src/PseudoTypes/ListShapeItem.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace phpDocumentor\Reflection\PseudoTypes;

final class ListShapeItem extends ArrayShapeItem
{
}
41 changes: 41 additions & 0 deletions src/PseudoTypes/ObjectShape.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace phpDocumentor\Reflection\PseudoTypes;

use phpDocumentor\Reflection\PseudoType;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\Types\Object_;

use function implode;

/** @psalm-immutable */
final class ObjectShape implements PseudoType
{
/** @var ObjectShapeItem[] */
private $items;

public function __construct(ObjectShapeItem ...$items)
{
$this->items = $items;
}

/**
* @return ObjectShapeItem[]
*/
public function getItems(): array
{
return $this->items;
}

public function underlyingType(): Type
{
return new Object_();
}

public function __toString(): string
{
return 'object{' . implode(', ', $this->items) . '}';
}
}
9 changes: 9 additions & 0 deletions src/PseudoTypes/ObjectShapeItem.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace phpDocumentor\Reflection\PseudoTypes;

final class ObjectShapeItem extends ShapeItem
{
}
56 changes: 56 additions & 0 deletions src/PseudoTypes/ShapeItem.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

declare(strict_types=1);

namespace phpDocumentor\Reflection\PseudoTypes;

use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\Types\Mixed_;

use function sprintf;

abstract class ShapeItem
{
/** @var string|null */
private $key;
/** @var Type */
private $value;
/** @var bool */
private $optional;

public function __construct(?string $key, ?Type $value, bool $optional)
{
$this->key = $key;
$this->value = $value ?? new Mixed_();
$this->optional = $optional;
}

public function getKey(): ?string
{
return $this->key;
}

public function getValue(): Type
{
return $this->value;
}

public function isOptional(): bool
{
return $this->optional;
}

public function __toString(): string
{
if ($this->key !== null) {
return sprintf(
'%s%s: %s',
$this->key,
$this->optional ? '?' : '',
(string) $this->value
);
}

return (string) $this->value;
}
}
45 changes: 42 additions & 3 deletions src/TypeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
use phpDocumentor\Reflection\PseudoTypes\IntegerRange;
use phpDocumentor\Reflection\PseudoTypes\IntegerValue;
use phpDocumentor\Reflection\PseudoTypes\List_;
use phpDocumentor\Reflection\PseudoTypes\ListShape;
use phpDocumentor\Reflection\PseudoTypes\ListShapeItem;
use phpDocumentor\Reflection\PseudoTypes\LiteralString;
use phpDocumentor\Reflection\PseudoTypes\LowercaseString;
use phpDocumentor\Reflection\PseudoTypes\NegativeInteger;
Expand All @@ -33,6 +35,8 @@
use phpDocumentor\Reflection\PseudoTypes\NonEmptyString;
use phpDocumentor\Reflection\PseudoTypes\Numeric_;
use phpDocumentor\Reflection\PseudoTypes\NumericString;
use phpDocumentor\Reflection\PseudoTypes\ObjectShape;
use phpDocumentor\Reflection\PseudoTypes\ObjectShapeItem;
use phpDocumentor\Reflection\PseudoTypes\PositiveInteger;
use phpDocumentor\Reflection\PseudoTypes\StringValue;
use phpDocumentor\Reflection\PseudoTypes\TraitString;
Expand Down Expand Up @@ -82,6 +86,8 @@
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use PHPStan\PhpDocParser\Ast\Type\IntersectionTypeNode;
use PHPStan\PhpDocParser\Ast\Type\NullableTypeNode;
use PHPStan\PhpDocParser\Ast\Type\ObjectShapeItemNode;
use PHPStan\PhpDocParser\Ast\Type\ObjectShapeNode;
use PHPStan\PhpDocParser\Ast\Type\OffsetAccessTypeNode;
use PHPStan\PhpDocParser\Ast\Type\ThisTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
Expand Down Expand Up @@ -234,10 +240,43 @@ public function createType(?TypeNode $type, Context $context): Type
);

case ArrayShapeNode::class:
return new ArrayShape(
switch ($type->kind) {
case ArrayShapeNode::KIND_ARRAY:
return new ArrayShape(
...array_map(
function (ArrayShapeItemNode $item) use ($context): ArrayShapeItem {
return new ArrayShapeItem(
(string) $item->keyName,
$this->createType($item->valueType, $context),
$item->optional
);
},
$type->items
)
);

case ArrayShapeNode::KIND_LIST:
return new ListShape(
...array_map(
function (ArrayShapeItemNode $item) use ($context): ListShapeItem {
return new ListShapeItem(
null,
$this->createType($item->valueType, $context),
$item->optional
);
},
$type->items
)
);

default:
throw new RuntimeException('Unsupported array shape kind');
}
case ObjectShapeNode::class:
return new ObjectShape(
...array_map(
function (ArrayShapeItemNode $item) use ($context): ArrayShapeItem {
return new ArrayShapeItem(
function (ObjectShapeItemNode $item) use ($context): ObjectShapeItem {
return new ObjectShapeItem(
(string) $item->keyName,
$this->createType($item->valueType, $context),
$item->optional
Expand Down
15 changes: 15 additions & 0 deletions tests/unit/CollectionResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class CollectionResolverTest extends TestCase
* @uses \phpDocumentor\Reflection\Types\String_
*
* @covers ::resolve
* @covers ::createType
* @covers ::__construct
*/
public function testResolvingCollection(): void
Expand Down Expand Up @@ -69,6 +70,7 @@ public function testResolvingCollection(): void
*
* @covers ::__construct
* @covers ::resolve
* @covers ::createType
*/
public function testResolvingCollectionWithKeyType(): void
{
Expand Down Expand Up @@ -99,6 +101,7 @@ public function testResolvingCollectionWithKeyType(): void
*
* @covers ::__construct
* @covers ::resolve
* @covers ::createType
*/
public function testResolvingArrayCollection(): void
{
Expand All @@ -125,6 +128,7 @@ public function testResolvingArrayCollection(): void
*
* @covers ::__construct
* @covers ::resolve
* @covers ::createType
*/
public function testResolvingArrayCollectionWithKey(): void
{
Expand All @@ -151,6 +155,7 @@ public function testResolvingArrayCollectionWithKey(): void
* @covers ::__construct
* @covers ::resolve
* @covers ::createType
*/
public function testResolvingArrayCollectionWithKeyAndWhitespace(): void
{
Expand All @@ -177,6 +182,7 @@ public function testResolvingArrayCollectionWithKeyAndWhitespace(): void
*
* @covers ::__construct
* @covers ::resolve
* @covers ::createType
*/
public function testResolvingCollectionOfCollection(): void
{
Expand Down Expand Up @@ -208,6 +214,7 @@ public function testResolvingCollectionOfCollection(): void
/**
* @covers ::__construct
* @covers ::resolve
* @covers ::createType
*/
public function testBadArrayCollectionKey(): void
{
Expand All @@ -220,6 +227,7 @@ public function testBadArrayCollectionKey(): void
/**
* @covers ::__construct
* @covers ::resolve
* @covers ::createType
*/
public function testGoodArrayCollectionKey(): void
{
Expand All @@ -239,6 +247,7 @@ public function testGoodArrayCollectionKey(): void
/**
* @covers ::__construct
* @covers ::resolve
* @covers ::createType
*/
public function testMissingStartCollection(): void
{
Expand All @@ -251,6 +260,7 @@ public function testMissingStartCollection(): void
/**
* @covers ::__construct
* @covers ::resolve
* @covers ::createType
*/
public function testMissingEndCollection(): void
{
Expand All @@ -263,6 +273,7 @@ public function testMissingEndCollection(): void
/**
* @covers ::__construct
* @covers ::resolve
* @covers ::createType
*/
public function testBadCollectionClass(): void
{
Expand All @@ -280,6 +291,7 @@ public function testBadCollectionClass(): void
*
* @covers ::__construct
* @covers ::resolve
* @covers ::createType
*/
public function testResolvingCollectionAsArray(): void
{
Expand All @@ -304,6 +316,7 @@ public function testResolvingCollectionAsArray(): void
*
* @covers ::__construct
* @covers ::resolve
* @covers ::createType
*/
public function testResolvingList(): void
{
Expand All @@ -328,6 +341,7 @@ public function testResolvingList(): void
*
* @covers ::__construct
* @covers ::resolve
* @covers ::createType
*/
public function testResolvingNonEmptyList(): void
{
Expand All @@ -352,6 +366,7 @@ public function testResolvingNonEmptyList(): void
*
* @covers ::__construct
* @covers ::resolve
* @covers ::createType
*/
public function testResolvingNullableArray(): void
{
Expand Down
Loading

0 comments on commit c92bb85

Please sign in to comment.