Skip to content

Commit fd037ad

Browse files
lina.wolfclaude
authored andcommitted
[FEATURE] Give DirectiveNode logger info, migrate logging directives
DirectiveNode now captures source location (file, line) once its content is fully parsed -- available to createNode() for directives that log a warning about their own content. Captured post-parse, not at construction: the old dispatch always fully exhausts the content iterator before a directive's own code runs, even for empty content, so capturing earlier gave a different (wrong) line number -- caught against directive-with-warning's exact log-line assertion. Migrates TestLoggerDirective, SectionauthorDirective, ConfigurationBlockDirective, and TabsDirective (paired with TabDirective in the same commit, since migrating TabDirective alone previously broke tabs_html -- TabsDirective inspects its children's concrete type, which only resolves correctly once both are on the compile-time model together). createNode() is the only method kept, per jaapio's review on #1379. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PP4LkejR5PSubbhNmF4RkT Signed-off-by: lina.wolf
1 parent 083ef8e commit fd037ad

7 files changed

Lines changed: 63 additions & 96 deletions

File tree

packages/guides-restructured-text/src/RestructuredText/Directives/ConfigurationBlockDirective.php

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@
1818
use phpDocumentor\Guides\Nodes\Configuration\ConfigurationBlockNode;
1919
use phpDocumentor\Guides\Nodes\Configuration\ConfigurationTab;
2020
use phpDocumentor\Guides\Nodes\Node;
21-
use phpDocumentor\Guides\RestructuredText\Parser\BlockContext;
22-
use phpDocumentor\Guides\RestructuredText\Parser\Directive;
21+
use phpDocumentor\Guides\RestructuredText\Nodes\DirectiveNode;
2322
use phpDocumentor\Guides\RestructuredText\Parser\Productions\Rule;
2423
use Psr\Log\LoggerInterface;
2524
use Symfony\Component\String\Slugger\AsciiSlugger;
@@ -29,6 +28,7 @@
2928
use function get_debug_type;
3029
use function sprintf;
3130

31+
#[Attributes\Directive(name: 'configuration-block')]
3232
final class ConfigurationBlockDirective extends SubDirective
3333
{
3434
private SluggerInterface $slugger;
@@ -47,22 +47,14 @@ public function __construct(
4747
$this->slugger = new AsciiSlugger();
4848
}
4949

50-
public function getName(): string
50+
public function createNode(DirectiveNode $directiveNode): Node
5151
{
52-
return 'configuration-block';
53-
}
54-
55-
protected function processSub(
56-
BlockContext $blockContext,
57-
CollectionNode $collectionNode,
58-
Directive $directive,
59-
): Node {
6052
$tabs = [];
61-
foreach ($collectionNode->getValue() as $child) {
53+
foreach ($directiveNode->getChildren() as $child) {
6254
if (!$child instanceof CodeNode) {
6355
$this->logger->warning(
6456
sprintf('The ".. configuration-block::" directive only supports code blocks, "%s" given.', get_debug_type($child)),
65-
$blockContext->getLoggerInformation(),
57+
$directiveNode->getLoggerInformation(),
6658
);
6759

6860
continue;

packages/guides-restructured-text/src/RestructuredText/Directives/SectionauthorDirective.php

Lines changed: 11 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,17 @@
1515

1616
use phpDocumentor\Guides\Nodes\AuthorNode;
1717
use phpDocumentor\Guides\Nodes\Node;
18-
use phpDocumentor\Guides\RestructuredText\Parser\BlockContext;
19-
use phpDocumentor\Guides\RestructuredText\Parser\Directive;
18+
use phpDocumentor\Guides\RestructuredText\Nodes\DirectiveNode;
2019
use Psr\Log\LoggerInterface;
2120

2221
use function preg_match;
2322

23+
/**
24+
* When the default domain contains a class directive, this directive will be shadowed. Therefore, Sphinx re-exports it as rst-class.
25+
*
26+
* See https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#rstclass
27+
*/
28+
#[Attributes\Directive(name: 'sectionauthor', aliases: ['codeauthor'])]
2429
final class SectionauthorDirective extends BaseDirective
2530
{
2631
/** @see https://regex101.com/r/vGy4Uu/1 */
@@ -31,41 +36,19 @@ public function __construct(
3136
) {
3237
}
3338

34-
public function getName(): string
35-
{
36-
return 'sectionauthor';
37-
}
38-
39-
/**
40-
* When the default domain contains a class directive, this directive will be shadowed. Therefore, Sphinx re-exports it as rst-class.
41-
*
42-
* See https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#rstclass
43-
*
44-
* @return string[]
45-
*/
46-
public function getAliases(): array
39+
public function createNode(DirectiveNode $directiveNode): Node|null
4740
{
48-
return ['codeauthor'];
49-
}
50-
51-
/** {@inheritDoc}
52-
*
53-
* @param Directive $directive
54-
*/
55-
public function process(
56-
BlockContext $blockContext,
57-
Directive $directive,
58-
): Node|null {
41+
$directive = $directiveNode->getDirective();
5942
$input = $directive->getData();
6043
$directiveName = $directive->getName();
6144
if ($input === '') {
62-
$this->logger->warning('`.. ' . $directiveName . ' ::` directive could not be parsed: `' . $input . '`', $blockContext->getLoggerInformation());
45+
$this->logger->warning('`.. ' . $directiveName . ' ::` directive could not be parsed: `' . $input . '`', $directiveNode->getLoggerInformation());
6346

6447
return null;
6548
}
6649

6750
if (!preg_match(self::NAME_EMAIL_REGEX, $input, $matches)) {
68-
$this->logger->warning('Content of `.. ' . $directiveName . ':: name <email>` must specify a name and can also specify an email', $blockContext->getLoggerInformation());
51+
$this->logger->warning('Content of `.. ' . $directiveName . ':: name <email>` must specify a name and can also specify an email', $directiveNode->getLoggerInformation());
6952

7053
return null;
7154
}

packages/guides-restructured-text/src/RestructuredText/Directives/TabDirective.php

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,10 @@
1313

1414
namespace phpDocumentor\Guides\RestructuredText\Directives;
1515

16-
use phpDocumentor\Guides\Nodes\CollectionNode;
1716
use phpDocumentor\Guides\Nodes\InlineCompoundNode;
1817
use phpDocumentor\Guides\Nodes\Node;
18+
use phpDocumentor\Guides\RestructuredText\Nodes\DirectiveNode;
1919
use phpDocumentor\Guides\RestructuredText\Nodes\TabNode;
20-
use phpDocumentor\Guides\RestructuredText\Parser\BlockContext;
21-
use phpDocumentor\Guides\RestructuredText\Parser\Directive;
2220

2321
use function class_alias;
2422
use function class_exists;
@@ -27,22 +25,13 @@
2725
use function str_replace;
2826
use function strtolower;
2927

28+
#[Attributes\Directive(name: 'tab')]
3029
final class TabDirective extends SubDirective
3130
{
32-
public function getName(): string
31+
public function createNode(DirectiveNode $directiveNode): Node
3332
{
34-
return 'tab';
35-
}
33+
$directive = $directiveNode->getDirective();
3634

37-
/** {@inheritDoc}
38-
*
39-
* @param Directive $directive
40-
*/
41-
protected function processSub(
42-
BlockContext $blockContext,
43-
CollectionNode $collectionNode,
44-
Directive $directive,
45-
): Node {
4635
if (is_string($directive->getOption('key')->getValue())) {
4736
$key = strtolower($directive->getOption('key')->getValue());
4837
} else {
@@ -59,7 +48,7 @@ protected function processSub(
5948
$directive->getDataNode() ?? new InlineCompoundNode(),
6049
$key,
6150
$active,
62-
$collectionNode->getChildren(),
51+
$directiveNode->getChildren(),
6352
);
6453
}
6554
}

packages/guides-restructured-text/src/RestructuredText/Directives/TabsDirective.php

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,16 @@
1818
use phpDocumentor\Guides\Nodes\Node;
1919
use phpDocumentor\Guides\ReferenceResolvers\AnchorNormalizer;
2020
use phpDocumentor\Guides\RestructuredText\Nodes\AbstractTabNode;
21+
use phpDocumentor\Guides\RestructuredText\Nodes\DirectiveNode;
2122
use phpDocumentor\Guides\RestructuredText\Nodes\TabsNode;
22-
use phpDocumentor\Guides\RestructuredText\Parser\BlockContext;
23-
use phpDocumentor\Guides\RestructuredText\Parser\Directive;
2423
use phpDocumentor\Guides\RestructuredText\Parser\Productions\Rule;
2524
use Psr\Log\LoggerInterface;
2625

2726
use function class_alias;
2827
use function class_exists;
2928
use function is_string;
3029

30+
#[Attributes\Directive(name: 'tabs')]
3131
final class TabsDirective extends SubDirective
3232
{
3333
private int $tabsCounter = 0;
@@ -41,23 +41,12 @@ public function __construct(
4141
parent::__construct($startingRule);
4242
}
4343

44-
public function getName(): string
44+
public function createNode(DirectiveNode $directiveNode): Node
4545
{
46-
return 'tabs';
47-
}
48-
49-
/** {@inheritDoc}
50-
*
51-
* @param Directive $directive
52-
*/
53-
protected function processSub(
54-
BlockContext $blockContext,
55-
CollectionNode $collectionNode,
56-
Directive $directive,
57-
): Node {
46+
$directive = $directiveNode->getDirective();
5847
$tabs = [];
5948
$hasActive = false;
60-
foreach ($collectionNode->getChildren() as $child) {
49+
foreach ($directiveNode->getChildren() as $child) {
6150
if ($child instanceof AbstractTabNode) {
6251
if ($child->isActive()) {
6352
if (!$hasActive) {
@@ -72,7 +61,7 @@ protected function processSub(
7261
} else {
7362
$this->logger->warning(
7463
'The "tabs" directive may only contain children of type "tab". The following node was found: ' . $child::class,
75-
$blockContext->getLoggerInformation(),
64+
$directiveNode->getLoggerInformation(),
7665
);
7766
}
7867
}

packages/guides-restructured-text/src/RestructuredText/Directives/TestLoggerDirective.php

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,9 @@
1313

1414
namespace phpDocumentor\Guides\RestructuredText\Directives;
1515

16-
use phpDocumentor\Guides\Nodes\CollectionNode;
1716
use phpDocumentor\Guides\Nodes\Node;
1817
use phpDocumentor\Guides\RestructuredText\Nodes\ContainerNode;
19-
use phpDocumentor\Guides\RestructuredText\Parser\BlockContext;
20-
use phpDocumentor\Guides\RestructuredText\Parser\Directive;
18+
use phpDocumentor\Guides\RestructuredText\Nodes\DirectiveNode;
2119
use phpDocumentor\Guides\RestructuredText\Parser\Productions\Rule;
2220
use Psr\Log\LoggerInterface;
2321

@@ -26,6 +24,7 @@
2624
*
2725
* @link https://docutils.sourceforge.io/docs/ref/rst/directives.html#container
2826
*/
27+
#[Attributes\Directive(name: 'testlogger')]
2928
final class TestLoggerDirective extends SubDirective
3029
{
3130
public function __construct(
@@ -35,22 +34,11 @@ public function __construct(
3534
parent::__construct($startingRule);
3635
}
3736

38-
public function getName(): string
37+
public function createNode(DirectiveNode $directiveNode): Node
3938
{
40-
return 'testlogger';
41-
}
42-
43-
/** {@inheritDoc}
44-
*
45-
* @param Directive $directive
46-
*/
47-
protected function processSub(
48-
BlockContext $blockContext,
49-
CollectionNode $collectionNode,
50-
Directive $directive,
51-
): Node {
52-
$this->logger->warning('Test logging in directives', $blockContext->getLoggerInformation());
39+
$this->logger->warning('Test logging in directives', $directiveNode->getLoggerInformation());
5340

54-
return (new ContainerNode($collectionNode->getChildren()))->withOptions(['class' => $directive->getData()]);
41+
return (new ContainerNode($directiveNode->getChildren()))
42+
->withOptions(['class' => $directiveNode->getDirective()->getData()]);
5543
}
5644
}

packages/guides-restructured-text/src/RestructuredText/Nodes/DirectiveNode.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
/** @extends CompoundNode<Node> */
2121
final class DirectiveNode extends CompoundNode
2222
{
23+
/** @var array<string, int|string> */
24+
private array $loggerInformation = [];
25+
2326
/** @param Node[] $children */
2427
public function __construct(private readonly Directive $directive, array $children = [])
2528
{
@@ -30,4 +33,23 @@ public function getDirective(): Directive
3033
{
3134
return $this->directive;
3235
}
36+
37+
/**
38+
* Source location info (file, line), set once the directive's content has
39+
* been fully parsed -- available to createNode() for directives that need
40+
* to log a warning about their own content, since createNode() itself has
41+
* no access to BlockContext.
42+
*
43+
* @param array<string, int|string> $loggerInformation
44+
*/
45+
public function setLoggerInformation(array $loggerInformation): void
46+
{
47+
$this->loggerInformation = $loggerInformation;
48+
}
49+
50+
/** @return array<string, int|string> */
51+
public function getLoggerInformation(): array
52+
{
53+
return $this->loggerInformation;
54+
}
3355
}

packages/guides-restructured-text/src/RestructuredText/Parser/Productions/DirectiveRule.php

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,14 @@ public function apply(BlockContext $blockContext, CompoundNode|null $on = null):
9595
$buffer = $this->collectDirectiveContents($documentIterator);
9696

9797
if ($this->startingRule !== null && $directiveHandler->isUpgraded()) {
98-
$node = $this->startingRule->apply(
99-
new BlockContext($blockContext->getDocumentParserContext(), $buffer->getLinesString(), true, $documentIterator->key()),
100-
new DirectiveNode($directive),
101-
);
98+
$subBlockContext = new BlockContext($blockContext->getDocumentParserContext(), $buffer->getLinesString(), true, $documentIterator->key());
99+
$directiveNode = new DirectiveNode($directive);
100+
$node = $this->startingRule->apply($subBlockContext, $directiveNode);
101+
// Captured only after the sub-parse has fully consumed $subBlockContext's
102+
// content, matching what a directive's own logging call would have seen
103+
// under the old (non-upgraded) dispatch, where content is always parsed
104+
// before the directive's own code runs.
105+
$directiveNode->setLoggerInformation($subBlockContext->getLoggerInformation());
102106

103107
if ($node === null) {
104108
return null;

0 commit comments

Comments
 (0)