Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
linkresolver.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#ifdef QDOC_TEMPLATE_GENERATOR_ENABLED
5
6#include "linkresolver.h"
7
8#include "genustypes.h"
9#include "hrefresolver.h"
10#include "ir/contentblock.h"
11#include "location.h"
12#include "node.h"
13#include "qdocdatabase.h"
14#include "qdoclogging.h"
15
16using namespace Qt::Literals::StringLiterals;
17
18QT_BEGIN_NAMESPACE
19
20static Genus genusFromString(const QString &s)
21{
22 if (s == "cpp"_L1)
23 return Genus::CPP;
24 if (s == "qml"_L1)
25 return Genus::QML;
26 if (s == "doc"_L1)
27 return Genus::DOC;
28 if (s == "api"_L1)
29 return Genus::API;
30 return Genus::DontCare;
31}
32
33static bool isBrokenAutolink(const IR::InlineContent &inline_)
34{
35 return inline_.type == IR::InlineType::Link
36 && inline_.link.has_value()
37 && inline_.link->state == IR::LinkState::Broken
38 && inline_.link->origin == IR::LinkOrigin::Auto;
39}
40
41/*!
42 Constructs a LinkResolver that uses \a qdb for node lookup,
43 \a hrefResolver for URL computation, and \a config for warning
44 policy.
45*/
46LinkResolver::LinkResolver(QDocDatabase *qdb, const HrefResolver &hrefResolver,
47 const LinkResolverConfig &config)
48 : m_qdb(qdb), m_hrefResolver(hrefResolver), m_config(config)
49{
50}
51
52/*!
53 Walks \a blocks and resolves all unresolved Link inlines in place.
54 The \a relative node provides context for relative URL computation
55 and warning emission.
56
57 This must be called after ContentBuilder produces the block tree
58 and before rendering.
59*/
60void LinkResolver::resolve(QList<IR::ContentBlock> &blocks, const Node *relative)
61{
62 for (auto &block : blocks)
63 resolveBlock(block, relative);
64}
65
66/*!
67 Resolves links within a single \a block by processing its inline
68 content and recursing into child blocks.
69*/
70void LinkResolver::resolveBlock(IR::ContentBlock &block, const Node *relative)
71{
72 resolveInlines(block.inlineContent, relative);
73 for (auto &child : block.children)
74 resolveBlock(child, relative);
75}
76
77/*!
78 Iterates \a inlines and resolves each Link element. Also recurses
79 into children of all inline elements since Link inlines can contain
80 nested formatting (such as Bold or Italic).
81*/
82void LinkResolver::resolveInlines(QList<IR::InlineContent> &inlines, const Node *relative)
83{
84 for (auto &inline_ : inlines) {
85 if (inline_.type == IR::InlineType::Link && !inline_.href.isEmpty())
86 resolveLink(inline_, relative);
87
88 if (isBrokenAutolink(inline_)) {
89 qCDebug(lcQdoc) << "Autolink degraded to text:" << inline_.href;
90 inline_.type = IR::InlineType::Text;
91 inline_.text = inline_.plainText();
92 inline_.href.clear();
93 inline_.children.clear();
94 inline_.link.reset();
95 inline_.attributes = QJsonObject();
96 continue;
97 }
98
99 if (!inline_.children.isEmpty())
100 resolveInlines(inline_.children, relative);
101 }
102}
103
104/*!
105 Core resolution logic for a single Link \a link. Mirrors the behavior
106 of XmlGenerator::getLink() and XmlGenerator::getAutoLink():
107
108 \list
109 \li External URLs (http, https, ftp, file, mailto) are marked without
110 node lookup.
111 \li Node lookup uses genus and module metadata from ContentBuilder
112 when available. Explicit links carry genus scoping (CPP, QML) and
113 module names for tree-scoped search. Autolinks fall back to
114 forest-wide search with DontCare genus.
115 \li Deprecated node links are suppressed when the relative node isn't
116 deprecated.
117 \li HrefResolver returns an HrefResult variant: a URL on success, or
118 an HrefSuppressReason (self-link, policy exclusion, etc.) that
119 maps to LinkState::Suppressed.
120 \li Unresolvable links emit warnings controlled by LinkResolverConfig.
121 \endlist
122
123 The \a relative node provides context for URL computation and
124 warning source location.
125*/
126void LinkResolver::resolveLink(IR::InlineContent &link, const Node *relative)
127{
128 Q_ASSERT(link.link.has_value());
129
130 if (link.link->state != IR::LinkState::Unresolved)
131 return;
132
133 const QString &target = link.href;
134
135 // External URL -- no resolution needed.
136 if (target.startsWith("http:"_L1) || target.startsWith("https:"_L1)
137 || target.startsWith("ftp:"_L1) || target.startsWith("file:"_L1)
138 || target.startsWith("mailto:"_L1)) {
139 link.link->state = IR::LinkState::External;
140 return;
141 }
142
143 // Intra-page anchor link -- references a section heading or member
144 // anchor within the same page. These don't go through node lookup.
145 if (target.startsWith('#'_L1)) {
146 link.link->state = IR::LinkState::Resolved;
147 return;
148 }
149
150 // Use genus and module metadata from ContentBuilder when available.
151 // Explicit links (LinkAtom) carry genus and module; autolinks don't.
152 const Genus genus = genusFromString(
153 link.attributes.value("linkGenus"_L1).toString());
154 const QString &moduleName =
155 link.attributes.value("linkModule"_L1).toString();
156 QString ref;
157 const Node *targetNode =
158 m_qdb->findNodeForTarget(target, relative, genus, moduleName, &ref);
159
160 if (!targetNode) {
161 const auto reportAt = [&](const QString &message) {
162 if (link.link->sourceLocation) {
163 Location loc(link.link->sourceLocation->filePath);
164 loc.setLineNo(link.link->sourceLocation->lineNo);
165 loc.warning(message);
166 } else if (relative) {
167 relative->doc().location().warning(message);
168 }
169 };
170 if (link.link->origin == IR::LinkOrigin::Auto) {
171 if (m_config.autolinkErrors)
172 reportAt(u"Can't autolink to '%1'"_s.arg(target));
173 } else {
174 if (!m_config.noLinkErrors)
175 reportAt(u"Can't link to '%1'"_s.arg(target));
176 }
177
178 link.link->state = IR::LinkState::Broken;
179 link.href.clear();
180 return;
181 }
182
183 // Deprecated node suppression: don't link to deprecated nodes from
184 // non-deprecated content, unless the link originates from within
185 // the deprecated node's own documentation (parent check mirrors
186 // HtmlGenerator::generateBody's inline deprecation logic).
187 if (targetNode->isDeprecated() && relative
188 && relative->parent() != targetNode && !relative->isDeprecated()) {
189 link.href.clear();
190 link.link->state = IR::LinkState::Suppressed;
191 return;
192 }
193
194 // Compute URL via HrefResolver. Cross-tree references reach LinkResolver
195 // carrying a URL baked at .index-load time using Generator::s_outSubdir,
196 // which is only updated by Generator-derived pipelines (HTML, DocBook).
197 // TemplateGenerator doesn't touch that static, so its baked URLs have the
198 // wrong relative depth for its layout. Route those references through
199 // hrefForNode's strip-and-recompute path so the emitted href matches the
200 // current OutputContext.
201 QString url = targetNode->url();
202 const bool isCrossTreeIndexLoaded = relative && !targetNode->isExternalPage()
203 && (targetNode->isIndexNode() || targetNode->tree() != relative->tree());
204 if (url.isNull() || (isCrossTreeIndexLoaded && !url.isEmpty())) {
205 auto result = m_hrefResolver.hrefForNode(targetNode, relative);
206 if (std::get_if<HrefSuppressReason>(&result)) {
207 link.href.clear();
208 link.link->state = IR::LinkState::Suppressed;
209 return;
210 }
211 url = std::get<QString>(std::move(result));
212 } else if (url.isEmpty()) {
213 link.href.clear();
214 link.link->state = IR::LinkState::Ignored;
215 return;
216 }
217
218 // When the target designates a section title or named anchor within the
219 // page, replace any member anchor the URL may already carry with the one
220 // the lookup computed. Mirrors XmlGenerator::getAutoLink().
221 if (!ref.isEmpty()) {
222 const qsizetype hashIndex = url.lastIndexOf(u'#');
223 if (hashIndex != -1)
224 url.truncate(hashIndex);
225 url += u'#' + ref;
226 }
227
228 link.href = url;
229 link.link->state = IR::LinkState::Resolved;
230}
231
232QT_END_NAMESPACE
233
234#endif // QDOC_TEMPLATE_GENERATOR_ENABLED