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
7 changes: 7 additions & 0 deletions docs/developers/compiler.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ interface and you should register it in the dependency injection container.
the priority of the node transformer, the earlier it will be executed. Where highest priority is `PHP_INT_MAX`, lower
number is lower priority.

A node transformer visits the children of each node, and also the nodes a node keeps in properties of its own, like
the term of a definition-list item, the caption of a code block or the cells of a table. A node that keeps such nodes
implements :php:interface:`phpDocumentor\Guides\Nodes\HasAttachedNodes` and names them in ``getAttachedNodes()``.
A transformer can change what is inside an attached node, but not replace or remove the attached node itself. Implement
the interface on your own nodes too, if they keep nodes outside their children, so the node transformers reach these
nodes as well.

CompilerPass
============

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
namespace phpDocumentor\Guides\RestructuredText\Nodes;

use phpDocumentor\Guides\Nodes\CompoundNode;
use phpDocumentor\Guides\Nodes\HasAttachedNodes;
use phpDocumentor\Guides\Nodes\InlineCompoundNode;
use phpDocumentor\Guides\Nodes\LinkTargetNode;
use phpDocumentor\Guides\Nodes\Node;
Expand All @@ -27,7 +28,7 @@
*
* @extends CompoundNode<Node>
*/
final class ConfvalNode extends CompoundNode implements LinkTargetNode, OptionalLinkTargetsNode, PrefixedLinkTargetNode
final class ConfvalNode extends CompoundNode implements LinkTargetNode, OptionalLinkTargetsNode, PrefixedLinkTargetNode, HasAttachedNodes
{
public const LINK_TYPE = 'std:confval';
public const LINK_PREFIX = 'confval-';
Expand Down Expand Up @@ -104,4 +105,23 @@ public function isNoindex(): bool
{
return $this->noindex;
}

/** @return array<string, InlineCompoundNode> */
public function getAttachedNodes(): array
{
$nodes = [];
if ($this->type !== null) {
$nodes['type'] = $this->type;
}

if ($this->default !== null) {
$nodes['default'] = $this->default;
}

foreach ($this->additionalOptions as $option => $value) {
$nodes['option-' . $option] = $value;
}

return $nodes;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
namespace phpDocumentor\Guides\RestructuredText\Nodes;

use phpDocumentor\Guides\Nodes\CompoundNode;
use phpDocumentor\Guides\Nodes\HasAttachedNodes;
use phpDocumentor\Guides\Nodes\InlineCompoundNode;
use phpDocumentor\Guides\Nodes\Node;

Expand All @@ -23,7 +24,7 @@
* @template TValue of Node = Node
* @extends CompoundNode<TValue>
*/
class GeneralDirectiveNode extends CompoundNode
class GeneralDirectiveNode extends CompoundNode implements HasAttachedNodes
{
/** @param TValue[] $value */
public function __construct(
Expand All @@ -49,4 +50,10 @@ public function getContent(): InlineCompoundNode
{
return $this->content;
}

/** @return array<string, Node> */
public function getAttachedNodes(): array
{
return ['content' => $this->getContent()];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@
namespace phpDocumentor\Guides\RestructuredText\Nodes;

use phpDocumentor\Guides\Nodes\CompoundNode;
use phpDocumentor\Guides\Nodes\HasAttachedNodes;
use phpDocumentor\Guides\Nodes\InlineCompoundNode;
use phpDocumentor\Guides\Nodes\Node;

/** @extends CompoundNode<Node> */
final class SidebarNode extends CompoundNode
final class SidebarNode extends CompoundNode implements HasAttachedNodes
{
/** {@inheritDoc} */
public function __construct(private readonly InlineCompoundNode $title, array $value)
Expand All @@ -30,4 +31,10 @@ public function getTitle(): InlineCompoundNode
{
return $this->title;
}

/** @return array<string, InlineCompoundNode> */
public function getAttachedNodes(): array
{
return ['title' => $this->title];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?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\RestructuredText\Nodes;

use Generator;
use phpDocumentor\Guides\Nodes\HasAttachedNodes;
use phpDocumentor\Guides\Nodes\InlineCompoundNode;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;

use function array_keys;

final class HasAttachedNodesTest extends TestCase
{
/** @param list<string> $names */
#[DataProvider('provideNodes')]
public function testNamesTheAttachedNodes(HasAttachedNodes $node, array $names): void
{
self::assertSame($names, array_keys($node->getAttachedNodes()));
}

/** @return Generator<string, array{HasAttachedNodes, list<string>}> */
public static function provideNodes(): Generator
{
yield 'sidebar' => [new SidebarNode(InlineCompoundNode::getPlainTextInlineNode('title'), []), ['title']];

yield 'confval' => [
new ConfvalNode(
'id',
'id',
InlineCompoundNode::getPlainTextInlineNode('string'),
false,
InlineCompoundNode::getPlainTextInlineNode('default'),
['scope' => InlineCompoundNode::getPlainTextInlineNode('global')],
),
['type', 'default', 'option-scope'],
];

yield 'confval without type and default' => [new ConfvalNode('id', 'id'), []];

yield 'general directive' => [
new GeneralDirectiveNode('custom', 'argument', InlineCompoundNode::getPlainTextInlineNode('argument')),
['content'],
];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,10 @@ public function isShow(): bool
{
return $this->show;
}

/** @return array<string, Node> */
public function getAttachedNodes(): array
{
return ['title' => $this->title, ...parent::getAttachedNodes()];
}
}
17 changes: 17 additions & 0 deletions packages/guides-theme-bootstrap/src/Bootstrap/Nodes/CardNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
use phpDocumentor\Guides\Nodes\TitleNode;
use phpDocumentor\Guides\RestructuredText\Nodes\GeneralDirectiveNode;

use function array_filter;

final class CardNode extends GeneralDirectiveNode implements LinkTargetNode, OptionalLinkTargetsNode, PrefixedLinkTargetNode
{
public const LINK_TYPE = 'std:card';
Expand Down Expand Up @@ -129,4 +131,19 @@ public function setCardHeight(int $cardHeight): CardNode

return $this;
}

/** @return array<string, Node> */
public function getAttachedNodes(): array
{
return array_filter(
[
'header' => $this->cardHeader,
'image' => $this->cardImage,
'title' => $this->title,
...parent::getAttachedNodes(),
'footer' => $this->cardFooter,
],
static fn (Node|null $node): bool => $node !== null,
);
}
}
8 changes: 8 additions & 0 deletions packages/guides/src/Compiler/DocumentNodeTraverser.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ private function traverseForTransformer(
CompilerContext $compilerContext,
): void {
if ($transformer instanceof ReverseNodeTransformer) {
foreach ($shadowNode->getAttachedNodes() as $shadowChild) {
$this->traverseForTransformer($transformer, $shadowChild, $compilerContext);
}

foreach ($shadowNode->getChildren() as $shadowChild) {
$this->traverseForTransformer($transformer, $shadowChild, $compilerContext);
}
Expand All @@ -87,6 +91,10 @@ private function traverseForTransformer(
}

if ($transformer instanceof ReverseNodeTransformer === false) {
foreach ($shadowNode->getAttachedNodes() as $shadowChild) {
$this->traverseForTransformer($transformer, $shadowChild, $compilerContext);
}

foreach ($shadowNode->getChildren() as $shadowChild) {
$this->traverseForTransformer($transformer, $shadowChild, $compilerContext);
}
Expand Down
60 changes: 60 additions & 0 deletions packages/guides/src/Compiler/ShadowTree/TreeNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@
use LogicException;
use phpDocumentor\Guides\Nodes\CompoundNode;
use phpDocumentor\Guides\Nodes\DocumentNode;
use phpDocumentor\Guides\Nodes\HasAttachedNodes;
use phpDocumentor\Guides\Nodes\Node;

use function array_unshift;
use function array_values;
use function count;
use function sprintf;

/** @template-covariant TNode of Node */
final class TreeNode
Expand All @@ -31,6 +33,15 @@ final class TreeNode
/** @var self<Node>[] */
private array $children = [];

/** @var array<string, self<Node>> */
private array $attachedNodes = [];

/**
* An attached node is held in a property of its parent node, which cannot take a copy of
* it, so changes to its children are made in the attached node itself.
*/
private bool $isAttached = false;

private function __construct(
/** @var TNode */
private Node $node,
Expand Down Expand Up @@ -77,6 +88,13 @@ private static function createFromNode(Node $node, self $parent): self
{
$treeNode = new self($node, $parent);
$treeNode->root = $parent->root;
if ($node instanceof HasAttachedNodes) {
foreach ($node->getAttachedNodes() as $name => $attachedNode) {
$treeNode->attachedNodes[$name] = self::createFromNode($attachedNode, $treeNode);
$treeNode->attachedNodes[$name]->isAttached = true;
}
}

if ($node instanceof CompoundNode === false) {
return $treeNode;
}
Expand Down Expand Up @@ -114,6 +132,16 @@ public function getChildren(): array
return $this->children;
}

/**
* The nodes the node keeps beside its children, see {@see HasAttachedNodes}.
*
* @return array<string, TreeNode<Node>>
*/
public function getAttachedNodes(): array
{
return $this->attachedNodes;
}

public function addChild(Node $child): void
{
if ($this->node instanceof CompoundNode === false) {
Expand Down Expand Up @@ -142,6 +170,8 @@ public function getParent(): TreeNode|null

public function removeChild(Node $node): void
{
$this->assertNotAttached($node, 'remove');

if ($this->node instanceof CompoundNode === false) {
throw new LogicException('Cannot remove a child from a non-compound node');
}
Expand All @@ -150,6 +180,13 @@ public function removeChild(Node $node): void
if ($child->getNode() === $node) {
unset($this->children[$key]);
$child->parent = null;
if ($this->isAttached) {
$children = $this->node->getChildren();
unset($children[$key]);
$this->node->setValue(array_values($children));
break;
}

$newNode = $this->node->removeNode($key);
$this->parent?->replaceChild($this->node, $newNode);
$this->node = $newNode;
Expand All @@ -162,13 +199,22 @@ public function removeChild(Node $node): void

public function replaceChild(Node $oldChildNode, Node $newChildNode): void
{
$this->assertNotAttached($oldChildNode, 'replace');

if ($this->node instanceof CompoundNode === false) {
throw new LogicException('Cannot remove a child from a non-compound node');
}

foreach ($this->children as $key => $child) {
if ($child->getNode() === $oldChildNode) {
$child->node = $newChildNode;
if ($this->isAttached) {
$children = $this->node->getChildren();
$children[$key] = $newChildNode;
$this->node->setValue($children);
break;
}

$newNode = $this->node->replaceNode($key, $newChildNode);
$this->parent?->replaceChild($this->node, $newNode);
$this->node = $newNode;
Expand All @@ -194,10 +240,15 @@ public function findPosition(Node $node): int|null
*/
public function release(): void
{
foreach ($this->attachedNodes as $attachedNode) {
$attachedNode->release();
}

foreach ($this->children as $child) {
$child->release();
}

$this->attachedNodes = [];
$this->children = [];
$this->parent = null;
unset($this->root);
Expand All @@ -211,4 +262,13 @@ public function isLastChildOfParent(): bool

return $this->parent->findPosition($this->node) === count($this->parent->getChildren()) - 1;
}

private function assertNotAttached(Node $node, string $action): void
{
foreach ($this->attachedNodes as $name => $attachedNode) {
if ($attachedNode->getNode() === $node) {
throw new LogicException(sprintf('Cannot %s the attached node "%s", change its children instead', $action, $name));
}
}
}
}
8 changes: 7 additions & 1 deletion packages/guides/src/Nodes/AdmonitionNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
namespace phpDocumentor\Guides\Nodes;

/** @extends CompoundNode<Node> */
class AdmonitionNode extends CompoundNode
class AdmonitionNode extends CompoundNode implements HasAttachedNodes
{
/** @param Node[] $value */
public function __construct(private readonly string $name, private readonly InlineCompoundNode|null $title, private readonly string $text, array $value, private readonly bool $isTitled = false)
Expand All @@ -41,4 +41,10 @@ public function isTitled(): bool
{
return $this->isTitled;
}

/** @return array<string, InlineCompoundNode> */
public function getAttachedNodes(): array
{
return $this->title === null ? [] : ['title' => $this->title];
}
}
Loading
Loading