Skip to content

Commit f256f0b

Browse files
authored
Merge pull request #198 from phpDocumentor/fix/invalid-tag-usage-fix
Fix issue when processing invalid tags
2 parents 43f90ab + bb629f6 commit f256f0b

File tree

9 files changed

+164
-48
lines changed

9 files changed

+164
-48
lines changed

src/DocBlock/DescriptionFactory.php

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,7 @@ public function create(string $contents, ?TypeContext $context = null) : Descrip
6969
$tags = [];
7070

7171
for ($i = 1; $i < $count; $i += 2) {
72-
$tag = $this->tagFactory->create($tokens[$i], $context);
73-
if ($tag !== null) {
74-
$tags[] = $tag;
75-
}
72+
$tags[] = $this->tagFactory->create($tokens[$i], $context);
7673
$tokens[$i] = '%' . ++$tagCount . '$s';
7774
}
7875

src/DocBlock/StandardTagFactory.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use phpDocumentor\Reflection\DocBlock\Tags\Deprecated;
2020
use phpDocumentor\Reflection\DocBlock\Tags\Factory\StaticMethod;
2121
use phpDocumentor\Reflection\DocBlock\Tags\Generic;
22+
use phpDocumentor\Reflection\DocBlock\Tags\InvalidTag;
2223
use phpDocumentor\Reflection\DocBlock\Tags\Link as LinkTag;
2324
use phpDocumentor\Reflection\DocBlock\Tags\Method;
2425
use phpDocumentor\Reflection\DocBlock\Tags\Param;
@@ -138,7 +139,7 @@ public function __construct(FqsenResolver $fqsenResolver, ?array $tagHandlers =
138139
/**
139140
* {@inheritDoc}
140141
*/
141-
public function create(string $tagLine, ?TypeContext $context = null) : ?Tag
142+
public function create(string $tagLine, ?TypeContext $context = null) : Tag
142143
{
143144
if (!$context) {
144145
$context = new TypeContext('');
@@ -215,7 +216,7 @@ private function extractTagParts(string $tagLine) : array
215216
* Creates a new tag object with the given name and body or returns null if the tag name was recognized but the
216217
* body was invalid.
217218
*/
218-
private function createTag(string $body, string $name, TypeContext $context) : ?Tag
219+
private function createTag(string $body, string $name, TypeContext $context) : Tag
219220
{
220221
$handlerClassName = $this->findHandlerClassName($name, $context);
221222
$arguments = $this->getArgumentsForParametersFromWiring(
@@ -228,7 +229,7 @@ private function createTag(string $body, string $name, TypeContext $context) : ?
228229
$callable = [$handlerClassName, 'create'];
229230
return call_user_func_array($callable, $arguments);
230231
} catch (InvalidArgumentException $e) {
231-
return null;
232+
return InvalidTag::create($body, $name)->withError($e);
232233
}
233234
}
234235

src/DocBlock/TagFactory.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public function addParameter(string $name, $value) : void;
4949
*
5050
* @throws InvalidArgumentException If an invalid tag line was presented.
5151
*/
52-
public function create(string $tagLine, ?TypeContext $context = null) : ?Tag;
52+
public function create(string $tagLine, ?TypeContext $context = null) : Tag;
5353

5454
/**
5555
* Registers a service with the Service Locator using the FQCN of the class or the alias, if provided.

src/DocBlock/Tags/InvalidTag.php

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace phpDocumentor\Reflection\DocBlock\Tags;
6+
7+
use phpDocumentor\Reflection\DocBlock\Tag;
8+
use Throwable;
9+
10+
/**
11+
* This class represents an exception during the tag creation
12+
*
13+
* Since the internals of the library are relaying on the correct syntax of a docblock
14+
* we cannot simply throw exceptions at all time because the exceptions will break the creation of a
15+
* docklock. Just silently ignore the exceptions is not an option because the user as an issue to fix.
16+
*
17+
* This tag holds that error information until a using application is able to display it. The object wil just behave
18+
* like any normal tag. So the normal application flow will not break.
19+
*/
20+
final class InvalidTag implements Tag
21+
{
22+
/** @var string */
23+
private $name;
24+
25+
/** @var string */
26+
private $body;
27+
28+
/** @var Throwable|null */
29+
private $throwable;
30+
31+
private function __construct(string $name, string $body)
32+
{
33+
$this->name = $name;
34+
$this->body = $body;
35+
}
36+
37+
public function getException() : ?Throwable
38+
{
39+
return $this->throwable;
40+
}
41+
42+
public function getName() : string
43+
{
44+
return $this->name;
45+
}
46+
47+
/**
48+
* @return self
49+
*
50+
* @inheritDoc
51+
*/
52+
public static function create(string $body, string $name = '')
53+
{
54+
return new self($name, $body);
55+
}
56+
57+
public function withError(Throwable $exception) : self
58+
{
59+
$tag = new self($this->name, $this->body);
60+
$tag->throwable = $exception;
61+
62+
return $tag;
63+
}
64+
65+
public function render(?Formatter $formatter = null) : string
66+
{
67+
if ($formatter === null) {
68+
$formatter = new Formatter\PassthroughFormatter();
69+
}
70+
71+
return $formatter->format($this);
72+
}
73+
74+
public function __toString() : string
75+
{
76+
return $this->body;
77+
}
78+
}

src/DocBlockFactory.php

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
use LogicException;
1818
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
1919
use phpDocumentor\Reflection\DocBlock\StandardTagFactory;
20-
use phpDocumentor\Reflection\DocBlock\Tag;
2120
use phpDocumentor\Reflection\DocBlock\TagFactory;
2221
use Webmozart\Assert\Assert;
2322
use function array_shift;
@@ -236,12 +235,7 @@ private function parseTagBlock(string $tags, Types\Context $context) : array
236235
$result = [];
237236
$lines = $this->splitTagBlockIntoTagLines($tags);
238237
foreach ($lines as $key => $tagLine) {
239-
$tag = $this->tagFactory->create(trim($tagLine), $context);
240-
if (!($tag instanceof Tag)) {
241-
continue;
242-
}
243-
244-
$result[$key] = $tag;
238+
$result[$key] = $this->tagFactory->create(trim($tagLine), $context);
245239
}
246240

247241
return $result;

tests/unit/DocBlock/DescriptionFactoryTest.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313

1414
namespace phpDocumentor\Reflection\DocBlock;
1515

16+
use Exception;
1617
use Mockery as m;
18+
use phpDocumentor\Reflection\DocBlock\Tags\InvalidTag;
1719
use phpDocumentor\Reflection\DocBlock\Tags\Link as LinkTag;
1820
use phpDocumentor\Reflection\Types\Context;
1921
use PHPUnit\Framework\TestCase;
@@ -162,6 +164,31 @@ public function testIfSuperfluousStartingSpacesAreRemoved() : void
162164
$this->assertSame($expectedDescription, $description->render());
163165
}
164166

167+
/**
168+
* @uses \phpDocumentor\Reflection\DocBlock\Description
169+
* @uses \phpDocumentor\Reflection\DocBlock\Tags\InvalidTag
170+
* @uses \phpDocumentor\Reflection\DocBlock\Tags\Formatter\PassthroughFormatter
171+
* @uses \phpDocumentor\Reflection\Types\Context
172+
*
173+
* @covers ::__construct
174+
* @covers ::create
175+
*/
176+
public function testDescriptionWithBrokenInlineTags() : void
177+
{
178+
$contents = 'This {@see $name} is a broken use case, but used in real life.';
179+
$context = new Context('');
180+
$tagFactory = m::mock(TagFactory::class);
181+
$tagFactory->shouldReceive('create')
182+
->once()
183+
->with('@see $name', $context)
184+
->andReturn(InvalidTag::create('$name', 'see', new Exception()));
185+
186+
$factory = new DescriptionFactory($tagFactory);
187+
$description = $factory->create($contents, $context);
188+
189+
$this->assertSame($contents, $description->render());
190+
}
191+
165192
/**
166193
* Provides a series of example strings that the parser should correctly interpret and return.
167194
*

tests/unit/DocBlock/StandardTagFactoryTest.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use phpDocumentor\Reflection\DocBlock\Tags\Formatter;
1919
use phpDocumentor\Reflection\DocBlock\Tags\Formatter\PassthroughFormatter;
2020
use phpDocumentor\Reflection\DocBlock\Tags\Generic;
21+
use phpDocumentor\Reflection\DocBlock\Tags\InvalidTag;
2122
use phpDocumentor\Reflection\DocBlock\Tags\Return_;
2223
use phpDocumentor\Reflection\DocBlock\Tags\See;
2324
use phpDocumentor\Reflection\Fqsen;
@@ -330,4 +331,14 @@ public function testReturnTagIsMappedCorrectly() : void
330331
$this->assertInstanceOf(Return_::class, $tag);
331332
$this->assertSame('return', $tag->getName());
332333
}
334+
335+
public function testInvalidTagIsReturnedOnFailure() : void
336+
{
337+
$tagFactory = new StandardTagFactory(m::mock(FqsenResolver::class));
338+
339+
/** @var InvalidTag $tag */
340+
$tag = $tagFactory->create('@see $name some invalid tag');
341+
342+
$this->assertInstanceOf(InvalidTag::class, $tag);
343+
}
333344
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace phpDocumentor\Reflection\DocBlock\Tags;
6+
7+
use Exception;
8+
use PHPUnit\Framework\TestCase;
9+
10+
/**
11+
* @coversDefaultClass \phpDocumentor\Reflection\DocBlock\Tags\InvalidTag
12+
* @covers ::<private>
13+
* @covers ::getName
14+
* @covers ::render
15+
* @covers ::getException
16+
* @covers ::create
17+
*/
18+
final class InvalidTagTest extends TestCase
19+
{
20+
public function testCreationWithoutError() : void
21+
{
22+
$tag = InvalidTag::create('Body', 'name');
23+
24+
self::assertSame('name', $tag->getName());
25+
self::assertSame('@name Body', $tag->render());
26+
self::assertNull($tag->getException());
27+
}
28+
29+
/**
30+
* @covers ::withError
31+
*/
32+
public function testCreationWithError() : void
33+
{
34+
$exception = new Exception();
35+
$tag = InvalidTag::create('Body', 'name')->withError($exception);
36+
37+
self::assertSame('name', $tag->getName());
38+
self::assertSame('@name Body', $tag->render());
39+
self::assertSame($exception, $tag->getException());
40+
}
41+
}

tests/unit/DocBlockFactoryTest.php

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -279,37 +279,4 @@ public function testTagsWithContextNamespace() : void
279279

280280
$this->assertInstanceOf(DocBlock::class, $docblock);
281281
}
282-
283-
/**
284-
* @uses \phpDocumentor\Reflection\DocBlock\DescriptionFactory
285-
* @uses \phpDocumentor\Reflection\DocBlock\Description
286-
*
287-
* @covers ::__construct
288-
* @covers ::create
289-
*/
290-
public function testTagsAreFilteredForNullValues() : void
291-
{
292-
$tagString = <<<TAG
293-
@author Mike van Riel <[email protected]> This is with
294-
multiline description.
295-
TAG;
296-
297-
$tagFactory = m::mock(TagFactory::class);
298-
$tagFactory->shouldReceive('create')->with($tagString, m::any())->andReturn(null);
299-
300-
$fixture = new DocBlockFactory(new DescriptionFactory($tagFactory), $tagFactory);
301-
302-
$given = <<<DOCBLOCK
303-
/**
304-
* This is a summary.
305-
*
306-
* @author Mike van Riel <[email protected]> This is with
307-
* multiline description.
308-
*/
309-
DOCBLOCK;
310-
311-
$docblock = $fixture->create($given, new Context(''));
312-
313-
$this->assertEquals([], $docblock->getTags());
314-
}
315282
}

0 commit comments

Comments
 (0)