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