Skip to content

Commit

Permalink
[BUGFIX] Fix doc references containing anchors
Browse files Browse the repository at this point in the history
  • Loading branch information
linawolf committed May 3, 2024
1 parent 23af86b commit 4296dd3
Show file tree
Hide file tree
Showing 8 changed files with 263 additions and 5 deletions.
14 changes: 12 additions & 2 deletions packages/guides/src/ReferenceResolvers/DocReferenceResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
use phpDocumentor\Guides\RenderContext;
use phpDocumentor\Guides\Renderer\UrlGenerator\UrlGeneratorInterface;

use function explode;
use function sprintf;
use function str_contains;

final class DocReferenceResolver implements ReferenceResolver
{
Expand All @@ -40,7 +42,15 @@ public function resolve(LinkInlineNode $node, RenderContext $renderContext, Mess
return false;
}

$canonicalDocumentName = $this->documentNameResolver->canonicalUrl($renderContext->getDirName(), $node->getTargetReference());
$targetReference = $node->getTargetReference();
$anchor = '';
if (str_contains($targetReference, '#')) {
$exploded = explode('#', $targetReference, 2);
$targetReference = $exploded[0];
$anchor = '#' . $exploded[1];
}

$canonicalDocumentName = $this->documentNameResolver->canonicalUrl($renderContext->getDirName(), $targetReference);

$document = $renderContext->getProjectNode()->findDocumentEntry($canonicalDocumentName);
if ($document === null) {
Expand All @@ -53,7 +63,7 @@ public function resolve(LinkInlineNode $node, RenderContext $renderContext, Mess
return false;
}

$node->setUrl($this->urlGenerator->generateCanonicalOutputUrl($renderContext, $document->getFile()));
$node->setUrl($this->urlGenerator->generateCanonicalOutputUrl($renderContext, $document->getFile()) . $anchor);
if ($node->getValue() === '') {
$node->setValue($document->getTitle()->toString());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,17 @@
namespace phpDocumentor\Guides\ReferenceResolvers\Interlink;

use phpDocumentor\Guides\Nodes\Inline\CrossReferenceNode;
use phpDocumentor\Guides\Nodes\Inline\DocReferenceNode;
use phpDocumentor\Guides\ReferenceResolvers\AnchorNormalizer;
use phpDocumentor\Guides\ReferenceResolvers\Message;
use phpDocumentor\Guides\ReferenceResolvers\Messages;
use phpDocumentor\Guides\RenderContext;

use function array_key_exists;
use function array_merge;
use function explode;
use function sprintf;
use function str_contains;

final class InventoryGroup
{
Expand All @@ -47,7 +50,15 @@ public function hasLink(string $key): bool

public function getLink(CrossReferenceNode $node, RenderContext $renderContext, Messages $messages): InventoryLink|null
{
$reducedKey = $this->anchorNormalizer->reduceAnchor($node->getTargetReference());
$targetReference = $node->getTargetReference();
$anchor = '';
if ($node instanceof DocReferenceNode && str_contains($targetReference, '#')) {
$exploded = explode('#', $targetReference, 2);
$targetReference = $exploded[0];
$anchor = '#' . $exploded[1];
}

$reducedKey = $this->anchorNormalizer->reduceAnchor($targetReference);
if (!array_key_exists($reducedKey, $this->links)) {
$messages->addWarning(
new Message(
Expand All @@ -64,6 +75,11 @@ public function getLink(CrossReferenceNode $node, RenderContext $renderContext,
return null;
}

return $this->links[$reducedKey];
$link = $this->links[$reducedKey];
if ($anchor !== '') {
$link = $link->withPath($link->getPath() . $anchor);
}

return $link;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ final class InventoryLink
public function __construct(
private readonly string $project,
private readonly string $version,
private readonly string $path,
private string $path,
private readonly string $title,
) {
if (preg_match('/^([a-zA-Z0-9-_.]+\/)*([a-zA-Z0-9-_.])+\.html(#[^#]*)?$/', $path) < 1) {
Expand All @@ -49,4 +49,12 @@ public function getTitle(): string
{
return $this->title;
}

public function withPath(string $path): InventoryLink
{
$that = clone$this;
$that->path = $path;

return $that;
}
}
68 changes: 68 additions & 0 deletions packages/guides/tests/unit/Interlink/InventoryGroupTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

declare(strict_types=1);

/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link https://phpdoc.org
*/

namespace phpDocumentor\Guides\Interlink;

use phpDocumentor\Guides\Nodes\Inline\DocReferenceNode;
use phpDocumentor\Guides\ReferenceResolvers\Interlink\InventoryGroup;
use phpDocumentor\Guides\ReferenceResolvers\Interlink\InventoryLink;
use phpDocumentor\Guides\ReferenceResolvers\Messages;
use phpDocumentor\Guides\ReferenceResolvers\NullAnchorNormalizer;
use phpDocumentor\Guides\RenderContext;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;

final class InventoryGroupTest extends TestCase
{
private InventoryGroup $inventoryGroup;

private RenderContext&MockObject $renderContext;

protected function setUp(): void
{
$this->inventoryGroup = new InventoryGroup(new NullAnchorNormalizer());
$this->renderContext = $this->createMock(RenderContext::class);
}

#[DataProvider('linkProvider')]
public function testGetLinkFromInterlinkGroup(string $expected, string $input, string $path): void
{
$this->inventoryGroup->addLink($path, new InventoryLink('', '', $path . '.html', ''));
$messages = new Messages();
$link = $this->inventoryGroup->getLink(
new DocReferenceNode($input, '', 'interlink'),
$this->renderContext,
$messages,
);
self::assertEmpty($messages->getWarnings());
self::assertEquals($expected, $link?->getPath());
}

/** @return string[][] */
public static function linkProvider(): array
{
return [
'plain' => [
'expected' => 'some-document.html',
'input' => 'some-document',
'path' => 'some-document',
],
'withAnchor' => [
'expected' => 'some-document.html#anchor',
'input' => 'some-document#anchor',
'path' => 'some-document',
],
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

declare(strict_types=1);

/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link https://phpdoc.org
*/

namespace phpDocumentor\Guides\ReferenceResolvers;

use phpDocumentor\Guides\Nodes\DocumentTree\DocumentEntryNode;
use phpDocumentor\Guides\Nodes\Inline\DocReferenceNode;
use phpDocumentor\Guides\Nodes\ProjectNode;
use phpDocumentor\Guides\Nodes\TitleNode;
use phpDocumentor\Guides\RenderContext;
use phpDocumentor\Guides\Renderer\UrlGenerator\UrlGeneratorInterface;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;

final class DocReferenceResolverTest extends TestCase
{
private RenderContext&MockObject $renderContext;
private ProjectNode $projectNode;
private MockObject&UrlGeneratorInterface $urlGenerator;
private MockObject&DocumentNameResolverInterface $documentNameResolver;
private DocReferenceResolver $subject;

protected function setUp(): void
{
$documentEntry = new DocumentEntryNode('some-document', TitleNode::emptyNode());
$this->projectNode = new ProjectNode('some-name');
$this->projectNode->addDocumentEntry($documentEntry);
$this->renderContext = $this->createMock(RenderContext::class);
$this->renderContext->expects(self::once())->method('getProjectNode')->willReturn($this->projectNode);
$this->documentNameResolver = self::createMock(DocumentNameResolverInterface::class);
$this->urlGenerator = self::createMock(UrlGeneratorInterface::class);
$this->subject = new DocReferenceResolver($this->urlGenerator, $this->documentNameResolver);
}

#[DataProvider('pathProvider')]
public function testDocumentReducer(string $expected, string $input, string $path): void
{
$this->documentNameResolver->expects(self::once())->method('canonicalUrl')->with('', $path)->willReturn($path);
$input = new DocReferenceNode($input);
$this->urlGenerator->expects(self::once())->method('generateCanonicalOutputUrl')->willReturn($path);
$messages = new Messages();
self::assertTrue($this->subject->resolve($input, $this->renderContext, $messages));
self::assertEmpty($messages->getWarnings());
self::assertEquals($expected, $input->getUrl());
}

/** @return string[][] */
public static function pathProvider(): array
{
return [
'plain' => [
'expected' => 'some-document',
'input' => 'some-document',
'path' => 'some-document',
],
'withAnchor' => [
'expected' => 'some-document#anchor',
'input' => 'some-document#anchor',
'path' => 'some-document',
],
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

declare(strict_types=1);

/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link https://phpdoc.org
*/

namespace phpDocumentor\Guides\ReferenceResolvers;

use phpDocumentor\Guides\Nodes\Inline\DocReferenceNode;
use phpDocumentor\Guides\ReferenceResolvers\Interlink\Inventory;
use phpDocumentor\Guides\ReferenceResolvers\Interlink\InventoryLink;
use phpDocumentor\Guides\ReferenceResolvers\Interlink\InventoryRepository;
use phpDocumentor\Guides\RenderContext;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;

final class InterlinkReferenceResolverTest extends TestCase
{
private RenderContext&MockObject $renderContext;
private MockObject&InventoryRepository $inventoryRepository;
private InterlinkReferenceResolver $subject;
private AnchorNormalizer $anchorNormalizer;

protected function setUp(): void
{
$this->renderContext = $this->createMock(RenderContext::class);
$this->inventoryRepository = $this->createMock(InventoryRepository::class);
$this->anchorNormalizer = new NullAnchorNormalizer();
$this->subject = new InterlinkReferenceResolver($this->inventoryRepository);
}

#[DataProvider('pathProvider')]
public function testDocumentReducer(string $expected, string $input, string $path): void
{
$input = new DocReferenceNode($input, '', 'interlink-target');
$inventoryLink = new InventoryLink('project', '1.0', $path, '');
$inventory = new Inventory('base-url/', $this->anchorNormalizer);
$this->inventoryRepository->expects(self::once())->method('getInventory')->willReturn($inventory);
$this->inventoryRepository->expects(self::once())->method('getLink')->willReturn($inventoryLink);
$messages = new Messages();
self::assertTrue($this->subject->resolve($input, $this->renderContext, $messages));
self::assertEmpty($messages->getWarnings());
self::assertEquals($expected, $input->getUrl());
}

/** @return string[][] */
public static function pathProvider(): array
{
return [
'plain' => [
'expected' => 'base-url/some-document.html',
'input' => 'some-document',
'path' => 'some-document.html',
],
'withAnchor' => [
'expected' => 'base-url/some-document.html#anchor',
'input' => 'some-document#anchor',
'path' => 'some-document.html#anchor',
],
];
}
}
8 changes: 8 additions & 0 deletions tests/Integration/tests/navigation/docref/expected/index.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
<!-- content start -->
<div class="section" id="root">
<h1>Root</h1>


<ul>
<li><a href="/subfolder/index.html">Subfolder</a></li>
<li><a href="/subfolder/index.html#subfolder-index">Subfolder</a></li>
<li><a href="/subfolder/index.html#something">Subfolder</a></li>
</ul>

</div>
<!-- content end -->
4 changes: 4 additions & 0 deletions tests/Integration/tests/navigation/docref/input/index.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
====
Root
====

* :doc:`Subfolder <subfolder/index>`
* :doc:`Subfolder <subfolder/index#subfolder-index>`
* :doc:`Subfolder <subfolder/index#something>`

0 comments on commit 4296dd3

Please sign in to comment.