Skip to content

Commit 4af636a

Browse files
committed
Move type extraction to base class and re-use in Throws
1 parent 07c5e27 commit 4af636a

File tree

5 files changed

+166
-67
lines changed

5 files changed

+166
-67
lines changed

src/DocBlock/Tags/BaseTag.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
<?php
2+
3+
declare(strict_types=1);
4+
25
/**
36
* This file is part of phpDocumentor.
47
*

src/DocBlock/Tags/Return_.php

Lines changed: 3 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,11 @@
2222
/**
2323
* Reflection class for a {@}return tag in a Docblock.
2424
*/
25-
final class Return_ extends BaseTag implements Factory\StaticMethod
25+
final class Return_ extends TagWithType implements Factory\StaticMethod
2626
{
27-
protected $name = 'return';
28-
29-
/** @var Type */
30-
private $type;
31-
3227
public function __construct(Type $type, Description $description = null)
3328
{
29+
$this->name = 'return';
3430
$this->type = $type;
3531
$this->description = $description;
3632
}
@@ -47,51 +43,16 @@ public static function create(
4743
Assert::string($body);
4844
Assert::allNotNull([$typeResolver, $descriptionFactory]);
4945

50-
list($type, $description) = self::splitBodyIntoTypeAndTheRest($body);
46+
list($type, $description) = self::extractTypeFromBody($body);
5147

5248
$type = $typeResolver->resolve($type, $context);
5349
$description = $descriptionFactory->create($description, $context);
5450

5551
return new static($type, $description);
5652
}
5753

58-
/**
59-
* Returns the type section of the variable.
60-
*
61-
* @return Type
62-
*/
63-
public function getType()
64-
{
65-
return $this->type;
66-
}
67-
6854
public function __toString()
6955
{
7056
return $this->type . ' ' . $this->description;
7157
}
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-
}
9758
}

src/DocBlock/Tags/TagWithType.php

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of phpDocumentor.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*
11+
* @copyright 2010-2015 Mike van Riel<[email protected]>
12+
* @license http://www.opensource.org/licenses/mit-license.php MIT
13+
* @link http://phpdoc.org
14+
*/
15+
16+
namespace phpDocumentor\Reflection\DocBlock\Tags;
17+
18+
use phpDocumentor\Reflection\Type;
19+
20+
abstract class TagWithType extends BaseTag
21+
{
22+
/** @var Type */
23+
protected $type;
24+
25+
/**
26+
* Returns the type section of the variable.
27+
*
28+
* @return Type
29+
*/
30+
public function getType()
31+
{
32+
return $this->type;
33+
}
34+
35+
protected static function extractTypeFromBody(string $body) : array
36+
{
37+
$type = '';
38+
$nestingLevel = 0;
39+
for ($i = 0; $i < strlen($body); $i++) {
40+
$character = $body[$i];
41+
42+
if (trim($character) === '' && $nestingLevel === 0) {
43+
break;
44+
}
45+
46+
$type .= $character;
47+
if (in_array($character, ['<', '(', '[', '{'])) {
48+
$nestingLevel++;
49+
}
50+
if (in_array($character, ['>', ')', ']', '}'])) {
51+
$nestingLevel--;
52+
}
53+
}
54+
55+
$description = trim(substr($body, strlen($type)));
56+
57+
return [$type, $description];
58+
}
59+
}

