1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "codemarker.h"
5
6#include "classnode.h"
7#include "config.h"
8#include "functionnode.h"
9#include "enumnode.h"
10#include "genustypes.h"
11#include "propertynode.h"
12#include "qmlpropertynode.h"
13#include "utilities.h"
14
15#include <QtCore/qobjectdefs.h>
16
17QT_BEGIN_NAMESPACE
18
19using namespace Qt::StringLiterals;
20
21QString CodeMarker::s_defaultLang;
22QList<CodeMarker *> CodeMarker::s_markers;
23
24
25/*!
26 When a code marker constructs itself, it puts itself into
27 the static list of code markers. All the code markers in
28 the static list get initialized in initialize(), which is
29 not called until after the qdoc configuration file has
30 been read.
31 */
32CodeMarker::CodeMarker()
33{
34 s_markers.prepend(t: this);
35}
36
37/*!
38 When a code marker destroys itself, it removes itself from
39 the static list of code markers.
40 */
41CodeMarker::~CodeMarker()
42{
43 s_markers.removeAll(t: this);
44}
45
46/*!
47 A code market performs no initialization by default. Marker-specific
48 initialization is performed in subclasses.
49 */
50void CodeMarker::initializeMarker() {}
51
52/*!
53 Terminating a code marker is trivial.
54 */
55void CodeMarker::terminateMarker()
56{
57 // nothing.
58}
59
60/*!
61 All the code markers in the static list are initialized
62 here, after the qdoc configuration file has been loaded.
63 */
64void CodeMarker::initialize()
65{
66 s_defaultLang = Config::instance().get(CONFIG_LANGUAGE).asString();
67 for (const auto &marker : std::as_const(t&: s_markers))
68 marker->initializeMarker();
69}
70
71/*!
72 All the code markers in the static list are terminated here.
73 */
74void CodeMarker::terminate()
75{
76 for (const auto &marker : std::as_const(t&: s_markers))
77 marker->terminateMarker();
78}
79
80CodeMarker *CodeMarker::markerForCode(const QString &code)
81{
82 CodeMarker *defaultMarker = markerForLanguage(lang: s_defaultLang);
83 if (defaultMarker != nullptr && defaultMarker->recognizeCode(code))
84 return defaultMarker;
85
86 for (const auto &marker : std::as_const(t&: s_markers)) {
87 if (marker->recognizeCode(code))
88 return marker;
89 }
90
91 return defaultMarker;
92}
93
94CodeMarker *CodeMarker::markerForFileName(const QString &fileName)
95{
96 CodeMarker *defaultMarker = markerForLanguage(lang: s_defaultLang);
97 qsizetype dot = -1;
98 while ((dot = fileName.lastIndexOf(ch: QLatin1Char('.'), from: dot)) != -1) {
99 QString ext = fileName.mid(position: dot + 1);
100 if (defaultMarker != nullptr && defaultMarker->recognizeExtension(ext))
101 return defaultMarker;
102 for (const auto &marker : std::as_const(t&: s_markers)) {
103 if (marker->recognizeExtension(ext))
104 return marker;
105 }
106 --dot;
107 }
108 return defaultMarker;
109}
110
111CodeMarker *CodeMarker::markerForLanguage(const QString &lang)
112{
113 for (const auto &marker : std::as_const(t&: s_markers)) {
114 if (marker->recognizeLanguage(lang))
115 return marker;
116 }
117 return nullptr;
118}
119
120/*!
121 Returns a string representing the \a node status, set using \preliminary, \since,
122 and \deprecated commands.
123
124 If a string is returned, it is one of:
125 \list
126 \li \c {"preliminary"}
127 \li \c {"since <version_since>, deprecated in <version_deprecated>"}
128 \li \c {"since <version_since>, until <version_deprecated>"}
129 \li \c {"since <version_since>"}
130 \li \c {"since <version_since>, deprecated"}
131 \li \c {"deprecated in <version_deprecated>"}
132 \li \c {"until <version_deprecated>"}
133 \li \c {"deprecated"}
134 \endlist
135
136 If \a node has no related status information, returns std::nullopt.
137*/
138static std::optional<QString> nodeStatusAsString(const Node *node)
139{
140 if (node->isPreliminary())
141 return std::optional(u"preliminary"_s);
142
143 QStringList result;
144 if (const auto &since = node->since(); !since.isEmpty())
145 result << "since %1"_L1.arg(args: since);
146 if (const auto &deprecated = node->deprecatedSince(); !deprecated.isEmpty()) {
147 if (node->isDeprecated())
148 result << "deprecated in %1"_L1.arg(args: deprecated);
149 else
150 result << "until %1"_L1.arg(args: deprecated);
151 } else if (node->isDeprecated()) {
152 result << u"deprecated"_s;
153 }
154
155 return result.isEmpty() ? std::nullopt : std::optional(result.join(sep: u", "_s));
156}
157
158/*!
159 Returns the 'extra' synopsis string for \a node with status information,
160 using a specified section \a style.
161*/
162QString CodeMarker::extraSynopsis(const Node *node, Section::Style style)
163{
164 if (style != Section::Summary && style != Section::Details)
165 return {};
166
167 QStringList extra;
168 if (style == Section::Details) {
169 switch (node->nodeType()) {
170 case NodeType::Enum:
171 if (static_cast<const EnumNode *>(node)->isAnonymous())
172 extra << "anonymous";
173 break;
174 case NodeType::Function: {
175 const auto *func = static_cast<const FunctionNode *>(node);
176 if (func->isStatic()) {
177 extra << "static";
178 } else if (!func->isNonvirtual()) {
179 if (func->isFinal())
180 extra << "final";
181 if (func->isOverride())
182 extra << "override";
183 if (func->isPureVirtual())
184 extra << "pure";
185 extra << "virtual";
186 }
187
188 if (func->isExplicit()) extra << "explicit";
189 if (func->isConstexpr()) extra << "constexpr";
190 if (auto noexcept_info = func->getNoexcept()) {
191 extra << (QString("noexcept") + (!(*noexcept_info).isEmpty() ? "(...)" : ""));
192 }
193
194 if (func->access() == Access::Protected)
195 extra << "protected";
196 else if (func->access() == Access::Private)
197 extra << "private";
198
199 if (func->isSignal()) {
200 if (func->parameters().isPrivateSignal())
201 extra << "private";
202 extra << "signal";
203 } else if (func->isSlot())
204 extra << "slot";
205 else if (func->isDefault())
206 extra << "default";
207 else if (func->isInvokable())
208 extra << "invokable";
209 }
210 break;
211 case NodeType::TypeAlias:
212 extra << "alias";
213 break;
214 case NodeType::Property: {
215 auto propertyNode = static_cast<const PropertyNode *>(node);
216 if (propertyNode->propertyType() == PropertyNode::PropertyType::BindableProperty)
217 extra << "bindable";
218 if (!propertyNode->isWritable())
219 extra << "read-only";
220 }
221 break;
222 case NodeType::QmlProperty: {
223 auto qmlProperty = static_cast<const QmlPropertyNode *>(node);
224 if (qmlProperty->isDefault())
225 extra << u"default"_s;
226 // Call non-const overloads to ensure attributes are fetched from
227 // associated C++ properties
228 else if (const_cast<QmlPropertyNode *>(qmlProperty)->isReadOnly())
229 extra << u"read-only"_s;
230 else if (const_cast<QmlPropertyNode *>(qmlProperty)->isRequired())
231 extra << u"required"_s;
232 else if (!qmlProperty->defaultValue().isEmpty()) {
233 extra << u"default: "_s + qmlProperty->defaultValue();
234 }
235 break;
236 }
237 default:
238 break;
239 }
240 }
241
242 // Add status for both Summary and Details
243 if (auto status = nodeStatusAsString(node)) {
244 if (!extra.isEmpty())
245 extra.last() += ','_L1;
246 extra << *status;
247 }
248
249 QString extraStr = extra.join(sep: QLatin1Char(' '));
250 if (!extraStr.isEmpty()) {
251 extraStr.prepend(c: style == Section::Details ? '[' : '(');
252 extraStr.append(c: style == Section::Details ? ']' : ')');
253 }
254
255 return extraStr;
256}
257
258QString CodeMarker::protect(const QString &str)
259{
260 return Utilities::protect(string: str);
261}
262
263void CodeMarker::appendProtectedString(QString *output, QStringView str)
264{
265 qsizetype n = str.size();
266 output->reserve(asize: output->size() + n * 2 + 30);
267 const QChar *data = str.constData();
268 for (int i = 0; i != n; ++i) {
269 switch (data[i].unicode()) {
270 case '&':
271 *output += Utilities::samp;
272 break;
273 case '<':
274 *output += Utilities::slt;
275 break;
276 case '>':
277 *output += Utilities::sgt;
278 break;
279 case '"':
280 *output += Utilities::squot;
281 break;
282 default:
283 *output += data[i];
284 }
285 }
286}
287
288QString CodeMarker::typified(const QString &string, bool trailingSpace)
289{
290 QString result;
291 QString pendingWord;
292
293 for (int i = 0; i <= string.size(); ++i) {
294 QChar ch;
295 if (i != string.size())
296 ch = string.at(i);
297
298 QChar lower = ch.toLower();
299 if ((lower >= QLatin1Char('a') && lower <= QLatin1Char('z')) || ch.digitValue() >= 0
300 || ch == QLatin1Char('_') || ch == QLatin1Char(':')) {
301 pendingWord += ch;
302 } else {
303 if (!pendingWord.isEmpty()) {
304 bool isProbablyType = (pendingWord != QLatin1String("const"));
305 if (isProbablyType)
306 result += QLatin1String("<@type>");
307 result += pendingWord;
308 if (isProbablyType)
309 result += QLatin1String("</@type>");
310 }
311 pendingWord.clear();
312
313 switch (ch.unicode()) {
314 case '\0':
315 break;
316 case '&':
317 result += QLatin1String("&amp;");
318 break;
319 case '<':
320 result += QLatin1String("&lt;");
321 break;
322 case '>':
323 result += QLatin1String("&gt;");
324 break;
325 default:
326 result += ch;
327 }
328 }
329 }
330 if (trailingSpace && string.size()) {
331 if (!string.endsWith(c: QLatin1Char('*')) && !string.endsWith(c: QLatin1Char('&')))
332 result += QLatin1Char(' ');
333 }
334 return result;
335}
336
337QString CodeMarker::taggedNode(const Node *node)
338{
339 QString tag;
340 const QString &name = node->name();
341
342 switch (node->nodeType()) {
343 case NodeType::Namespace:
344 tag = QLatin1String("@namespace");
345 break;
346 case NodeType::Class:
347 case NodeType::Struct:
348 case NodeType::Union:
349 tag = QLatin1String("@class");
350 break;
351 case NodeType::Enum:
352 tag = QLatin1String("@enum");
353 break;
354 case NodeType::TypeAlias:
355 case NodeType::Typedef:
356 tag = QLatin1String("@typedef");
357 break;
358 case NodeType::Function:
359 tag = QLatin1String("@function");
360 break;
361 case NodeType::Property:
362 tag = QLatin1String("@property");
363 break;
364 case NodeType::QmlType:
365 tag = QLatin1String("@property");
366 break;
367 case NodeType::Page:
368 tag = QLatin1String("@property");
369 break;
370 default:
371 tag = QLatin1String("@unknown");
372 break;
373 }
374 return (QLatin1Char('<') + tag + QLatin1Char('>') + protect(str: name) + QLatin1String("</") + tag
375 + QLatin1Char('>'));
376}
377
378QString CodeMarker::taggedQmlNode(const Node *node)
379{
380 QString tag;
381 if (node->isFunction()) {
382 const auto *fn = static_cast<const FunctionNode *>(node);
383 switch (fn->metaness()) {
384 case FunctionNode::QmlSignal:
385 tag = QLatin1String("@signal");
386 break;
387 case FunctionNode::QmlSignalHandler:
388 tag = QLatin1String("@signalhandler");
389 break;
390 case FunctionNode::QmlMethod:
391 tag = QLatin1String("@method");
392 break;
393 default:
394 tag = QLatin1String("@unknown");
395 break;
396 }
397 } else if (node->isQmlProperty()) {
398 tag = QLatin1String("@property");
399 } else {
400 tag = QLatin1String("@unknown");
401 }
402 return QLatin1Char('<') + tag + QLatin1Char('>') + protect(str: node->name()) + QLatin1String("</")
403 + tag + QLatin1Char('>');
404}
405
406QString CodeMarker::linkTag(const Node *node, const QString &body)
407{
408 return QLatin1String("<@link node=\"") + Utilities::stringForNode(node) + QLatin1String("\">") + body
409 + QLatin1String("</@link>");
410}
411
412QT_END_NAMESPACE
413

source code of qttools/src/qdoc/qdoc/src/qdoc/codemarker.cpp