From 5c59bc6d90a9beb9ce3344da4af367031ed34c2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felipe=20Say=C3=A3o=20Lobato=20Abreu?= Date: Tue, 27 Oct 2026 02:13:49 -0300 Subject: [PATCH] fix: resolve interface links through native routes --- .changelog/native-interface-links.md | 8 ++++++++ components/table-of-contents.html.twig | 6 +++--- tests/fixture/src/InterfaceExamples.php | 20 ++++++++++++++++++++ tests/verify-output.php | 24 ++++++++++++++++++++++++ 4 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 .changelog/native-interface-links.md create mode 100644 tests/fixture/src/InterfaceExamples.php diff --git a/.changelog/native-interface-links.md b/.changelog/native-interface-links.md new file mode 100644 index 0000000..7e883ec --- /dev/null +++ b/.changelog/native-interface-links.md @@ -0,0 +1,8 @@ +--- +category: fixed +type: patch +--- + +Resolve implemented interface links through phpDocumentor's native renderer so +external interfaces without generated API pages remain visible without broken +local links, and documented interfaces retain a single valid link. diff --git a/components/table-of-contents.html.twig b/components/table-of-contents.html.twig index ced9b34..8b2e906 100644 --- a/components/table-of-contents.html.twig +++ b/components/table-of-contents.html.twig @@ -55,9 +55,9 @@
{% for interface in node.interfaces %}
- + {{ interface|route('class:short') }} - + {% if interface.summary %}

{{ interface.summary }}

{% endif %} @@ -210,4 +210,4 @@ {% endif %}
- \ No newline at end of file + diff --git a/tests/fixture/src/InterfaceExamples.php b/tests/fixture/src/InterfaceExamples.php new file mode 100644 index 0000000..be03cb7 --- /dev/null +++ b/tests/fixture/src/InterfaceExamples.php @@ -0,0 +1,20 @@ +query('//base[@href]')->item(0); $base = localTarget($source, $baseElement?->getAttribute('href') ?? './'); requireValid($base !== null, "External base URL: {$source}"); + + if (basename($source) === 'FastForward-Documentation-InterfaceExample.html') { + $interfaces = $xpath->query('//*[@id="toc-interfaces"]/ancestor::section[1]')->item(0); + requireValid($interfaces !== null, 'Implemented interfaces section missing'); + requireValid(str_contains($interfaces->textContent, 'MissingContract'), 'Unresolved external interface name missing'); + requireValid($xpath->query('.//abbr[@title="\\External\\Package\\MissingContract"]', $interfaces)->length === 1, 'Namespaced external interface title missing'); + requireValid($xpath->query('.//a[contains(@href, "External-Package-MissingContract")]', $interfaces)->length === 0, 'Unresolved external interface must remain visible without a fabricated local link'); + $internalLinks = 0; + foreach ($xpath->query('.//a[@href]', $interfaces) as $anchor) { + $target = localTarget($base['path'], $anchor->getAttribute('href')); + requireValid($target !== null && is_file($target['path']), 'Interface link has no generated target: ' . $anchor->getAttribute('href')); + requireValid(basename($target['path']) === 'FastForward-Documentation-LocalContract.html', 'Unexpected interface target'); + requireValid(str_contains($anchor->textContent, 'LocalContract'), 'Local interface link text missing'); + ++$internalLinks; + } + requireValid($internalLinks === 1, 'Documented interface must have exactly one native link'); + } + + if (basename($source) === 'FastForward-Documentation-NoInterfaceExample.html') { + requireValid($xpath->query('//*[@id="toc-interfaces"]')->length === 0, 'Empty implemented interfaces section rendered'); + } foreach ($xpath->query('//img[@src] | //script[@src] | //link[@href]') as $element) { $href = $element->getAttribute($element->tagName === 'link' ? 'href' : 'src'); $target = localTarget($base['path'], $href); @@ -155,6 +176,9 @@ function verify(string $output): void requireValid(is_file($output . '/index.html'), 'Root guide not generated'); requireValid(is_file($output . '/guides/installation.html'), 'Nested guide not generated'); requireValid(is_file($output . '/classes/FastForward-Documentation-Example.html'), 'API not generated'); + foreach (['LocalContract', 'InterfaceExample', 'NoInterfaceExample'] as $name) { + requireValid(is_file($output . '/classes/FastForward-Documentation-' . $name . '.html'), 'Interface regression fixture not generated: ' . $name); + } $source = $output . '/files/src/Example.php.txt'; requireValid(is_file($source), 'Source View fixture not generated'); requireValid(hash_file('sha256', $source) === hash_file('sha256', $template . '/tests/fixture/src/Example.php'), 'Source View bytes changed');