src/DocBlock/Tags/Throws.php

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,12 @@
2222
/**
2323
* Reflection class for a {@}throws tag in a Docblock.
2424
*/
25-
final class Throws extends BaseTag implements Factory\StaticMethod
25+
final class Throws extends TagWithType implements Factory\StaticMethod
2626
{
27-
protected $name = 'throws';
28-
29-
/** @var Type */
30-
private $type;
31-
3227
public function __construct(Type $type, Description $description = null)
3328
{
34-
$this->type = $type;
29+
$this->name = 'throws';
30+
$this->type = $type;
3531
$this->description = $description;
3632
}
3733

@@ -47,24 +43,14 @@ public static function create(
4743
Assert::string($body);
4844
Assert::allNotNull([$typeResolver, $descriptionFactory]);
4945

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

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

5551
return new static($type, $description);
5652
}
5753

58-
/**
59-
* Returns the type section of the variable.
60-
*
61-
* @return Type
62-
*/
63-
public function getType()
64-
{
65-
return $this->type;
66-
}
67-
6854
public function __toString()
6955
{
7056
return $this->type . ' ' . $this->description;

tests/unit/DocBlock/Tags/ThrowsTest.php

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

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

118118
/**
@@ -127,20 +127,110 @@ public function testStringRepresentationIsReturned()
127127
public function testFactoryMethod()
128128
{
129129
$descriptionFactory = m::mock(DescriptionFactory::class);
130-
$resolver = new TypeResolver();
131-
$context = new Context('');
130+
$resolver = new TypeResolver();
131+
$context = new Context('');
132132

133-
$type = new String_();
133+
$type = new String_();
134134
$description = new Description('My Description');
135135
$descriptionFactory->shouldReceive('create')->with('My Description', $context)->andReturn($description);
136136

137137
$fixture = Throws::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\Throws::<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+
Throws::create('array<string, string> My Description', $resolver, $descriptionFactory, $context);
177+
}
178+
179+
/**
180+
* @see self::testFactoryMethodWithGenericWithSpace()
181+
*
182+
* @covers ::create
183+
* @uses \phpDocumentor\Reflection\DocBlock\Tags\Throws::<public>
184+
* @uses \phpDocumentor\Reflection\DocBlock\DescriptionFactory
185+
* @uses \phpDocumentor\Reflection\TypeResolver
186+
* @uses \phpDocumentor\Reflection\DocBlock\Description
187+
* @uses \phpDocumentor\Reflection\Types\String_
188+
* @uses \phpDocumentor\Reflection\Types\Context
189+
*/
190+
public function testFactoryMethodWithGenericWithSpaceAndAddedEmojisToVerifyMultiByteBehaviour()
191+
{
192+
$this->expectException(\InvalidArgumentException::class);
193+
$this->expectExceptionMessage('"\array😁<string,😁 😁string>" is not a valid Fqsen.');
194+
195+
$descriptionFactory = m::mock(DescriptionFactory::class);
196+
$resolver = new TypeResolver();
197+
$context = new Context('');
198+
199+
$description = new Description('My Description');
200+
$descriptionFactory->shouldReceive('create')
201+
->with('My Description', $context)
202+
->andReturn($description);
203+
204+
Throws::create('array😁<string,😁 😁string> My Description', $resolver, $descriptionFactory, $context);
205+
}
206+
207+
/**
208+
* @covers ::create
209+
* @uses \phpDocumentor\Reflection\DocBlock\Tags\Throws::<public>
210+
* @uses \phpDocumentor\Reflection\DocBlock\DescriptionFactory
211+
* @uses \phpDocumentor\Reflection\TypeResolver
212+
* @uses \phpDocumentor\Reflection\DocBlock\Description
213+
* @uses \phpDocumentor\Reflection\Types\String_
214+
* @uses \phpDocumentor\Reflection\Types\Context
215+
*/
216+
public function testFactoryMethodWithEmojisToVerifyMultiByteBehaviour()
217+
{
218+
$descriptionFactory = m::mock(DescriptionFactory::class);
219+
$resolver = new TypeResolver();
220+
$context = new Context('');
221+
222+
$description = new Description('My Description');
223+
$descriptionFactory->shouldReceive('create')
224+
->with('My Description', $context)
225+
->andReturn($description);
226+
227+
$fixture = Throws::create('\My😁Class My Description', $resolver, $descriptionFactory, $context);
228+
229+
$this->assertSame('\My😁Class My Description', (string) $fixture);
230+
$this->assertEquals('\My😁Class', $fixture->getType());
231+
$this->assertSame($description, $fixture->getDescription());
232+
}
233+
144234
/**
145235
* @covers ::create
146236
* @expectedException \InvalidArgumentException

0 commit comments

Comments
 (0)