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 "node.h"
12#include "qdocdatabase.h"
13#include "qdoclogging.h"
14
15using namespace Qt::Literals::StringLiterals;
16
17QT_BEGIN_NAMESPACE
18
19static Genus genusFromString(const QString &s)
20{
21 if (s == "cpp"_L1)
22 return Genus::CPP;
23 if (s == "qml"_L1)
24 return Genus::QML;
25 if (s == "doc"_L1)
26 return Genus::DOC;
27 if (s == "api"_L1)
28 return Genus::API;
29 return Genus::DontCare;
30}
31
32static bool isBrokenAutolink(const IR::InlineContent &inline_)
33{
34 return inline_.type == IR::InlineType::Link
35 && inline_.link.has_value()
36 && inline_.link->state == IR::LinkState::Broken
37 && inline_.link->origin == IR::LinkOrigin::Auto;
38}
39
40/*!
41 Constructs a LinkResolver that uses \a qdb for node lookup,
42 \a hrefResolver for URL computation, and \a config for warning
43 policy.
44*/
45LinkResolver::LinkResolver(QDocDatabase *qdb, const HrefResolver &hrefResolver,
46 const LinkResolverConfig &config)
47 : m_qdb(qdb), m_hrefResolver(hrefResolver), m_config(config)
48{
49}
50
51/*!
52 Walks \a blocks and resolves all unresolved Link inlines in place.
53 The \a relative node provides context for relative URL computation
54 and warning emission.
55
56 This must be called after ContentBuilder produces the block tree
57 and before rendering.
58*/
59void LinkResolver::resolve(QList<IR::ContentBlock> &blocks, const Node *relative)
60{
61 for (auto &block : blocks)
62 resolveBlock(block, relative);
63}
64
65/*!
66 Resolves links within a single \a block by processing its inline
67 content and recursing into child blocks.
68*/
69void LinkResolver::resolveBlock(IR::ContentBlock &block, const Node *relative)
70{
71 resolveInlines(block.inlineContent, relative);
72 for (auto &child : block.children)
73 resolveBlock(child, relative);
74}
75
76/*!
77 Iterates \a inlines and resolves each Link element. Also recurses
78 into children of all inline elements since Link inlines can contain
79 nested formatting (such as Bold or Italic).
80*/
81void LinkResolver::resolveInlines(QList<IR::InlineContent> &inlines, const Node *relative)
82{
83 for (auto &inline_ : inlines) {
84 if (inline_.type == IR::InlineType::Link && !inline_.href.isEmpty())
85 resolveLink(inline_, relative);
86
87 if (isBrokenAutolink(inline_)) {
88 qCDebug(lcQdoc) << "Autolink degraded to text:" << inline_.href;
89 inline_.type = IR::InlineType::Text;
90 inline_.text = inline_.plainText();
91 inline_.href.clear();
92 inline_.children.clear();
93 inline_.link.reset();
94 inline_.attributes = QJsonObject();
95 continue;
96 }
97
98 if (!inline_.children.isEmpty())
99 resolveInlines(inline_.children, relative);
100 }
101}
102
103/*!
104 Core resolution logic for a single Link \a link. Mirrors the behavior
105 of XmlGenerator::getLink() and XmlGenerator::getAutoLink():
106
107 \list
108 \li External URLs (http, https, ftp, file, mailto) are marked without
109 node lookup.
110 \li Node lookup uses genus and module metadata from ContentBuilder
111 when available. Explicit links carry genus scoping (CPP, QML) and
112 module names for tree-scoped search. Autolinks fall back to
113 forest-wide search with DontCare genus.
114 \li Deprecated node links are suppressed when the relative node isn't
115 deprecated.
116 \li HrefResolver returns an HrefResult variant: a URL on success, or
117 an HrefSuppressReason (self-link, policy exclusion, etc.) that
118 maps to LinkState::Suppressed.
119 \li Unresolvable links emit warnings controlled by LinkResolverConfig.
120 \endlist
121
122 The \a relative node provides context for URL computation and
123 warning source location.
124*/
125void LinkResolver::resolveLink(IR::InlineContent &link, const Node *relative)
126{
127 Q_ASSERT(link.link.has_value());
128
129 const QString &target = link.href;
130
131 // External URL -- no resolution needed.
132 if (target.startsWith("http:"_L1) || target.startsWith("https:"_L1)
133 || target.startsWith("ftp:"_L1) || target.startsWith("file:"_L1)
134 || target.startsWith("mailto:"_L1)) {
135 link.link->state = IR::LinkState::External;
136 return;
137 }
138
139 // Use genus and module metadata from ContentBuilder when available.
140 // Explicit links (LinkAtom) carry genus and module; autolinks don't.
141 const Genus genus = genusFromString(
142 link.attributes.value("linkGenus"_L1).toString());
143 const QString &moduleName =
144 link.attributes.value("linkModule"_L1).toString();
145 const Node *targetNode =
146 m_qdb->findNodeForTarget(target, relative, genus, moduleName);
147
148 if (!targetNode) {
149 if (link.link->origin == IR::LinkOrigin::Auto) {
150 if (m_config.autolinkErrors && relative)
151 relative->doc().location().warning(
152 u"Can't autolink to '%1'"_s.arg(target));
153 } else {
154 if (!m_config.noLinkErrors && relative)
155 relative->doc().location().warning(
156 u"Can't link to '%1'"_s.arg(target));
157 }
158
159 link.link->state = IR::LinkState::Broken;
160 return;
161 }
162
163 // Deprecated node suppression: don't link to deprecated nodes from
164 // non-deprecated content, unless the link originates from within
165 // the deprecated node's own documentation (parent check mirrors
166 // HtmlGenerator::generateBody's inline deprecation logic).
167 if (targetNode->isDeprecated() && relative
168 && relative->parent() != targetNode && !relative->isDeprecated()) {
169 link.href.clear();
170 link.link->state = IR::LinkState::Suppressed;
171 return;
172 }
173
174 // Compute URL via HrefResolver.
175 QString url = targetNode->url();
176 if (url.isNull()) {
177 auto result = m_hrefResolver.hrefForNode(targetNode, relative);
178 if (std::get_if<HrefSuppressReason>(&result)) {
179 link.href.clear();
180 link.link->state = IR::LinkState::Suppressed;
181 return;
182 }
183 url = std::get<QString>(std::move(result));
184 } else if (url.isEmpty()) {
185 link.href.clear();
186 link.link->state = IR::LinkState::Ignored;
187 return;
188 }
189
190 link.href = url;
191 link.link->state = IR::LinkState::Resolved;
192}
193
194QT_END_NAMESPACE
195
196#endif // QDOC_TEMPLATE_GENERATOR_ENABLED