diff --git a/src/Analyzer/NodeVisitors/PhpDocResolver.php b/src/Analyzer/NodeVisitors/PhpDocResolver.php index acdee8fc8..60ebeabd1 100644 --- a/src/Analyzer/NodeVisitors/PhpDocResolver.php +++ b/src/Analyzer/NodeVisitors/PhpDocResolver.php @@ -35,6 +35,7 @@ use PHPStan\PhpDocParser\Ast\PhpDoc\TypeAliasTagValueNode; use PHPStan\PhpDocParser\Ast\Type\ArrayShapeItemNode; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; +use PHPStan\PhpDocParser\Ast\Type\ObjectShapeItemNode; use PHPStan\PhpDocParser\Lexer\Lexer; use PHPStan\PhpDocParser\Parser\PhpDocParser; use PHPStan\PhpDocParser\Parser\TokenIterator; @@ -208,7 +209,7 @@ protected function resolvePhpDoc(PhpDocNode $phpDoc): void } elseif ($value instanceof ConstExprNode) { $value->setAttribute('info', $this->resolveConstExpr($value)); - } elseif ($value instanceof ArrayShapeItemNode) { + } elseif ($value instanceof ArrayShapeItemNode || $value instanceof ObjectShapeItemNode) { $stack[$index++] = $value->valueType; // intentionally not pushing $value->keyName } else { diff --git a/src/Renderer/Latte/Template/blocks/type.latte b/src/Renderer/Latte/Template/blocks/type.latte index 259a70216..ca6c02bd1 100644 --- a/src/Renderer/Latte/Template/blocks/type.latte +++ b/src/Renderer/Latte/Template/blocks/type.latte @@ -42,6 +42,16 @@ {if !$type->sealed}{if $type->items}, {/if}...{/if} } + {elseif $type instanceof PHPStan\PhpDocParser\Ast\Type\ObjectShapeNode} + object{ + {foreach $type->items as $item} + {$item->keyName} + {if $item->optional}?{/if}{=": "} + {include this, type: $item->valueType, scope: $scope, brackets: true, short: $short} + {sep}, {/sep} + {/foreach} + } + {elseif $type instanceof PHPStan\PhpDocParser\Ast\Type\NullableTypeNode} ?{include this, type: $type->type, scope: $scope, brackets: true, short: $short} diff --git a/tests/Features/PhpDoc/Types/ObjectShapes.neon b/tests/Features/PhpDoc/Types/ObjectShapes.neon new file mode 100644 index 000000000..79837afbd --- /dev/null +++ b/tests/Features/PhpDoc/Types/ObjectShapes.neon @@ -0,0 +1,118 @@ +[ + @Interface( + file: '%rootDir%/tests/Features/PhpDoc/Types/ObjectShapes.php' + properties: { + a: @Property( + type: @ObjectShape( + items: [ + @ObjectShapeItem( + keyName: @IdentifierType( + name: 'a' + ) + optional: false + valueType: @IdentifierType( + name: 'int' + attributes: { + kind: 'Keyword' + } + ) + ), + ] + ) + name: 'a' + magic: true + public: true + ) + b: @Property( + type: @ObjectShape( + items: [ + @ObjectShapeItem( + keyName: @IdentifierType( + name: 'b' + ) + optional: false + valueType: @NullableType( + type: @IdentifierType( + name: 'int' + attributes: { + kind: 'Keyword' + } + ) + ) + ), + ] + ) + name: 'b' + magic: true + public: true + ) + c: @Property( + type: @ObjectShape( + items: [ + @ObjectShapeItem( + keyName: @IdentifierType( + name: 'c' + ) + optional: true + valueType: @IdentifierType( + name: 'int' + attributes: { + kind: 'Keyword' + } + ) + ), + ] + ) + name: 'c' + magic: true + public: true + ) + d: @Property( + type: @NullableType( + type: @ObjectShape( + items: [ + @ObjectShapeItem( + keyName: @IdentifierType( + name: 'd' + ) + optional: false + valueType: @IdentifierType( + name: 'int' + attributes: { + kind: 'Keyword' + } + ) + ), + ] + ) + ) + name: 'd' + magic: true + public: true + ) + e: @Property( + type: @ObjectShape( + items: [ + @ObjectShapeItem( + keyName: @ConstExprString( + value: 'e' + ) + optional: false + valueType: @IdentifierType( + name: 'int' + attributes: { + kind: 'Keyword' + } + ) + ), + ] + ) + name: 'e' + magic: true + public: true + ) + } + name: 'ApiGenTests\Features\PhpDoc\Types\ObjectShapes' + primary: true + ), +] diff --git a/tests/Features/PhpDoc/Types/ObjectShapes.php b/tests/Features/PhpDoc/Types/ObjectShapes.php new file mode 100644 index 000000000..505e43960 --- /dev/null +++ b/tests/Features/PhpDoc/Types/ObjectShapes.php @@ -0,0 +1,14 @@ +