From b6a886bbc70d7db0c84d34281114a72a0a11fe73 Mon Sep 17 00:00:00 2001 From: smoench Date: Thu, 16 Jul 2020 13:00:43 +0200 Subject: [PATCH 1/3] support psalm scalar types --- src/PseudoTypes/CallableString.php | 39 ++++++++++++++++++ src/PseudoTypes/HtmlEscapedString.php | 39 ++++++++++++++++++ src/PseudoTypes/LowercaseString.php | 39 ++++++++++++++++++ src/PseudoTypes/NonEmptyLowercaseString.php | 39 ++++++++++++++++++ src/PseudoTypes/NonEmptyString.php | 39 ++++++++++++++++++ src/PseudoTypes/NumericString.php | 39 ++++++++++++++++++ src/PseudoTypes/PositiveInteger.php | 40 ++++++++++++++++++ src/PseudoTypes/TraitString.php | 39 ++++++++++++++++++ src/TypeResolver.php | 15 +++++-- src/Types/ArrayKey.php | 34 ++++++++++++++++ src/Types/Integer.php | 2 +- src/Types/String_.php | 2 +- tests/unit/TypeResolverTest.php | 15 +++++-- tests/unit/Types/ArrayKeyTest.php | 45 +++++++++++++++++++++ 14 files changed, 418 insertions(+), 8 deletions(-) create mode 100644 src/PseudoTypes/CallableString.php create mode 100644 src/PseudoTypes/HtmlEscapedString.php create mode 100644 src/PseudoTypes/LowercaseString.php create mode 100644 src/PseudoTypes/NonEmptyLowercaseString.php create mode 100644 src/PseudoTypes/NonEmptyString.php create mode 100644 src/PseudoTypes/NumericString.php create mode 100644 src/PseudoTypes/PositiveInteger.php create mode 100644 src/PseudoTypes/TraitString.php create mode 100644 src/Types/ArrayKey.php create mode 100644 tests/unit/Types/ArrayKeyTest.php diff --git a/src/PseudoTypes/CallableString.php b/src/PseudoTypes/CallableString.php new file mode 100644 index 0000000..f4ba49a --- /dev/null +++ b/src/PseudoTypes/CallableString.php @@ -0,0 +1,39 @@ + Types\String_::class, 'class-string' => Types\ClassString::class, + 'html-escaped-string' => PseudoTypes\HtmlEscapedString::class, + 'lowercase-string' => PseudoTypes\LowercaseString::class, + 'non-empty-lowercase-string' => PseudoTypes\NonEmptyLowercaseString::class, + 'non-empty-string' => PseudoTypes\NonEmptyString::class, + 'numeric-string' => PseudoTypes\NumericString::class, + 'trait-string' => PseudoTypes\TraitString::class, 'int' => Types\Integer::class, 'integer' => Types\Integer::class, + 'positive-int' => PseudoTypes\PositiveInteger::class, 'bool' => Types\Boolean::class, 'boolean' => Types\Boolean::class, 'real' => Types\Float_::class, 'float' => Types\Float_::class, 'double' => Types\Float_::class, - 'object' => Object_::class, + 'object' => Types\Object_::class, 'mixed' => Types\Mixed_::class, - 'array' => Array_::class, + 'array' => Types\Array_::class, + 'array-key' => Types\ArrayKey::class, 'resource' => Types\Resource_::class, 'void' => Types\Void_::class, 'null' => Types\Null_::class, 'scalar' => Types\Scalar::class, 'callback' => Types\Callable_::class, 'callable' => Types\Callable_::class, + 'callable-string' => PseudoTypes\CallableString::class, 'false' => PseudoTypes\False_::class, 'true' => PseudoTypes\True_::class, 'self' => Types\Self_::class, '$this' => Types\This::class, 'static' => Types\Static_::class, 'parent' => Types\Parent_::class, - 'iterable' => Iterable_::class, + 'iterable' => Types\Iterable_::class, ]; /** diff --git a/src/Types/ArrayKey.php b/src/Types/ArrayKey.php new file mode 100644 index 0000000..df21a45 --- /dev/null +++ b/src/Types/ArrayKey.php @@ -0,0 +1,34 @@ +assertSame('array-key', (string) (new ArrayKey())); + } + + /** + * @uses ::__construct + * + * @covers ::getIterator + */ + public function testArrayKeyCanBeIterated() : void + { + $types = [String_::class, Integer::class]; + + foreach (new ArrayKey() as $index => $type) { + $this->assertInstanceOf($types[$index], $type); + } + } +} From e6759359c918c286339ca1b9d5f90fd5debef398 Mon Sep 17 00:00:00 2001 From: smoench Date: Fri, 18 Sep 2020 09:23:33 +0200 Subject: [PATCH 2/3] add interface-string --- src/TypeResolver.php | 37 ++++++++++++++++ src/Types/InterfaceString.php | 56 ++++++++++++++++++++++++ tests/unit/TypeResolverTest.php | 39 +++++++++++++++++ tests/unit/Types/ClassStringTest.php | 2 +- tests/unit/Types/InterfaceStringTest.php | 43 ++++++++++++++++++ 5 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 src/Types/InterfaceString.php create mode 100644 tests/unit/Types/InterfaceStringTest.php diff --git a/src/TypeResolver.php b/src/TypeResolver.php index a2c7123..eed56e6 100644 --- a/src/TypeResolver.php +++ b/src/TypeResolver.php @@ -22,6 +22,7 @@ use phpDocumentor\Reflection\Types\Context; use phpDocumentor\Reflection\Types\Expression; use phpDocumentor\Reflection\Types\Integer; +use phpDocumentor\Reflection\Types\InterfaceString; use phpDocumentor\Reflection\Types\Intersection; use phpDocumentor\Reflection\Types\Iterable_; use phpDocumentor\Reflection\Types\Nullable; @@ -71,6 +72,7 @@ final class TypeResolver private $keywords = [ 'string' => Types\String_::class, 'class-string' => Types\ClassString::class, + 'interface-string' => Types\InterfaceString::class, 'html-escaped-string' => PseudoTypes\HtmlEscapedString::class, 'lowercase-string' => PseudoTypes\LowercaseString::class, 'non-empty-lowercase-string' => PseudoTypes\NonEmptyLowercaseString::class, @@ -246,6 +248,8 @@ private function parseTypes(ArrayIterator $tokens, Context $context, int $parser if ($classType !== null) { if ((string) $classType === 'class-string') { $types[] = $this->resolveClassString($tokens, $context); + } elseif ((string) $classType === 'interface-string') { + $types[] = $this->resolveInterfaceString($tokens, $context); } else { $types[] = $this->resolveCollection($tokens, $classType, $context); } @@ -455,6 +459,39 @@ private function resolveClassString(ArrayIterator $tokens, Context $context) : T return new ClassString($classType->getFqsen()); } + /** + * Resolves class string + * + * @param ArrayIterator $tokens + */ + private function resolveInterfaceString(ArrayIterator $tokens, Context $context) : Type + { + $tokens->next(); + + $classType = $this->parseTypes($tokens, $context, self::PARSER_IN_COLLECTION_EXPRESSION); + + if (!$classType instanceof Object_ || $classType->getFqsen() === null) { + throw new RuntimeException( + $classType . ' is not a interface string' + ); + } + + $token = $tokens->current(); + if ($token !== '>') { + if (empty($token)) { + throw new RuntimeException( + 'interface-string: ">" is missing' + ); + } + + throw new RuntimeException( + 'Unexpected character "' . $token . '", ">" is missing' + ); + } + + return new InterfaceString($classType->getFqsen()); + } + /** * Resolves the collection values and keys * diff --git a/src/Types/InterfaceString.php b/src/Types/InterfaceString.php new file mode 100644 index 0000000..8400bef --- /dev/null +++ b/src/Types/InterfaceString.php @@ -0,0 +1,56 @@ +fqsen = $fqsen; + } + + /** + * Returns the FQSEN associated with this object. + */ + public function getFqsen() : ?Fqsen + { + return $this->fqsen; + } + + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + */ + public function __toString() : string + { + if ($this->fqsen === null) { + return 'interface-string'; + } + + return 'interface-string<' . (string) $this->fqsen . '>'; + } +} diff --git a/tests/unit/TypeResolverTest.php b/tests/unit/TypeResolverTest.php index 0b1bd47..aa191b7 100644 --- a/tests/unit/TypeResolverTest.php +++ b/tests/unit/TypeResolverTest.php @@ -20,6 +20,7 @@ use phpDocumentor\Reflection\Types\Context; use phpDocumentor\Reflection\Types\Expression; use phpDocumentor\Reflection\Types\Integer; +use phpDocumentor\Reflection\Types\InterfaceString; use phpDocumentor\Reflection\Types\Intersection; use phpDocumentor\Reflection\Types\Iterable_; use phpDocumentor\Reflection\Types\Null_; @@ -79,6 +80,30 @@ public function testResolvingClassStrings(string $classString, bool $throwsExcep $this->assertInstanceOf(ClassString::class, $resolvedType); } + /** + * @uses \phpDocumentor\Reflection\Types\Context + * @uses \phpDocumentor\Reflection\Types\Object_ + * @uses \phpDocumentor\Reflection\Types\String_ + * + * @covers ::__construct + * @covers ::resolve + * @covers :: + * + * @dataProvider provideInterfaceStrings + */ + public function testResolvingInterfaceStrings(string $interfaceString, bool $throwsException) : void + { + $fixture = new TypeResolver(); + + if ($throwsException) { + $this->expectException('RuntimeException'); + } + + $resolvedType = $fixture->resolve($interfaceString, new Context('')); + + $this->assertInstanceOf(InterfaceString::class, $resolvedType); + } + /** * @uses \phpDocumentor\Reflection\Types\Context * @uses \phpDocumentor\Reflection\Types\Object_ @@ -748,6 +773,20 @@ public function provideClassStrings() : array ]; } + /** + * Returns a list of interface string types and whether they throw an exception. + * + * @return (string|bool)[][] + */ + public function provideInterfaceStrings() : array + { + return [ + ['interface-string<\phpDocumentor\Reflection>', false], + ['interface-string<\phpDocumentor\Reflection\DocBlock>', false], + ['interface-string', true], + ]; + } + /** * Provides a list of FQSENs to test the resolution patterns with. * diff --git a/tests/unit/Types/ClassStringTest.php b/tests/unit/Types/ClassStringTest.php index 4f779d4..37c9173 100644 --- a/tests/unit/Types/ClassStringTest.php +++ b/tests/unit/Types/ClassStringTest.php @@ -36,7 +36,7 @@ public function testClassStringStringifyCorrectly(ClassString $array, string $ex public function provideClassStrings() : array { return [ - 'generic clss string' => [new ClassString(), 'class-string'], + 'generic class string' => [new ClassString(), 'class-string'], 'typed class string' => [new ClassString(new Fqsen('\Foo\Bar')), 'class-string<\Foo\Bar>'], ]; } diff --git a/tests/unit/Types/InterfaceStringTest.php b/tests/unit/Types/InterfaceStringTest.php new file mode 100644 index 0000000..f4afc8a --- /dev/null +++ b/tests/unit/Types/InterfaceStringTest.php @@ -0,0 +1,43 @@ +assertSame($expectedString, (string) $array); + } + + /** + * @return mixed[] + */ + public function provideInterfaceStrings() : array + { + return [ + 'generic interface string' => [new InterfaceString(), 'interface-string'], + 'typed interface string' => [new InterfaceString(new Fqsen('\Foo\Bar')), 'interface-string<\Foo\Bar>'], + ]; + } +} From 80a222cceba5066af908df41f5239ccd39f45d60 Mon Sep 17 00:00:00 2001 From: smoench Date: Fri, 25 Sep 2020 16:24:43 +0200 Subject: [PATCH 3/3] cs fixes --- src/PseudoTypes/CallableString.php | 2 +- src/PseudoTypes/HtmlEscapedString.php | 2 +- src/PseudoTypes/LowercaseString.php | 2 +- src/PseudoTypes/NonEmptyLowercaseString.php | 2 +- src/PseudoTypes/NonEmptyString.php | 2 +- src/PseudoTypes/NumericString.php | 2 +- src/PseudoTypes/PositiveInteger.php | 3 +-- src/PseudoTypes/TraitString.php | 4 ++-- 8 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/PseudoTypes/CallableString.php b/src/PseudoTypes/CallableString.php index f4ba49a..e9c7c14 100644 --- a/src/PseudoTypes/CallableString.php +++ b/src/PseudoTypes/CallableString.php @@ -24,7 +24,7 @@ */ final class CallableString extends String_ implements PseudoType { - public function underlyingType(): Type + public function underlyingType() : Type { return new String_(); } diff --git a/src/PseudoTypes/HtmlEscapedString.php b/src/PseudoTypes/HtmlEscapedString.php index 58cc1a7..7343517 100644 --- a/src/PseudoTypes/HtmlEscapedString.php +++ b/src/PseudoTypes/HtmlEscapedString.php @@ -24,7 +24,7 @@ */ final class HtmlEscapedString extends String_ implements PseudoType { - public function underlyingType(): Type + public function underlyingType() : Type { return new String_(); } diff --git a/src/PseudoTypes/LowercaseString.php b/src/PseudoTypes/LowercaseString.php index 7cea1b6..8fddbaa 100644 --- a/src/PseudoTypes/LowercaseString.php +++ b/src/PseudoTypes/LowercaseString.php @@ -24,7 +24,7 @@ */ final class LowercaseString extends String_ implements PseudoType { - public function underlyingType(): Type + public function underlyingType() : Type { return new String_(); } diff --git a/src/PseudoTypes/NonEmptyLowercaseString.php b/src/PseudoTypes/NonEmptyLowercaseString.php index 49857c7..bf24aaa 100644 --- a/src/PseudoTypes/NonEmptyLowercaseString.php +++ b/src/PseudoTypes/NonEmptyLowercaseString.php @@ -24,7 +24,7 @@ */ final class NonEmptyLowercaseString extends String_ implements PseudoType { - public function underlyingType(): Type + public function underlyingType() : Type { return new String_(); } diff --git a/src/PseudoTypes/NonEmptyString.php b/src/PseudoTypes/NonEmptyString.php index 24fcc64..f67c239 100644 --- a/src/PseudoTypes/NonEmptyString.php +++ b/src/PseudoTypes/NonEmptyString.php @@ -24,7 +24,7 @@ */ final class NonEmptyString extends String_ implements PseudoType { - public function underlyingType(): Type + public function underlyingType() : Type { return new String_(); } diff --git a/src/PseudoTypes/NumericString.php b/src/PseudoTypes/NumericString.php index f87b632..64300f6 100644 --- a/src/PseudoTypes/NumericString.php +++ b/src/PseudoTypes/NumericString.php @@ -24,7 +24,7 @@ */ final class NumericString extends String_ implements PseudoType { - public function underlyingType(): Type + public function underlyingType() : Type { return new String_(); } diff --git a/src/PseudoTypes/PositiveInteger.php b/src/PseudoTypes/PositiveInteger.php index 794fb0e..88ac7ed 100644 --- a/src/PseudoTypes/PositiveInteger.php +++ b/src/PseudoTypes/PositiveInteger.php @@ -16,7 +16,6 @@ use phpDocumentor\Reflection\PseudoType; use phpDocumentor\Reflection\Type; use phpDocumentor\Reflection\Types\Integer; -use phpDocumentor\Reflection\Types\String_; /** * Value Object representing the type 'string'. @@ -25,7 +24,7 @@ */ final class PositiveInteger extends Integer implements PseudoType { - public function underlyingType(): Type + public function underlyingType() : Type { return new Integer(); } diff --git a/src/PseudoTypes/TraitString.php b/src/PseudoTypes/TraitString.php index c321f7d..4e2f5df 100644 --- a/src/PseudoTypes/TraitString.php +++ b/src/PseudoTypes/TraitString.php @@ -24,9 +24,9 @@ */ final class TraitString extends String_ implements PseudoType { - public function underlyingType(): Type + public function underlyingType() : Type { - return new String_(); + return new String_(); } /**