From 3e49b0f002f989c456e947724cc8a1c508a80c46 Mon Sep 17 00:00:00 2001 From: Emil Masiakowski Date: Fri, 3 Dec 2021 16:29:16 +0100 Subject: [PATCH] [PropertyInfo] Add support for phpDocumentor and PHPStan pseudo-types --- composer.json | 2 +- .../Component/PropertyInfo/CHANGELOG.md | 5 ++ .../Tests/Extractor/PhpDocExtractorTest.php | 23 +++++++++ .../Tests/Extractor/PhpStanExtractorTest.php | 33 +++++++++++++ .../Fixtures/PhpStanPseudoTypesDummy.php | 45 +++++++++++++++++ .../Tests/Fixtures/PseudoTypesDummy.php | 48 +++++++++++++++++++ .../PropertyInfo/Util/PhpDocTypeHelper.php | 11 +++++ .../PropertyInfo/Util/PhpStanTypeHelper.php | 29 +++++++++++ .../Component/PropertyInfo/composer.json | 2 +- 9 files changed, 196 insertions(+), 2 deletions(-) create mode 100644 src/Symfony/Component/PropertyInfo/Tests/Fixtures/PhpStanPseudoTypesDummy.php create mode 100644 src/Symfony/Component/PropertyInfo/Tests/Fixtures/PseudoTypesDummy.php diff --git a/composer.json b/composer.json index 61a7251983f12..1750d55ee22ce 100644 --- a/composer.json +++ b/composer.json @@ -157,7 +157,7 @@ "egulias/email-validator": "~3.0.0", "masterminds/html5": "<2.6", "phpdocumentor/reflection-docblock": "<5.2", - "phpdocumentor/type-resolver": "<1.4.0", + "phpdocumentor/type-resolver": "<1.5.1", "ocramius/proxy-manager": "<2.1", "phpunit/phpunit": "<5.4.3" }, diff --git a/src/Symfony/Component/PropertyInfo/CHANGELOG.md b/src/Symfony/Component/PropertyInfo/CHANGELOG.md index 34cb272b23064..0970592546afa 100644 --- a/src/Symfony/Component/PropertyInfo/CHANGELOG.md +++ b/src/Symfony/Component/PropertyInfo/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.1 +--- + + * Add support for phpDocumentor and PHPStan pseudo-types + 6.0 --- diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php index c2bd02ce051b5..ba93cf47a8055 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php @@ -406,6 +406,29 @@ public function constructorTypesProvider() ['ddd', null], ]; } + + /** + * @dataProvider pseudoTypesProvider + */ + public function testPseudoTypes($property, array $type) + { + $this->assertEquals($type, $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\PseudoTypesDummy', $property)); + } + + public function pseudoTypesProvider(): array + { + return [ + ['classString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]], + ['classStringGeneric', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]], + ['htmlEscapedString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]], + ['lowercaseString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]], + ['nonEmptyLowercaseString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]], + ['nonEmptyString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]], + ['numericString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]], + ['traitString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]], + ['positiveInt', [new Type(Type::BUILTIN_TYPE_INT, false, null)]], + ]; + } } class EmptyDocBlock diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php index f3d7088dd3eca..01c68216dc026 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php @@ -371,6 +371,39 @@ public function unionTypesProvider(): array ]; } + /** + * @dataProvider pseudoTypesProvider + */ + public function testPseudoTypes($property, array $type) + { + $this->assertEquals($type, $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\PhpStanPseudoTypesDummy', $property)); + } + + public function pseudoTypesProvider(): array + { + return [ + ['classString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]], + ['classStringGeneric', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]], + ['htmlEscapedString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]], + ['lowercaseString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]], + ['nonEmptyLowercaseString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]], + ['nonEmptyString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]], + ['numericString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]], + ['traitString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]], + ['interfaceString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]], + ['literalString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]], + ['positiveInt', [new Type(Type::BUILTIN_TYPE_INT, false, null)]], + ['negativeInt', [new Type(Type::BUILTIN_TYPE_INT, false, null)]], + ['nonEmptyArray', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true)]], + ['nonEmptyList', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT))]], + ['scalar', [new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_FLOAT), new Type(Type::BUILTIN_TYPE_STRING), new Type(Type::BUILTIN_TYPE_BOOL)]], + ['number', [new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_FLOAT)]], + ['numeric', [new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_FLOAT), new Type(Type::BUILTIN_TYPE_STRING)]], + ['arrayKey', [new Type(Type::BUILTIN_TYPE_STRING), new Type(Type::BUILTIN_TYPE_INT)]], + ['double', [new Type(Type::BUILTIN_TYPE_FLOAT)]], + ]; + } + public function testDummyNamespace() { $this->assertEquals( diff --git a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/PhpStanPseudoTypesDummy.php b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/PhpStanPseudoTypesDummy.php new file mode 100644 index 0000000000000..06690f4b1fd6f --- /dev/null +++ b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/PhpStanPseudoTypesDummy.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyInfo\Tests\Fixtures; + +/** + * @author Emil Masiakowski + */ +class PhpStanPseudoTypesDummy extends PseudoTypesDummy +{ + /** @var negative-int */ + public $negativeInt; + + /** @var non-empty-array */ + public $nonEmptyArray; + + /** @var non-empty-list */ + public $nonEmptyList; + + /** @var interface-string */ + public $interfaceString; + + /** @var scalar */ + public $scalar; + + /** @var array-key */ + public $arrayKey; + + /** @var number */ + public $number; + + /** @var numeric */ + public $numeric; + + /** @var double */ + public $double; +} diff --git a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/PseudoTypesDummy.php b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/PseudoTypesDummy.php new file mode 100644 index 0000000000000..48574a1efe43e --- /dev/null +++ b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/PseudoTypesDummy.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyInfo\Tests\Fixtures; + +/** + * @author Emil Masiakowski + */ +class PseudoTypesDummy +{ + /** @var class-string */ + public $classString; + + /** @var class-string<\stdClass> */ + public $classStringGeneric; + + /** @var html-escaped-string */ + public $htmlEscapedString; + + /** @var lowercase-string */ + public $lowercaseString; + + /** @var non-empty-lowercase-string */ + public $nonEmptyLowercaseString; + + /** @var non-empty-string */ + public $nonEmptyString; + + /** @var numeric-string */ + public $numericString; + + /** @var trait-string */ + public $traitString; + + /** @var positive-int */ + public $positiveInt; + + /** @var literal-string */ + public $literalString; +} diff --git a/src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php b/src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php index d1752d6c1b2fb..0999ed5767cd8 100644 --- a/src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php +++ b/src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php @@ -11,13 +11,16 @@ namespace Symfony\Component\PropertyInfo\Util; +use phpDocumentor\Reflection\PseudoType; use phpDocumentor\Reflection\PseudoTypes\List_; use phpDocumentor\Reflection\Type as DocType; use phpDocumentor\Reflection\Types\Array_; use phpDocumentor\Reflection\Types\Collection; use phpDocumentor\Reflection\Types\Compound; +use phpDocumentor\Reflection\Types\Integer; use phpDocumentor\Reflection\Types\Null_; use phpDocumentor\Reflection\Types\Nullable; +use phpDocumentor\Reflection\Types\String_; use Symfony\Component\PropertyInfo\Type; // Workaround for phpdocumentor/type-resolver < 1.6 @@ -143,6 +146,14 @@ private function createType(DocType $type, bool $nullable, string $docType = nul return new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true, $collectionKeyType, $collectionValueType); } + if ($type instanceof PseudoType) { + if ($type->underlyingType() instanceof Integer) { + return new Type(Type::BUILTIN_TYPE_INT, $nullable, null); + } elseif ($type->underlyingType() instanceof String_) { + return new Type(Type::BUILTIN_TYPE_STRING, $nullable, null); + } + } + $docType = $this->normalizeType($docType); [$phpType, $class] = $this->getPhpTypeAndClass($docType); diff --git a/src/Symfony/Component/PropertyInfo/Util/PhpStanTypeHelper.php b/src/Symfony/Component/PropertyInfo/Util/PhpStanTypeHelper.php index b5ed7bb5732ee..8b618a17bd9c1 100644 --- a/src/Symfony/Component/PropertyInfo/Util/PhpStanTypeHelper.php +++ b/src/Symfony/Component/PropertyInfo/Util/PhpStanTypeHelper.php @@ -110,6 +110,10 @@ private function extractTypes(TypeNode $node, NameScope $nameScope): array return $this->compressNullableType($types); } if ($node instanceof GenericTypeNode) { + if ('class-string' === $node->type->name) { + return [new Type(Type::BUILTIN_TYPE_STRING)]; + } + [$mainType] = $this->extractTypes($node->type, $nameScope); $collectionKeyTypes = $mainType->getCollectionKeyTypes(); @@ -158,9 +162,16 @@ private function extractTypes(TypeNode $node, NameScope $nameScope): array switch ($node->name) { case 'integer': + case 'positive-int': + case 'negative-int': return [new Type(Type::BUILTIN_TYPE_INT)]; + case 'double': + return [new Type(Type::BUILTIN_TYPE_FLOAT)]; case 'list': + case 'non-empty-list': return [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT))]; + case 'non-empty-array': + return [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true)]; case 'mixed': return []; // mixed seems to be ignored in all other extractors case 'parent': @@ -168,8 +179,26 @@ private function extractTypes(TypeNode $node, NameScope $nameScope): array case 'static': case 'self': return [new Type(Type::BUILTIN_TYPE_OBJECT, false, $nameScope->resolveRootClass())]; + case 'class-string': + case 'html-escaped-string': + case 'lowercase-string': + case 'non-empty-lowercase-string': + case 'non-empty-string': + case 'numeric-string': + case 'trait-string': + case 'interface-string': + case 'literal-string': + return [new Type(Type::BUILTIN_TYPE_STRING)]; case 'void': return [new Type(Type::BUILTIN_TYPE_NULL)]; + case 'scalar': + return [new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_FLOAT), new Type(Type::BUILTIN_TYPE_STRING), new Type(Type::BUILTIN_TYPE_BOOL)]; + case 'number': + return [new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_FLOAT)]; + case 'numeric': + return [new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_FLOAT), new Type(Type::BUILTIN_TYPE_STRING)]; + case 'array-key': + return [new Type(Type::BUILTIN_TYPE_STRING), new Type(Type::BUILTIN_TYPE_INT)]; } return [new Type(Type::BUILTIN_TYPE_OBJECT, false, $nameScope->resolveStringName($node->name))]; diff --git a/src/Symfony/Component/PropertyInfo/composer.json b/src/Symfony/Component/PropertyInfo/composer.json index d2ffb8693696b..7bc555b9bd316 100644 --- a/src/Symfony/Component/PropertyInfo/composer.json +++ b/src/Symfony/Component/PropertyInfo/composer.json @@ -36,7 +36,7 @@ }, "conflict": { "phpdocumentor/reflection-docblock": "<5.2", - "phpdocumentor/type-resolver": "<1.4.0", + "phpdocumentor/type-resolver": "<1.5.1", "symfony/dependency-injection": "<5.4" }, "suggest": {