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 "cppcodemarker.h"
5
6#include "access.h"
7#include "enumnode.h"
8#include "functionnode.h"
9#include "namespacenode.h"
10#include "propertynode.h"
11#include "qmlpropertynode.h"
12#include "text.h"
13#include "tree.h"
14#include "typedefnode.h"
15#include "variablenode.h"
16
17#include <QtCore/qdebug.h>
18#include <QtCore/qregularexpression.h>
19
20QT_BEGIN_NAMESPACE
21
22/*!
23 Returns \c true.
24 */
25bool CppCodeMarker::recognizeCode(const QString & /* code */)
26{
27 return true;
28}
29
30/*!
31 Returns \c true if \a ext is any of a list of file extensions
32 for the C++ language.
33 */
34bool CppCodeMarker::recognizeExtension(const QString &extension)
35{
36 QByteArray ext = extension.toLatin1();
37 return ext == "c" || ext == "c++" || ext == "qdoc" || ext == "qtt" || ext == "qtx"
38 || ext == "cc" || ext == "cpp" || ext == "cxx" || ext == "ch" || ext == "h"
39 || ext == "h++" || ext == "hh" || ext == "hpp" || ext == "hxx";
40}
41
42/*!
43 Returns \c true if \a lang is either "C" or "Cpp".
44 */
45bool CppCodeMarker::recognizeLanguage(const QString &lang)
46{
47 return lang == QLatin1String("C") || lang == QLatin1String("Cpp");
48}
49
50/*!
51 Returns the type of atom used to represent C++ code in the documentation.
52*/
53Atom::AtomType CppCodeMarker::atomType() const
54{
55 return Atom::Code;
56}
57
58QString CppCodeMarker::markedUpCode(const QString &code, const Node *relative,
59 const Location &location)
60{
61 return addMarkUp(protectedCode: code, relative, location);
62}
63
64QString CppCodeMarker::markedUpSynopsis(const Node *node, const Node * /* relative */,
65 Section::Style style)
66{
67 const int MaxEnumValues = 6;
68 const FunctionNode *func;
69 const VariableNode *variable;
70 const EnumNode *enume;
71 QString synopsis;
72 QString name;
73
74 name = taggedNode(node);
75 if (style != Section::Details)
76 name = linkTag(node, body: name);
77 name = "<@name>" + name + "</@name>";
78
79 if (style == Section::Details) {
80 if (!node->isRelatedNonmember() && !node->isProxyNode() && !node->parent()->name().isEmpty()
81 && !node->parent()->isHeader() && !node->isProperty() && !node->isQmlNode()) {
82 name.prepend(s: taggedNode(node: node->parent()) + "::");
83 }
84 }
85
86 switch (node->nodeType()) {
87 case Node::Namespace:
88 case Node::Class:
89 case Node::Struct:
90 case Node::Union:
91 synopsis = Node::nodeTypeString(t: node->nodeType());
92 synopsis += QLatin1Char(' ') + name;
93 break;
94 case Node::Function:
95 func = (const FunctionNode *)node;
96 if (style == Section::Details) {
97 const QString &templateDecl = node->templateDecl();
98 if (!templateDecl.isEmpty())
99 synopsis = templateDecl + QLatin1Char(' ');
100 }
101 if (style != Section::AllMembers && !func->returnType().isEmpty())
102 synopsis += typified(string: func->returnType(), trailingSpace: true);
103 synopsis += name;
104 if (!func->isMacroWithoutParams()) {
105 synopsis += QLatin1Char('(');
106 if (!func->parameters().isEmpty()) {
107 const Parameters &parameters = func->parameters();
108 for (int i = 0; i < parameters.count(); ++i) {
109 if (i > 0)
110 synopsis += ", ";
111 QString name = parameters.at(i).name();
112 QString type = parameters.at(i).type();
113 QString value = parameters.at(i).defaultValue();
114 bool trailingSpace = style != Section::AllMembers && !name.isEmpty();
115 synopsis += typified(string: type, trailingSpace);
116 if (style != Section::AllMembers && !name.isEmpty())
117 synopsis += "<@param>" + protect(string: name) + "</@param>";
118 if (style != Section::AllMembers && !value.isEmpty())
119 synopsis += " = " + protect(string: value);
120 }
121 }
122 synopsis += QLatin1Char(')');
123 }
124 if (func->isConst())
125 synopsis += " const";
126
127 if (style == Section::Summary || style == Section::Accessors) {
128 if (!func->isNonvirtual())
129 synopsis.prepend(s: "virtual ");
130 if (func->isFinal())
131 synopsis.append(s: " final");
132 if (func->isOverride())
133 synopsis.append(s: " override");
134 if (func->isPureVirtual())
135 synopsis.append(s: " = 0");
136 if (func->isRef())
137 synopsis.append(s: " &");
138 else if (func->isRefRef())
139 synopsis.append(s: " &&");
140 } else if (style == Section::AllMembers) {
141 if (!func->returnType().isEmpty() && func->returnType() != "void")
142 synopsis += " : " + typified(string: func->returnType());
143 } else {
144 if (func->isRef())
145 synopsis.append(s: " &");
146 else if (func->isRefRef())
147 synopsis.append(s: " &&");
148 }
149 break;
150 case Node::Enum:
151 enume = static_cast<const EnumNode *>(node);
152 synopsis = "enum ";
153 if (enume->isScoped())
154 synopsis += "class ";
155 synopsis += name;
156 if (style == Section::Summary) {
157 synopsis += " { ";
158
159 QStringList documentedItems = enume->doc().enumItemNames();
160 if (documentedItems.isEmpty()) {
161 const auto &enumItems = enume->items();
162 for (const auto &item : enumItems)
163 documentedItems << item.name();
164 }
165 const QStringList omitItems = enume->doc().omitEnumItemNames();
166 for (const auto &item : omitItems)
167 documentedItems.removeAll(t: item);
168
169 if (documentedItems.size() > MaxEnumValues) {
170 // Take the last element and keep it safe, then elide the surplus.
171 const QString last = documentedItems.last();
172 documentedItems = documentedItems.mid(pos: 0, len: MaxEnumValues - 1);
173 documentedItems += "&hellip;";
174 documentedItems += last;
175 }
176 synopsis += documentedItems.join(sep: QLatin1String(", "));
177
178 if (!documentedItems.isEmpty())
179 synopsis += QLatin1Char(' ');
180 synopsis += QLatin1Char('}');
181 }
182 break;
183 case Node::TypeAlias:
184 if (style == Section::Details) {
185 const QString &templateDecl = node->templateDecl();
186 if (!templateDecl.isEmpty())
187 synopsis += templateDecl + QLatin1Char(' ');
188 }
189 synopsis += name;
190 break;
191 case Node::Typedef:
192 if (static_cast<const TypedefNode *>(node)->associatedEnum())
193 synopsis = "flags ";
194 synopsis += name;
195 break;
196 case Node::Property: {
197 auto property = static_cast<const PropertyNode *>(node);
198 synopsis = name + " : " + typified(string: property->qualifiedDataType());
199 break;
200 }
201 case Node::QmlProperty: {
202 auto property = static_cast<const QmlPropertyNode *>(node);
203 synopsis = name + " : " + typified(string: property->dataType());
204 break;
205 }
206 case Node::Variable:
207 variable = static_cast<const VariableNode *>(node);
208 if (style == Section::AllMembers) {
209 synopsis = name + " : " + typified(string: variable->dataType());
210 } else {
211 synopsis = typified(string: variable->leftType(), trailingSpace: true) + name + protect(string: variable->rightType());
212 }
213 break;
214 default:
215 synopsis = name;
216 }
217
218 QString extra = CodeMarker::extraSynopsis(node, style);
219 if (!extra.isEmpty()) {
220 extra.prepend(s: "<@extra>");
221 extra.append(s: "</@extra>");
222 }
223
224 return extra + synopsis;
225}
226
227/*!
228 */
229QString CppCodeMarker::markedUpQmlItem(const Node *node, bool summary)
230{
231 QString name = taggedQmlNode(node);
232 if (summary) {
233 name = linkTag(node, body: name);
234 } else if (node->isQmlProperty()) {
235 const auto *pn = static_cast<const QmlPropertyNode *>(node);
236 if (pn->isAttached())
237 name.prepend(s: pn->element() + QLatin1Char('.'));
238 }
239 name = "<@name>" + name + "</@name>";
240 QString synopsis;
241 if (node->isQmlProperty()) {
242 const auto *pn = static_cast<const QmlPropertyNode *>(node);
243 synopsis = name + " : " + typified(string: pn->dataType());
244 } else if (node->isFunction(g: Node::QML)) {
245 const auto *func = static_cast<const FunctionNode *>(node);
246 if (!func->returnType().isEmpty())
247 synopsis = typified(string: func->returnType(), trailingSpace: true) + name;
248 else
249 synopsis = name;
250 synopsis += QLatin1Char('(');
251 if (!func->parameters().isEmpty()) {
252 const Parameters &parameters = func->parameters();
253 for (int i = 0; i < parameters.count(); ++i) {
254 if (i > 0)
255 synopsis += ", ";
256 QString name = parameters.at(i).name();
257 QString type = parameters.at(i).type();
258 QString paramName;
259 if (!name.isEmpty()) {
260 synopsis += typified(string: type, trailingSpace: true);
261 paramName = name;
262 } else {
263 paramName = type;
264 }
265 synopsis += "<@param>" + protect(string: paramName) + "</@param>";
266 }
267 }
268 synopsis += QLatin1Char(')');
269 } else {
270 synopsis = name;
271 }
272
273 QString extra;
274 if (summary) {
275 if (node->isPreliminary())
276 extra += " (preliminary)";
277 else if (node->isDeprecated()) {
278 if (const QString &version = node->deprecatedSince(); !version.isEmpty())
279 extra += " (deprecated since " + version + ")";
280 else
281 extra += " (deprecated)";
282 }
283 }
284
285 if (!extra.isEmpty()) {
286 extra.prepend(s: "<@extra>");
287 extra.append(s: "</@extra>");
288 }
289 return synopsis + extra;
290}
291
292QString CppCodeMarker::markedUpName(const Node *node)
293{
294 QString name = linkTag(node, body: taggedNode(node));
295 if (node->isFunction() && !node->isMacro())
296 name += "()";
297 return name;
298}
299
300QString CppCodeMarker::markedUpEnumValue(const QString &enumValue, const Node *relative)
301{
302 if (!relative->isEnumType())
303 return enumValue;
304
305 const Node *node = relative->parent();
306 QStringList parts;
307 while (!node->isHeader() && node->parent()) {
308 parts.prepend(t: markedUpName(node));
309 if (node->parent() == relative || node->parent()->name().isEmpty())
310 break;
311 node = node->parent();
312 }
313 if (static_cast<const EnumNode *>(relative)->isScoped())
314 parts.append(t: relative->name());
315
316 parts.append(t: enumValue);
317 return parts.join(sep: QLatin1String("<@op>::</@op>"));
318}
319
320QString CppCodeMarker::markedUpInclude(const QString &include)
321{
322 return "<@preprocessor>#include &lt;<@headerfile>" + include + "</@headerfile>&gt;</@preprocessor>";
323}
324
325/*
326 @char
327 @class
328 @comment
329 @function
330 @keyword
331 @number
332 @op
333 @preprocessor
334 @string
335 @type
336*/
337
338QString CppCodeMarker::addMarkUp(const QString &in, const Node * /* relative */,
339 const Location & /* location */)
340{
341 static QSet<QString> types{
342 QLatin1String("bool"), QLatin1String("char"), QLatin1String("double"),
343 QLatin1String("float"), QLatin1String("int"), QLatin1String("long"),
344 QLatin1String("short"), QLatin1String("signed"), QLatin1String("unsigned"),
345 QLatin1String("uint"), QLatin1String("ulong"), QLatin1String("ushort"),
346 QLatin1String("uchar"), QLatin1String("void"), QLatin1String("qlonglong"),
347 QLatin1String("qulonglong"), QLatin1String("qint"), QLatin1String("qint8"),
348 QLatin1String("qint16"), QLatin1String("qint32"), QLatin1String("qint64"),
349 QLatin1String("quint"), QLatin1String("quint8"), QLatin1String("quint16"),
350 QLatin1String("quint32"), QLatin1String("quint64"), QLatin1String("qreal"),
351 QLatin1String("cond")
352 };
353
354 static QSet<QString> keywords{
355 QLatin1String("and"), QLatin1String("and_eq"), QLatin1String("asm"), QLatin1String("auto"),
356 QLatin1String("bitand"), QLatin1String("bitor"), QLatin1String("break"),
357 QLatin1String("case"), QLatin1String("catch"), QLatin1String("class"),
358 QLatin1String("compl"), QLatin1String("const"), QLatin1String("const_cast"),
359 QLatin1String("continue"), QLatin1String("default"), QLatin1String("delete"),
360 QLatin1String("do"), QLatin1String("dynamic_cast"), QLatin1String("else"),
361 QLatin1String("enum"), QLatin1String("explicit"), QLatin1String("export"),
362 QLatin1String("extern"), QLatin1String("false"), QLatin1String("for"),
363 QLatin1String("friend"), QLatin1String("goto"), QLatin1String("if"),
364 QLatin1String("include"), QLatin1String("inline"), QLatin1String("monitor"),
365 QLatin1String("mutable"), QLatin1String("namespace"), QLatin1String("new"),
366 QLatin1String("not"), QLatin1String("not_eq"), QLatin1String("operator"),
367 QLatin1String("or"), QLatin1String("or_eq"), QLatin1String("private"),
368 QLatin1String("protected"), QLatin1String("public"), QLatin1String("register"),
369 QLatin1String("reinterpret_cast"), QLatin1String("return"), QLatin1String("sizeof"),
370 QLatin1String("static"), QLatin1String("static_cast"), QLatin1String("struct"),
371 QLatin1String("switch"), QLatin1String("template"), QLatin1String("this"),
372 QLatin1String("throw"), QLatin1String("true"), QLatin1String("try"),
373 QLatin1String("typedef"), QLatin1String("typeid"), QLatin1String("typename"),
374 QLatin1String("union"), QLatin1String("using"), QLatin1String("virtual"),
375 QLatin1String("volatile"), QLatin1String("wchar_t"), QLatin1String("while"),
376 QLatin1String("xor"), QLatin1String("xor_eq"), QLatin1String("synchronized"),
377 // Qt specific
378 QLatin1String("signals"), QLatin1String("slots"), QLatin1String("emit")
379 };
380
381 QString code = in;
382 QString out;
383 QStringView text;
384 int braceDepth = 0;
385 int parenDepth = 0;
386 int i = 0;
387 int start = 0;
388 int finish = 0;
389 QChar ch;
390 static const QRegularExpression classRegExp(QRegularExpression::anchoredPattern(expression: "Qt?(?:[A-Z3]+[a-z][A-Za-z]*|t)"));
391 static const QRegularExpression functionRegExp(QRegularExpression::anchoredPattern(expression: "q([A-Z][a-z]+)+"));
392 static const QRegularExpression findFunctionRegExp(QStringLiteral("^\\s*\\("));
393 bool atEOF = false;
394
395 auto readChar = [&]() {
396 if (i < code.size())
397 ch = code[i++];
398 else
399 atEOF = true;
400 };
401
402 readChar();
403 while (!atEOF) {
404 QString tag;
405 bool target = false;
406
407 if (ch.isLetter() || ch == '_') {
408 QString ident;
409 do {
410 ident += ch;
411 finish = i;
412 readChar();
413 } while (!atEOF && (ch.isLetterOrNumber() || ch == '_'));
414
415 if (classRegExp.match(subject: ident).hasMatch()) {
416 tag = QStringLiteral("type");
417 } else if (functionRegExp.match(subject: ident).hasMatch()) {
418 tag = QStringLiteral("func");
419 target = true;
420 } else if (types.contains(value: ident)) {
421 tag = QStringLiteral("type");
422 } else if (keywords.contains(value: ident)) {
423 tag = QStringLiteral("keyword");
424 } else if (braceDepth == 0 && parenDepth == 0) {
425 if (code.indexOf(re: findFunctionRegExp, from: i - 1) == i - 1)
426 tag = QStringLiteral("func");
427 target = true;
428 }
429 } else if (ch.isDigit()) {
430 do {
431 finish = i;
432 readChar();
433 } while (!atEOF && (ch.isLetterOrNumber() || ch == '.' || ch == '\''));
434 tag = QStringLiteral("number");
435 } else {
436 switch (ch.unicode()) {
437 case '+':
438 case '-':
439 case '!':
440 case '%':
441 case '^':
442 case '&':
443 case '*':
444 case ',':
445 case '.':
446 case '<':
447 case '=':
448 case '>':
449 case '?':
450 case '[':
451 case ']':
452 case '|':
453 case '~':
454 finish = i;
455 readChar();
456 tag = QStringLiteral("op");
457 break;
458 case '"':
459 finish = i;
460 readChar();
461
462 while (!atEOF && ch != '"') {
463 if (ch == '\\')
464 readChar();
465 readChar();
466 }
467 finish = i;
468 readChar();
469 tag = QStringLiteral("string");
470 break;
471 case '#':
472 finish = i;
473 readChar();
474 while (!atEOF && ch != '\n') {
475 if (ch == '\\')
476 readChar();
477 finish = i;
478 readChar();
479 }
480 tag = QStringLiteral("preprocessor");
481 break;
482 case '\'':
483 finish = i;
484 readChar();
485
486 while (!atEOF && ch != '\'') {
487 if (ch == '\\')
488 readChar();
489 readChar();
490 }
491 finish = i;
492 readChar();
493 tag = QStringLiteral("char");
494 break;
495 case '(':
496 finish = i;
497 readChar();
498 ++parenDepth;
499 break;
500 case ')':
501 finish = i;
502 readChar();
503 --parenDepth;
504 break;
505 case ':':
506 finish = i;
507 readChar();
508 if (!atEOF && ch == ':') {
509 finish = i;
510 readChar();
511 tag = QStringLiteral("op");
512 }
513 break;
514 case '/':
515 finish = i;
516 readChar();
517 if (!atEOF && ch == '/') {
518 do {
519 finish = i;
520 readChar();
521 } while (!atEOF && ch != '\n');
522 tag = QStringLiteral("comment");
523 } else if (ch == '*') {
524 bool metAster = false;
525 bool metAsterSlash = false;
526
527 finish = i;
528 readChar();
529
530 while (!metAsterSlash) {
531 if (atEOF)
532 break;
533 if (ch == '*')
534 metAster = true;
535 else if (metAster && ch == '/')
536 metAsterSlash = true;
537 else
538 metAster = false;
539 finish = i;
540 readChar();
541 }
542 tag = QStringLiteral("comment");
543 } else {
544 tag = QStringLiteral("op");
545 }
546 break;
547 case '{':
548 finish = i;
549 readChar();
550 braceDepth++;
551 break;
552 case '}':
553 finish = i;
554 readChar();
555 braceDepth--;
556 break;
557 default:
558 finish = i;
559 readChar();
560 }
561 }
562
563 text = QStringView{code}.mid(pos: start, n: finish - start);
564 start = finish;
565
566 if (!tag.isEmpty()) {
567 out += QStringLiteral("<@");
568 out += tag;
569 if (target) {
570 out += QStringLiteral(" target=\"");
571 out += text;
572 out += QStringLiteral("()\"");
573 }
574 out += QStringLiteral(">");
575 }
576
577 appendProtectedString(output: &out, str: text);
578
579 if (!tag.isEmpty()) {
580 out += QStringLiteral("</@");
581 out += tag;
582 out += QStringLiteral(">");
583 }
584 }
585
586 if (start < code.size()) {
587 appendProtectedString(output: &out, str: QStringView{code}.mid(pos: start));
588 }
589
590 return out;
591}
592
593QT_END_NAMESPACE
594

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