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 if (link.link->state != IR::LinkState::Unresolved)
130 return;
131
132 const QString &target = link.href;
133
134 // External URL -- no resolution needed.
135 if (target.startsWith("http:"_L1) || target.startsWith("https:"_L1)
136 || target.startsWith("ftp:"_L1) || target.startsWith("file:"_L1)
137 || target.startsWith("mailto:"_L1)) {
138 link.link->state = IR::LinkState::External;
139 return;
140 }
141
142 // Use genus and module metadata from ContentBuilder when available.
143 // Explicit links (LinkAtom) carry genus and module; autolinks don't.
144 const Genus genus = genusFromString(
145 link.attributes.value("linkGenus"_L1).toString());
146 const QString &moduleName =
147 link.attributes.value("linkModule"_L1).toString();
148 const Node *targetNode =
149 m_qdb->findNodeForTarget(target, relative, genus, moduleName);
150
151 if (!targetNode) {
152 if (link.link->origin == IR::LinkOrigin::Auto) {
153 if (m_config.autolinkErrors && relative)
154 relative->doc().location().warning(
155 u"Can't autolink to '%1'"_s.arg(target));
156 } else {
157 if (!m_config.noLinkErrors && relative)
158 relative->doc().location().warning(
159 u"Can't link to '%1'"_s.arg(target));
160 }
161
162 link.link->state = IR::LinkState::Broken;
163 link.href.clear();
164 return;
165 }
166
167 // Deprecated node suppression: don't link to deprecated nodes from
168 // non-deprecated content, unless the link originates from within
169 // the deprecated node's own documentation (parent check mirrors
170 // HtmlGenerator::generateBody's inline deprecation logic).
171 if (targetNode->isDeprecated() && relative
172 && relative->parent() != targetNode && !relative->isDeprecated()) {
173 link.href.clear();
174 link.link->state = IR::LinkState::Suppressed;
175 return;
176 }
177
178 // Compute URL via HrefResolver.
179 QString url = targetNode->url();
180 if (url.isNull()) {
181 auto result = m_hrefResolver.hrefForNode(targetNode, relative);
182 if (std::get_if<HrefSuppressReason>(&result)) {
183 link.href.clear();
184 link.link->state = IR::LinkState::Suppressed;
185 return;
186 }
187 url = std::get<QString>(std::move(result));
188 } else if (url.isEmpty()) {
189 link.href.clear();
190 link.link->state = IR::LinkState::Ignored;
191 return;
192 }
193
194 link.href = url;
195 link.link->state = IR::LinkState::Resolved;
196}
197
198QT_END_NAMESPACE
199
200#endif // QDOC_TEMPLATE_GENERATOR_ENABLED