Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions src/DocBlock/Tags/Reference/Fqsen.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,25 @@ final class Fqsen implements Reference
{
private RealFqsen $fqsen;

public function __construct(RealFqsen $fqsen)
private ?string $bookmark;

public function __construct(RealFqsen $fqsen, ?string $bookmark = null)
{
$this->fqsen = $fqsen;
$this->bookmark = $bookmark;
}

/**
* Returns the bookmark suffix declared with `#<bookmark>` in the docblock, or null when none was given.
*/
public function getBookmark(): ?string
{
return $this->bookmark;
}

/**
* @return string string representation of the referenced fqsen
* @return string string representation of the referenced fqsen, without the bookmark suffix so
* callers can feed it back to {@see \phpDocumentor\Reflection\Fqsen} safely
*/
public function __toString(): string
{
Expand Down
14 changes: 13 additions & 1 deletion src/DocBlock/Tags/See.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,16 @@ public static function create(
return new static(new Url($parts[0]), $description);
}

return new static(new FqsenRef(self::resolveFqsen($parts[0], $typeResolver, $context)), $description);
$fragments = explode('#', $parts[0], 2);
$bookmark = $fragments[1] ?? null;

return new static(
new FqsenRef(
self::resolveFqsen($fragments[0], $typeResolver, $context),
$bookmark === '' ? null : $bookmark
),
$description
);
}

private static function resolveFqsen(string $parts, ?FqsenResolver $fqsenResolver, ?TypeContext $context): Fqsen
Expand Down Expand Up @@ -98,6 +107,9 @@ public function __toString(): string
}

$refers = (string) $this->refers;
if ($this->refers instanceof FqsenRef && $this->refers->getBookmark() !== null) {
$refers .= '#' . $this->refers->getBookmark();
}

return $refers . ($description !== '' ? ($refers !== '' ? ' ' : '') . $description : '');
}
Expand Down
56 changes: 56 additions & 0 deletions tests/integration/DocblockSeeTagResolvingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace phpDocumentor\Reflection;

use phpDocumentor\Reflection\DocBlock\Tags\Reference\Fqsen as FqsenRef;
use phpDocumentor\Reflection\DocBlock\Tags\See;
use phpDocumentor\Reflection\Types\Context;
use PHPUnit\Framework\TestCase;
Expand Down Expand Up @@ -38,4 +39,59 @@ public function testResolvesSeeFQSENOfInlineTags()

$this->assertSame('\Project\Other\Level\Issue2425B::bar()', (string)$see1->getReference());
}

public function testResolvesSeeFQSENWithBookmarkFromTopLevelTag(): void
{
$docblockString = <<<'DOCBLOCK'
/**
* Class summary.
*
* @see \Project\Other\Level\Issue3710::run()#42 Jumps inside the runner
*/
DOCBLOCK;

$factory = DocBlockFactory::createInstance();
$docblock = $factory->create($docblockString);

$seeTags = $docblock->getTagsByName('see');
$this->assertCount(1, $seeTags);

/** @var See $see */
$see = $seeTags[0];
$reference = $see->getReference();

$this->assertInstanceOf(FqsenRef::class, $reference);
$this->assertSame('42', $reference->getBookmark());
$this->assertSame('\Project\Other\Level\Issue3710::run()', (string) $reference);
$this->assertSame(
'\Project\Other\Level\Issue3710::run()#42 Jumps inside the runner',
(string) $see
);
}

public function testResolvesSeeFQSENWithBookmarkFromInlineTag(): void
{
$context = new Context('\Project\Sub\Level', ['Runner' => '\Project\Other\Level\Issue3710']);
$docblockString = <<<'DOCBLOCK'
/**
* Class summary.
*
* A description containing {@see Runner::run()#42} with an inline bookmark.
*/
DOCBLOCK;

$factory = DocBlockFactory::createInstance();
$docblock = $factory->create($docblockString, $context);

$inlineTags = $docblock->getDescription()->getTags();
$this->assertCount(1, $inlineTags);

/** @var See $see */
$see = $inlineTags[0];
$reference = $see->getReference();

$this->assertInstanceOf(FqsenRef::class, $reference);
$this->assertSame('42', $reference->getBookmark());
$this->assertSame('\Project\Other\Level\Issue3710::run()', (string) $reference);
}
}
160 changes: 160 additions & 0 deletions tests/unit/DocBlock/Tags/SeeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,166 @@ public function testFactoryMethodWithoutUrl(): void
$this->assertSame('My Description ', $fixture->getDescription() . '');
}

/**
* @uses \phpDocumentor\Reflection\DocBlock\Tags\See::<public>
* @uses \phpDocumentor\Reflection\DocBlock\DescriptionFactory
* @uses \phpDocumentor\Reflection\FqsenResolver
* @uses \phpDocumentor\Reflection\DocBlock\Description
* @uses \phpDocumentor\Reflection\DocBlock\Tags\Reference\Fqsen
* @uses \phpDocumentor\Reflection\Fqsen
* @uses \phpDocumentor\Reflection\Types\Context
*
* @covers ::create
*/
public function testFactoryMethodWithBookmark(): void
{
$fqsenResolver = new FqsenResolver();
$descriptionFactory = new DescriptionFactory($this->createMock(TagFactory::class));
$context = new Context('');

$fixture = See::create(
'\DateTime::format()#42 Jumps inside the formatter',
$fqsenResolver,
$descriptionFactory,
$context
);

$reference = $fixture->getReference();
$this->assertInstanceOf(TagsFqsen::class, $reference);
$this->assertSame('42', $reference->getBookmark());
$this->assertSame('\DateTime::format()', (string) $reference);
$this->assertSame(
'\DateTime::format()#42 Jumps inside the formatter',
(string) $fixture
);
}

