From 82cc2fcda9d8a163fa7abf0c589582b23886f107 Mon Sep 17 00:00:00 2001 From: anne-gaelle123inkt <50699909+anne-gaelle123inkt@users.noreply.github.com> Date: Fri, 8 Nov 2024 17:01:50 +0100 Subject: [PATCH] B#188652 AccessorPair accept all array notations (#62) * B#188652 AccessorPair accept all array notations --- composer.json | 5 +- src/Constraint/Typehint/PhpDocParser.php | 12 ++--- src/Constraint/Typehint/TypehintResolver.php | 6 +-- .../Compound/ArrayShapeProvider.php | 47 ++++++++++++++++++ .../NativeValueProviderFactory.php | 9 ++++ .../Integration/AccessorPairAsserterTest.php | 1 + .../CompoundTypes/ArrayShapeProperty.php | 34 +++++++++++++ .../Unit/Constraint/ConstraintConfigTest.php | 2 +- .../data/success/TypedArrayShapeSetGet.php | 49 +++++++++++++++++++ .../Compound/ArrayShapeProviderTest.php | 36 ++++++++++++++ 10 files changed, 188 insertions(+), 13 deletions(-) create mode 100644 src/Constraint/ValueProvider/Compound/ArrayShapeProvider.php create mode 100644 tests/Integration/data/success/Regular/Types/CompoundTypes/ArrayShapeProperty.php create mode 100644 tests/Unit/Constraint/MethodPair/AccessorPair/data/success/TypedArrayShapeSetGet.php create mode 100644 tests/Unit/Constraint/ValueProvider/Compound/ArrayShapeProviderTest.php diff --git a/composer.json b/composer.json index fb0dbaf..ed41b0d 100644 --- a/composer.json +++ b/composer.json @@ -8,12 +8,13 @@ "sort-packages": true, "allow-plugins": { "phpstan/extension-installer": true - } + }, + "lock": false }, "require": { "php": ">=8.1", "doctrine/inflector": "^2.0", - "phpdocumentor/type-resolver": "^1.7", + "phpdocumentor/type-resolver": "^1.9", "phpunit/phpunit": "^10.0 || ^11.0" }, "require-dev": { diff --git a/src/Constraint/Typehint/PhpDocParser.php b/src/Constraint/Typehint/PhpDocParser.php index 731b03b..060f3a8 100644 --- a/src/Constraint/Typehint/PhpDocParser.php +++ b/src/Constraint/Typehint/PhpDocParser.php @@ -19,12 +19,12 @@ public function getParamTypehint(string $parameterName, string $docComment): ?st preg_match('/\*\s*@(?:phpstan|psalm)-param\s+(.*?)\s*(?:\.\.\.)?' . preg_quote('$' . $parameterName, '/') . '\W/i', $docComment, $matches); if (isset($matches[1])) { - return $this->normalizeDocblock((string)$matches[1]); + return $this->normalizeDocblock($matches[1]); } preg_match('/\*\s*@param\s+(.*?)\s*(?:\.\.\.)?' . preg_quote('$' . $parameterName, '/') . '\W/i', $docComment, $matches); if (isset($matches[1])) { - return $this->normalizeDocblock((string)$matches[1]); + return $this->normalizeDocblock($matches[1]); } return null; @@ -36,6 +36,7 @@ public function getParamTypehint(string $parameterName, string $docComment): ?st public function getReturnTypehint(string $originalDocComment): ?string { $docComment = trim($originalDocComment); + $docComment = str_replace([', ', ': '], [',', ':'], $docComment); // empty docblock provided, no typehint found if ($docComment === '') { return null; @@ -49,12 +50,12 @@ public function getReturnTypehint(string $originalDocComment): ?string preg_match('/\*\s*@(?:phpstan|psalm)-return\s+(.*?)(?:\s+|\*)/', $docComment, $matches); if (isset($matches[1])) { - return $this->normalizeDocblock((string)$matches[1]); + return $this->normalizeDocblock($matches[1]); } preg_match('/\*\s*@return\s+(.*?)(?:\s+|\*)/', $docComment, $matches); if (isset($matches[1])) { - return $this->normalizeDocblock((string)$matches[1]); + return $this->normalizeDocblock($matches[1]); } return null; @@ -74,9 +75,6 @@ public function getTemplateTypehints(string $originalDocComment): array } preg_match_all('/\*\s*@template\s+(.*?)\sof\s(.*?)(?:\s+|\*)/', $docComment, $matches); - if (isset($matches[1], $matches[2]) === false) { - return []; // @codeCoverageIgnore - } return array_combine($matches[1], $matches[2]); } diff --git a/src/Constraint/Typehint/TypehintResolver.php b/src/Constraint/Typehint/TypehintResolver.php index 8eaaa2b..c0938d8 100644 --- a/src/Constraint/Typehint/TypehintResolver.php +++ b/src/Constraint/Typehint/TypehintResolver.php @@ -21,10 +21,10 @@ */ class TypehintResolver { - protected PhpDocParser $phpDocParser; + protected PhpDocParser $phpDocParser; protected ReflectionMethod $method; - protected TypeResolver $resolver; - protected Context $resolverContext; + protected TypeResolver $resolver; + protected Context $resolverContext; public function __construct(ReflectionMethod $method) { diff --git a/src/Constraint/ValueProvider/Compound/ArrayShapeProvider.php b/src/Constraint/ValueProvider/Compound/ArrayShapeProvider.php new file mode 100644 index 0000000..83afdd3 --- /dev/null +++ b/src/Constraint/ValueProvider/Compound/ArrayShapeProvider.php @@ -0,0 +1,47 @@ + $items + */ + public function __construct(protected array $items) + { + } + + /** + * @return array> + * @inheritDoc + */ + public function getValues(): array + { + $testArray = []; + $keyValues = []; + + $minValues = null; + foreach ($this->items as $key => $item) { + $values = $item->getValues(); + if ($minValues === null || count($values) < $minValues) { + $minValues = count($values); + } + $keyValues[$key] = $item->getValues(); + } + + foreach ($keyValues as $key => $values) { + $keyValues[$key] = array_slice($values, 0, $minValues); + } + + foreach ($keyValues as $key => $values) { + foreach ($values as $index => $value) { + $testArray[$index][$key] = $value; + } + } + + return $testArray; + } +} diff --git a/src/Constraint/ValueProvider/NativeValueProviderFactory.php b/src/Constraint/ValueProvider/NativeValueProviderFactory.php index 501668f..d34624b 100644 --- a/src/Constraint/ValueProvider/NativeValueProviderFactory.php +++ b/src/Constraint/ValueProvider/NativeValueProviderFactory.php @@ -4,6 +4,7 @@ namespace DigitalRevolution\AccessorPairConstraint\Constraint\ValueProvider; use DigitalRevolution\AccessorPairConstraint\Constraint\ValueProvider\Compound\ArrayProvider; +use DigitalRevolution\AccessorPairConstraint\Constraint\ValueProvider\Compound\ArrayShapeProvider; use DigitalRevolution\AccessorPairConstraint\Constraint\ValueProvider\Compound\CallableProvider; use DigitalRevolution\AccessorPairConstraint\Constraint\ValueProvider\Compound\IterableProvider; use DigitalRevolution\AccessorPairConstraint\Constraint\ValueProvider\Compound\ObjectProvider; @@ -16,6 +17,7 @@ use DigitalRevolution\AccessorPairConstraint\Constraint\ValueProvider\Special\NullProvider; use DigitalRevolution\AccessorPairConstraint\Constraint\ValueProvider\Special\ResourceProvider; use LogicException; +use phpDocumentor\Reflection\PseudoTypes\ArrayShape; use phpDocumentor\Reflection\Type; use phpDocumentor\Reflection\Types\Array_; use phpDocumentor\Reflection\Types\Boolean; @@ -79,6 +81,13 @@ protected function getCompoundProvider(Type $typehint): ?ValueProvider $this->valueProviderFactory->getProvider($typehint->getValueType()), $this->valueProviderFactory->getProvider($typehint->getKeyType()) ); + case ArrayShape::class: + $items = $typehint->getItems(); + $itemProviders = []; + foreach ($items as $item) { + $itemProviders[$item->getKey()] = $this->valueProviderFactory->getProvider($item->getValue()); + } + return new ArrayShapeProvider($itemProviders); case Callable_::class: return new CallableProvider(); case Iterable_::class: diff --git a/tests/Integration/AccessorPairAsserterTest.php b/tests/Integration/AccessorPairAsserterTest.php index 042b086..e5e04d7 100644 --- a/tests/Integration/AccessorPairAsserterTest.php +++ b/tests/Integration/AccessorPairAsserterTest.php @@ -31,6 +31,7 @@ * @uses \DigitalRevolution\AccessorPairConstraint\Constraint\Typehint\PhpDocParser * @uses \DigitalRevolution\AccessorPairConstraint\Constraint\Typehint\TypehintResolver * @uses \DigitalRevolution\AccessorPairConstraint\Constraint\ValueProvider\Compound\ArrayProvider + * @uses \DigitalRevolution\AccessorPairConstraint\Constraint\ValueProvider\Compound\ArrayShapeProvider * @uses \DigitalRevolution\AccessorPairConstraint\Constraint\ValueProvider\Compound\CallableProvider * @uses \DigitalRevolution\AccessorPairConstraint\Constraint\ValueProvider\Compound\InstanceProvider * @uses \DigitalRevolution\AccessorPairConstraint\Constraint\ValueProvider\Compound\IntersectionProvider diff --git a/tests/Integration/data/success/Regular/Types/CompoundTypes/ArrayShapeProperty.php b/tests/Integration/data/success/Regular/Types/CompoundTypes/ArrayShapeProperty.php new file mode 100644 index 0000000..666bd48 --- /dev/null +++ b/tests/Integration/data/success/Regular/Types/CompoundTypes/ArrayShapeProperty.php @@ -0,0 +1,34 @@ +items = $items; + } + + /** + * @param array{foo: int, bar: string}[] $items + */ + public function setItems(array $items): void + { + $this->items = $items; + } + + /** + * @return array{foo: int, bar: string}[] + */ + public function getItems(): array + { + return $this->items; + } +} diff --git a/tests/Unit/Constraint/ConstraintConfigTest.php b/tests/Unit/Constraint/ConstraintConfigTest.php index 896199b..1a4fbc5 100644 --- a/tests/Unit/Constraint/ConstraintConfigTest.php +++ b/tests/Unit/Constraint/ConstraintConfigTest.php @@ -1,7 +1,7 @@ items = $items; + } + + /** + * @param array{foo: int, bar: string} $item + */ + public function addItem(array $item): void + { + $this->items[] = $item; + } + + /** + * @param array{foo: int, bar: string}[] $items + */ + public function setItems(array $items): void + { + $this->items = $items; + } + + /** + * @return array{foo: int, bar: string}[] + */ + public function getItems(): array + { + return $this->items; + } + + public function getExpectedPairs(): array + { + return [['getItems', 'setItems', false], ['getItems', 'addItem', true]]; + } +} diff --git a/tests/Unit/Constraint/ValueProvider/Compound/ArrayShapeProviderTest.php b/tests/Unit/Constraint/ValueProvider/Compound/ArrayShapeProviderTest.php new file mode 100644 index 0000000..04307e8 --- /dev/null +++ b/tests/Unit/Constraint/ValueProvider/Compound/ArrayShapeProviderTest.php @@ -0,0 +1,36 @@ + new IntProvider(), 'bar' => new StringProvider()]); + $values = $valueProvider->getValues(); + + static::assertNotCount(0, $values); + foreach ($values as $value) { + static::assertCount(2, $value); + static::assertArrayHasKey('foo', $value); + static::assertIsInt($value['foo']); + static::assertArrayHasKey('bar', $value); + static::assertIsString($value['bar']); + } + } +}