Skip to content

Commit aa64ffc

Browse files
committed
Support braces in types for @return
In this change, I have introduced a miniature automaton-light to parse the type from the @return body. This will prevent issues with people using generics and other unsupported forms of types. This change does _not_ allow for the use of Generics or similar; the TypeResolver will still fail to resolve this type. This will remove a breaking issue in consuming applications where a runtime exception used to be thrown. Please note that this change is only for @return; other tags still need to be done. This will resolve issue #186
1 parent 480f254 commit aa64ffc

File tree

2 files changed

+65
-5
lines changed

2 files changed

+65
-5
lines changed

src/DocBlock/Tags/Return_.php

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,10 @@ public static function create(
4747
Assert::string($body);
4848
Assert::allNotNull([$typeResolver, $descriptionFactory]);
4949

50-
$parts = preg_split('/\s+/Su', $body, 2);
50+
list($type, $description) = self::splitBodyIntoTypeAndTheRest($body);
5151

52-
$type = $typeResolver->resolve(isset($parts[0]) ? $parts[0] : '', $context);
53-
$description = $descriptionFactory->create(isset($parts[1]) ? $parts[1] : '', $context);
52+
$type = $typeResolver->resolve($type, $context);
53+
$description = $descriptionFactory->create($description, $context);
5454

5555
return new static($type, $description);
5656
}
@@ -69,4 +69,29 @@ public function __toString()
6969
{
7070
return $this->type . ' ' . $this->description;
7171
}
72+
73+
private static function splitBodyIntoTypeAndTheRest(string $body) : array
74+
{
75+
$type = '';
76+
$nestingLevel = 0;
77+
for ($i = 0; $i < strlen($body); $i++) {
78+
$character = $body[$i];
79+
80+
if (trim($character) === '' && $nestingLevel === 0) {
81+
break;
82+
}
83+
84+
$type .= $character;
85+
if (in_array($character, ['<', '(', '[', '{'])) {
86+
$nestingLevel++;
87+
}
88+
if (in_array($character, ['>', ')', ']', '}'])) {
89+
$nestingLevel--;
90+
}
91+
}
92+
93+
$description = trim(substr($body, strlen($type)));
94+
95+
return [$type, $description];
96+
}
7297
}

tests/unit/DocBlock/Tags/ReturnTest.php

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ public function testStringRepresentationIsReturned()
112112
{
113113
$fixture = new Return_(new String_(), new Description('Description'));
114114

115-
$this->assertSame('string Description', (string)$fixture);
115+
$this->assertSame('string Description', (string) $fixture);
116116
}
117117

118118
/**
@@ -136,11 +136,46 @@ public function testFactoryMethod()
136136

137137
$fixture = Return_::create('string My Description', $resolver, $descriptionFactory, $context);
138138

139-
$this->assertSame('string My Description', (string)$fixture);
139+
$this->assertSame('string My Description', (string) $fixture);
140140
$this->assertEquals($type, $fixture->getType());
141141
$this->assertSame($description, $fixture->getDescription());
142142
}
143143

144+
/**
145+
* This test checks whether a braces in a Type are allowed.
146+
*
147+
* 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
148+
* could purely rely on spaces to split the individual parts of the body of a tag; but when there is a type in play
149+
* we now need to check for braces.
150+
*
151+
* This test tests whether an error occurs demonstrating that the braces were taken into account; this test is still
152+
* expected to produce an exception because the TypeResolver does not support generics.
153+
*
154+
* @covers ::create
155+
* @uses \phpDocumentor\Reflection\DocBlock\Tags\Return_::<public>
156+
* @uses \phpDocumentor\Reflection\DocBlock\DescriptionFactory
157+
* @uses \phpDocumentor\Reflection\TypeResolver
158+
* @uses \phpDocumentor\Reflection\DocBlock\Description
159+
* @uses \phpDocumentor\Reflection\Types\String_
160+
* @uses \phpDocumentor\Reflection\Types\Context
161+
*/
162+
public function testFactoryMethodWithGenericWithSpace()
163+
{
164+
$this->expectException(\InvalidArgumentException::class);
165+
$this->expectExceptionMessage('"\array😁<string,😁 😁string>" is not a valid Fqsen.');
166+
167+
$descriptionFactory = m::mock(DescriptionFactory::class);
168+
$resolver = new TypeResolver();
169+
$context = new Context('');
170+
171+
$description = new Description('My Description');
172+
$descriptionFactory->shouldReceive('create')
173+
->with('My Description', $context)
174+
->andReturn($description);
175+
176+
Return_::create('array😁<string,😁 string> My Description', $resolver, $descriptionFactory, $context);
177+
}
178+
144179
/**
145180
* @covers ::create
146181
* @expectedException \InvalidArgumentException

0 commit comments

Comments
 (0)