diff --git a/src/PseudoTypes/CallableString.php b/src/PseudoTypes/CallableString.php new file mode 100644 index 0000000..e9c7c14 --- /dev/null +++ b/src/PseudoTypes/CallableString.php @@ -0,0 +1,39 @@ + 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, + '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, ]; /** @@ -237,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); } @@ -446,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/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 @@ +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/src/Types/String_.php b/src/Types/String_.php index 6b92e41..010682a 100644 --- a/src/Types/String_.php +++ b/src/Types/String_.php @@ -20,7 +20,7 @@ * * @psalm-immutable */ -final class String_ implements Type +class String_ implements Type { /** * Returns a rendered output of the Type as it would be used in a DocBlock. diff --git a/tests/unit/TypeResolverTest.php b/tests/unit/TypeResolverTest.php index 92c1bc5..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_ @@ -698,8 +723,15 @@ public function provideKeywords() : array return [ ['string', 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], ['float', Types\Float_::class], ['double', Types\Float_::class], ['bool', Types\Boolean::class], @@ -711,17 +743,19 @@ public function provideKeywords() : array ['resource', Types\Resource_::class], ['null', Types\Null_::class], ['callable', Types\Callable_::class], + ['callable-string', PseudoTypes\CallableString::class], ['callback', Types\Callable_::class], - ['array', Array_::class], + ['array', Types\Array_::class], + ['array-key', Types\ArrayKey::class], ['scalar', Types\Scalar::class], - ['object', Object_::class], + ['object', Types\Object_::class], ['mixed', Types\Mixed_::class], ['void', Types\Void_::class], ['$this', Types\This::class], ['static', Types\Static_::class], ['self', Types\Self_::class], ['parent', Types\Parent_::class], - ['iterable', Iterable_::class], + ['iterable', Types\Iterable_::class], ]; } @@ -739,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/ArrayKeyTest.php b/tests/unit/Types/ArrayKeyTest.php new file mode 100644 index 0000000..86bbe70 --- /dev/null +++ b/tests/unit/Types/ArrayKeyTest.php @@ -0,0 +1,45 @@ +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); + } + } +} 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>'], + ]; + } +}