diff --git a/src/DocBlock/Tags/Return_.php b/src/DocBlock/Tags/Return_.php index 2fb8135..1ae6632 100644 --- a/src/DocBlock/Tags/Return_.php +++ b/src/DocBlock/Tags/Return_.php @@ -50,11 +50,10 @@ final class Return_ extends BaseTag implements Factory\StaticMethod Assert::notNull($typeResolver); Assert::notNull($descriptionFactory); - $parts = preg_split('/\s+/Su', $body, 2); - Assert::isArray($parts); + list($type, $description) = self::splitBodyIntoTypeAndTheRest($body); - $type = $typeResolver->resolve($parts[0] ?? '', $context); - $description = $descriptionFactory->create($parts[1] ?? '', $context); + $type = $typeResolver->resolve($type, $context); + $description = $descriptionFactory->create($description, $context); return new static($type, $description); } @@ -71,4 +70,29 @@ final class Return_ extends BaseTag implements Factory\StaticMethod { return $this->type . ' ' . (string) $this->description; } + + private static function splitBodyIntoTypeAndTheRest(string $body) : array + { + $type = ''; + $nestingLevel = 0; + for ($i = 0; $i < strlen($body); $i++) { + $character = $body[$i]; + + if (trim($character) === '' && $nestingLevel === 0) { + break; + } + + $type .= $character; + if (in_array($character, ['<', '(', '[', '{'])) { + $nestingLevel++; + } + if (in_array($character, ['>', ')', ']', '}'])) { + $nestingLevel--; + } + } + + $description = trim(substr($body, strlen($type))); + + return [$type, $description]; + } } diff --git a/tests/unit/DocBlock/Tags/ReturnTest.php b/tests/unit/DocBlock/Tags/ReturnTest.php index 63ace03..18a0dc5 100644 --- a/tests/unit/DocBlock/Tags/ReturnTest.php +++ b/tests/unit/DocBlock/Tags/ReturnTest.php @@ -148,6 +148,41 @@ class ReturnTest extends TestCase $this->assertSame($description, $fixture->getDescription()); } + /** + * This test checks whether a braces in a Type are allowed. + * + * The advent of generics poses a few issues, one of them is that spaces can now be part of a type. In the past we + * could purely rely on spaces to split the individual parts of the body of a tag; but when there is a type in play + * we now need to check for braces. + * + * This test tests whether an error occurs demonstrating that the braces were taken into account; this test is still + * expected to produce an exception because the TypeResolver does not support generics. + * + * @covers ::create + * @uses \phpDocumentor\Reflection\DocBlock\Tags\Return_:: + * @uses \phpDocumentor\Reflection\DocBlock\DescriptionFactory + * @uses \phpDocumentor\Reflection\TypeResolver + * @uses \phpDocumentor\Reflection\DocBlock\Description + * @uses \phpDocumentor\Reflection\Types\String_ + * @uses \phpDocumentor\Reflection\Types\Context + */ + public function testFactoryMethodWithGenericWithSpace() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('"\array😁" is not a valid Fqsen.'); + + $descriptionFactory = m::mock(DescriptionFactory::class); + $resolver = new TypeResolver(); + $context = new Context(''); + + $description = new Description('My Description'); + $descriptionFactory->shouldReceive('create') + ->with('My Description', $context) + ->andReturn($description); + + Return_::create('array😁 My Description', $resolver, $descriptionFactory, $context); + } + /** * @covers ::create */