Skip to content

Commit 41309de

Browse files
authored
Merge pull request #945 from phpDocumentor/task/warn-duplicate
[TASK] Warn about duplicate anchors in LinkTargetNode
2 parents 3896e4f + 17c3a07 commit 41309de

File tree

16 files changed

+296
-15
lines changed

16 files changed

+296
-15
lines changed

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,12 @@ protected function processSub(
6161
CollectionNode $collectionNode,
6262
Directive $directive,
6363
): Node|null {
64-
$id = $this->anchorReducer->reduceAnchor($directive->getData());
64+
$id = $directive->getData();
65+
if ($directive->hasOption('name')) {
66+
$id = (string) $directive->getOption('name')->getValue();
67+
}
68+
69+
$id = $this->anchorReducer->reduceAnchor($id);
6570
$type = null;
6671
$required = false;
6772
$default = null;
@@ -79,7 +84,7 @@ protected function processSub(
7984
}
8085

8186
foreach ($directive->getOptions() as $option) {
82-
if (in_array($option->getName(), ['type', 'required', 'default', 'noindex'], true)) {
87+
if (in_array($option->getName(), ['type', 'required', 'default', 'noindex', 'name'], true)) {
8388
continue;
8489
}
8590

packages/guides/src/Compiler/NodeTransformers/CollectLinkTargetsTransformer.php

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,12 @@
2323
use phpDocumentor\Guides\Nodes\Node;
2424
use phpDocumentor\Guides\Nodes\SectionNode;
2525
use phpDocumentor\Guides\ReferenceResolvers\AnchorNormalizer;
26+
use Psr\Log\LoggerInterface;
2627
use SplStack;
2728
use Webmozart\Assert\Assert;
2829

30+
use function sprintf;
31+
2932
/** @implements NodeTransformer<DocumentNode|AnchorNode|SectionNode> */
3033
final class CollectLinkTargetsTransformer implements NodeTransformer
3134
{
@@ -34,6 +37,7 @@ final class CollectLinkTargetsTransformer implements NodeTransformer
3437

3538
public function __construct(
3639
private readonly AnchorNormalizer $anchorReducer,
40+
private LoggerInterface|null $logger = null,
3741
) {
3842
/*
3943
* TODO: remove stack here, as we should not have sub documents in this way, sub documents are
@@ -47,7 +51,11 @@ public function enterNode(Node $node, CompilerContext $compilerContext): Node
4751
{
4852
if ($node instanceof DocumentNode) {
4953
$this->documentStack->push($node);
50-
} elseif ($node instanceof AnchorNode) {
54+
55+
return $node;
56+
}
57+
58+
if ($node instanceof AnchorNode) {
5159
$currentDocument = $compilerContext->getDocumentNode();
5260
$parentSection = $compilerContext->getShadowTree()->getParent()?->getNode();
5361
$title = null;
@@ -64,12 +72,33 @@ public function enterNode(Node $node, CompilerContext $compilerContext): Node
6472
$title,
6573
),
6674
);
67-
} elseif ($node instanceof LinkTargetNode) {
75+
76+
return $node;
77+
}
78+
79+
if ($node instanceof SectionNode) {
6880
$currentDocument = $this->documentStack->top();
6981
Assert::notNull($currentDocument);
70-
$anchor = $node->getId();
82+
$anchorName = $node->getId();
7183
$compilerContext->getProjectNode()->addLinkTarget(
72-
$anchor,
84+
$anchorName,
85+
new InternalTarget(
86+
$currentDocument->getFilePath(),
87+
$anchorName,
88+
$node->getLinkText(),
89+
$node->getLinkType(),
90+
),
91+
);
92+
93+
return $node;
94+
}
95+
96+
if ($node instanceof LinkTargetNode) {
97+
$currentDocument = $this->documentStack->top();
98+
Assert::notNull($currentDocument);
99+
$anchor = $this->anchorReducer->reduceAnchor($node->getId());
100+
$this->addLinkTargetToProject(
101+
$compilerContext,
73102
new InternalTarget(
74103
$currentDocument->getFilePath(),
75104
$anchor,
@@ -79,11 +108,12 @@ public function enterNode(Node $node, CompilerContext $compilerContext): Node
79108
);
80109
if ($node instanceof MultipleLinkTargetsNode) {
81110
foreach ($node->getAdditionalIds() as $id) {
82-
$compilerContext->getProjectNode()->addLinkTarget(
83-
$id,
111+
$anchor = $this->anchorReducer->reduceAnchor($id);
112+
$this->addLinkTargetToProject(
113+
$compilerContext,
84114
new InternalTarget(
85115
$currentDocument->getFilePath(),
86-
$id,
116+
$anchor,
87117
$node->getLinkText(),
88118
$node->getLinkType(),
89119
),
@@ -114,4 +144,28 @@ public function getPriority(): int
114144
// After MetasPass
115145
return 5000;
116146
}
147+
148+
private function addLinkTargetToProject(CompilerContext $compilerContext, InternalTarget $internalTarget): void
149+
{
150+
if ($compilerContext->getProjectNode()->hasInternalTarget($internalTarget->getAnchor(), $internalTarget->getLinkType())) {
151+
$otherLink = $compilerContext->getProjectNode()->getInternalTarget($internalTarget->getAnchor(), $internalTarget->getLinkType());
152+
$this->logger?->warning(
153+
sprintf(
154+
'Duplicate anchor "%s" for link type "%s" in document "%s". The anchor is already used at "%s"',
155+
$internalTarget->getAnchor(),
156+
$internalTarget->getLinkType(),
157+
$compilerContext->getDocumentNode()->getFilePath(),
158+
$otherLink?->getDocumentPath(),
159+
),
160+
$compilerContext->getLoggerInformation(),
161+
);
162+
163+
return;
164+
}
165+
166+
$compilerContext->getProjectNode()->addLinkTarget(
167+
$internalTarget->getAnchor(),
168+
$internalTarget,
169+
);
170+
}
117171
}

packages/guides/src/Nodes/ProjectNode.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,11 @@ public function addLinkTarget(string $anchorName, InternalTarget $target): void
152152
$this->internalLinkTargets[$target->getLinkType()][$anchorName] = $target;
153153
}
154154

155+
public function hasInternalTarget(string $anchorName, string $linkType = SectionNode::STD_LABEL): bool
156+
{
157+
return isset($this->internalLinkTargets[$linkType][$anchorName]);
158+
}
159+
155160
public function getInternalTarget(string $anchorName, string $linkType = SectionNode::STD_LABEL): InternalTarget|null
156161
{
157162
return $this->internalLinkTargets[$linkType][$anchorName] ?? null;

tests/Functional/tests/section-nesting/section-nesting.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ <h1>
3737
<h1>
3838
Level 1 Test 4
3939
</h1>
40-
<div class="section" id="level-2-test-3">
40+
<div class="section" id="level-2-test-3b">
4141
<h2>
42-
Level 2 Test 3
42+
Level 2 Test 3b
4343
</h2>
4444
<div class="section" id="level-3-test-1">
4545
<h3>

tests/Functional/tests/section-nesting/section-nesting.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ Level 1 Test 3
2222
Level 1 Test 4
2323
==============
2424

25-
Level 2 Test 3
26-
--------------
25+
Level 2 Test 3b
26+
---------------
2727

2828
Level 3 Test 1
2929
**************

tests/Integration/tests/confval/expected/index.html renamed to tests/Integration/tests/confval/confval-name/expected/another.html

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<title>Confval directive</title>
5+
6+
</head>
7+
<body>
18
<!-- content start -->
29
<div class="section" id="confval-directive">
310
<h1>Confval directive</h1>
411

512
<dl class="confval">
6-
<dt id="demo">
13+
<dt id="another-demo">
714
<code class="sig-name descname"><span class="pre">demo</span></code></dt>
815
<dd>
916
<div class="line-block">
@@ -21,3 +28,5 @@ <h1>Confval directive</h1>
2128
</div>
2229

2330
<!-- content end -->
31+
</body>
32+
</html>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<title>Confval directive</title>
5+
6+
</head>
7+
<body>
8+
<!-- content start -->
9+
<div class="section" id="confval-directive">
10+
<h1>Confval directive</h1>
11+
12+
<dl class="confval">
13+
<dt id="demo">
14+
<code class="sig-name descname"><span class="pre">demo</span></code></dt>
15+
<dd>
16+
<div class="line-block">
17+
<div class="line"><strong>Type:</strong> <code>&quot;Hello World&quot;</code></div>
18+
<div class="line"><strong>Required:</strong> true</div>
19+
<div class="line"><strong>Custom Info:</strong> <strong>custom</strong></div>
20+
21+
</div>
22+
<div class="confval-description">
23+
<p>This is the confval <code>demo</code> content!</p><p>Another paragraph.</p>
24+
</div>
25+
</dd>
26+
</dl>
27+
<p>See option <a href="/index.html#demo">demo</a>.</p>
28+
<div class="toc">
29+
<ul class="menu-level">
30+
<li class="toc-item"><a href="/another.html#confval-directive">Confval directive</a></li>
31+
32+
</ul>
33+
</div>
34+
35+
</div>
36+
37+
<!-- content end -->
38+
</body>
39+
</html>
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"std:doc": {
3+
"another": [
4+
"-",
5+
"-",
6+
"another.html",
7+
"Confval directive"
8+
],
9+
"index": [
10+
"-",
11+
"-",
12+
"index.html",
13+
"Confval directive"
14+
]
15+
},
16+
"std:label": {
17+
"confval-directive": [
18+
"-",
19+
"-",
20+
"index.html#confval-directive",
21+
"Confval directive"
22+
]
23+
},
24+
"std:confval": {
25+
"another-demo": [
26+
"-",
27+
"-",
28+
"another.html#another-demo",
29+
"demo"
30+
],
31+
"demo": [
32+
"-",
33+
"-",
34+
"index.html#demo",
35+
"demo"
36+
]
37+
}
38+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
Confval directive
2+
=================
3+
4+
.. confval:: demo
5+
:name: another-demo
6+
:type: :php:`string`
7+
:default: ``"Hello World"``
8+
:required: true
9+
:Custom Info: **custom**
10+
11+
This is the confval ``demo`` content!
12+
13+
Another paragraph.
14+
15+
See option :confval:`demo`.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Confval directive
2+
=================
3+
4+
.. confval:: demo
5+
:type: :php:`string`
6+
:default: ``"Hello World"``
7+
:required: true
8+
:Custom Info: **custom**
9+
10+
This is the confval ``demo`` content!
11+
12+
Another paragraph.
13+
14+
See option :confval:`demo`.
15+
16+
.. toctree::
17+
:glob:
18+
19+
*
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<title>Confval directive</title>
5+
6+
</head>
7+
<body>
8+
<!-- content start -->
9+
<div class="section" id="confval-directive">
10+
<h1>Confval directive</h1>
11+
12+
<dl class="confval">
13+
<dt id="demo">
14+
<code class="sig-name descname"><span class="pre">demo</span></code></dt>
15+
<dd>
16+
<div class="line-block">
17+
<div class="line"><strong>Type:</strong> <code>&quot;Hello World&quot;</code></div>
18+
<div class="line"><strong>Required:</strong> true</div>
19+
<div class="line"><strong>Custom Info:</strong> <strong>custom</strong></div>
20+
21+
</div>
22+
<div class="confval-description">
23+
<p>This is the confval <code>demo</code> content!</p><p>Another paragraph.</p>
24+
</div>
25+
</dd>
26+
</dl>
27+
<p>See option <a href="/another.html#demo">demo</a>.</p>
28+
</div>
29+
30+
<!-- content end -->
31+
</body>
32+
</html>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<title>Confval directive</title>
5+
6+
</head>
7+
<body>
8+
<!-- content start -->
9+
<div class="section" id="confval-directive">
10+
<h1>Confval directive</h1>
11+
12+
<dl class="confval">
13+
<dt id="demo">
14+
<code class="sig-name descname"><span class="pre">demo</span></code></dt>
15+
<dd>
16+
<div class="line-block">
17+
<div class="line"><strong>Type:</strong> <code>&quot;Hello World&quot;</code></div>
18+
<div class="line"><strong>Required:</strong> true</div>
19+
<div class="line"><strong>Custom Info:</strong> <strong>custom</strong></div>
20+
21+
</div>
22+
<div class="confval-description">
23+
<p>This is the confval <code>demo</code> content!</p><p>Another paragraph.</p>
24+
</div>
25+
</dd>
26+
</dl>
27+
<p>See option <a href="/another.html#demo">demo</a>.</p>
28+
<div class="toc">
29+
<ul class="menu-level">
30+
<li class="toc-item"><a href="/another.html#confval-directive">Confval directive</a></li>
31+
32+
</ul>
33+
</div>
34+
35+
</div>
36+
37+
<!-- content end -->
38+
</body>
39+
</html>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
app.WARNING: Duplicate anchor "demo" for link type "std:confval" in document "index". The anchor is already used at "another"

0 commit comments

Comments
 (0)