/**
* @uses \phpDocumentor\Reflection\DocBlock\Tags\See::<public>
* @uses \phpDocumentor\Reflection\DocBlock\DescriptionFactory
* @uses \phpDocumentor\Reflection\FqsenResolver
* @uses \phpDocumentor\Reflection\DocBlock\Description
* @uses \phpDocumentor\Reflection\DocBlock\Tags\Reference\Fqsen
* @uses \phpDocumentor\Reflection\Fqsen
* @uses \phpDocumentor\Reflection\Types\Context
*
* @covers ::create
* @covers ::__toString
*/
public function testFactoryMethodRendersBookmarkWithoutDescription(): void
{
$fqsenResolver = new FqsenResolver();
$descriptionFactory = new DescriptionFactory($this->createMock(TagFactory::class));
$context = new Context('');

$fixture = See::create('\DateTime::format()#42', $fqsenResolver, $descriptionFactory, $context);

$reference = $fixture->getReference();
$this->assertInstanceOf(TagsFqsen::class, $reference);
$this->assertSame('42', $reference->getBookmark());
$this->assertSame('\DateTime::format()#42', (string) $fixture);
}

/**
* @uses \phpDocumentor\Reflection\DocBlock\Tags\See::<public>
* @uses \phpDocumentor\Reflection\DocBlock\DescriptionFactory
* @uses \phpDocumentor\Reflection\FqsenResolver
* @uses \phpDocumentor\Reflection\DocBlock\Description
* @uses \phpDocumentor\Reflection\DocBlock\Tags\Reference\Fqsen
* @uses \phpDocumentor\Reflection\Fqsen
* @uses \phpDocumentor\Reflection\Types\Context
*
* @covers ::create
*/
public function testFactoryMethodPreservesAdditionalHashesInBookmark(): void
{
$fqsenResolver = new FqsenResolver();
$descriptionFactory = new DescriptionFactory($this->createMock(TagFactory::class));
$context = new Context('');

$fixture = See::create('\DateTime::format()#a#b', $fqsenResolver, $descriptionFactory, $context);

$reference = $fixture->getReference();
$this->assertInstanceOf(TagsFqsen::class, $reference);
$this->assertSame('a#b', $reference->getBookmark());
$this->assertSame('\DateTime::format()#a#b', (string) $fixture);
}

/**
* @uses \phpDocumentor\Reflection\DocBlock\Tags\See::<public>
* @uses \phpDocumentor\Reflection\DocBlock\DescriptionFactory
* @uses \phpDocumentor\Reflection\FqsenResolver
* @uses \phpDocumentor\Reflection\DocBlock\Description
* @uses \phpDocumentor\Reflection\DocBlock\Tags\Reference\Fqsen
* @uses \phpDocumentor\Reflection\Fqsen
* @uses \phpDocumentor\Reflection\Types\Context
*
* @covers ::create
*/
public function testFactoryMethodNormalizesTrailingHashToNullBookmark(): void
{
$fqsenResolver = new FqsenResolver();
$descriptionFactory = new DescriptionFactory($this->createMock(TagFactory::class));
$context = new Context('');

$fixture = See::create('\DateTime#', $fqsenResolver, $descriptionFactory, $context);

$reference = $fixture->getReference();
$this->assertInstanceOf(TagsFqsen::class, $reference);
$this->assertNull($reference->getBookmark());
$this->assertSame('\DateTime', (string) $reference);
}

/**
* @uses \phpDocumentor\Reflection\DocBlock\Tags\See::<public>
* @uses \phpDocumentor\Reflection\DocBlock\DescriptionFactory
* @uses \phpDocumentor\Reflection\FqsenResolver
* @uses \phpDocumentor\Reflection\DocBlock\Description
* @uses \phpDocumentor\Reflection\DocBlock\Tags\Reference\Url
* @uses \phpDocumentor\Reflection\Types\Context
*
* @covers ::create
*/
public function testFactoryMethodKeepsUrlFragmentWithinUrlReference(): void
{
$descriptionFactory = m::mock(DescriptionFactory::class);
$resolver = m::mock(FqsenResolver::class);
$context = new Context('');

$descriptionFactory->shouldReceive('create')->andReturn(new Description(''));
$resolver->shouldNotReceive('resolve');

$fixture = See::create('https://example.org/page#section', $resolver, $descriptionFactory, $context);

$this->assertInstanceOf(UrlRef::class, $fixture->getReference());
$this->assertSame('https://example.org/page#section', (string) $fixture->getReference());
}

/**
* @uses \phpDocumentor\Reflection\DocBlock\Tags\See::<public>
* @uses \phpDocumentor\Reflection\DocBlock\DescriptionFactory
* @uses \phpDocumentor\Reflection\FqsenResolver
* @uses \phpDocumentor\Reflection\DocBlock\Description
* @uses \phpDocumentor\Reflection\DocBlock\Tags\Reference\Fqsen
* @uses \phpDocumentor\Reflection\Fqsen
* @uses \phpDocumentor\Reflection\Types\Context
*
* @covers ::create
*/
public function testFactoryMethodLeavesBookmarkNullWhenAbsent(): void
{
$fqsenResolver = new FqsenResolver();
$descriptionFactory = new DescriptionFactory($this->createMock(TagFactory::class));
$context = new Context('');

$fixture = See::create('\DateTime::format()', $fqsenResolver, $descriptionFactory, $context);

$reference = $fixture->getReference();
$this->assertInstanceOf(TagsFqsen::class, $reference);
$this->assertNull($reference->getBookmark());
$this->assertSame('\DateTime::format()', (string) $reference);
}

/**
* @covers ::create
*/
Expand Down