1 /****************************************************************************
2 **
3 ** Copyright (C) 2019 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the tools applications of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21 ** included in the packaging of this file. Please review the following
22 ** information to ensure the GNU General Public License requirements will
23 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24 **
25 ** $QT_END_LICENSE$
26 **
27 ****************************************************************************/
28
29 /*
30 cppcodemarker.cpp
31 */
32
33 #include "cppcodemarker.h"
34
35 #include "generator.h"
36 #include "text.h"
37 #include "tree.h"
38
39 #include <QtCore/qdebug.h>
40 #include <QtCore/qregexp.h>
41
42 #include <ctype.h>
43
44 QT_BEGIN_NAMESPACE
45
46 /*!
47 The constructor does nothing.
48 */
CppCodeMarker()49 CppCodeMarker::CppCodeMarker()
50 {
51 // nothing.
52 }
53
54 /*!
55 The destructor does nothing.
56 */
~CppCodeMarker()57 CppCodeMarker::~CppCodeMarker()
58 {
59 // nothing.
60 }
61
62 /*!
63 Returns \c true.
64 */
recognizeCode(const QString &)65 bool CppCodeMarker::recognizeCode(const QString & /* code */)
66 {
67 return true;
68 }
69
70 /*!
71 Returns \c true if \a ext is any of a list of file extensions
72 for the C++ language.
73 */
recognizeExtension(const QString & extension)74 bool CppCodeMarker::recognizeExtension(const QString &extension)
75 {
76 QByteArray ext = extension.toLatin1();
77 return ext == "c" || ext == "c++" || ext == "qdoc" || ext == "qtt" || ext == "qtx"
78 || ext == "cc" || ext == "cpp" || ext == "cxx" || ext == "ch" || ext == "h"
79 || ext == "h++" || ext == "hh" || ext == "hpp" || ext == "hxx";
80 }
81
82 /*!
83 Returns \c true if \a lang is either "C" or "Cpp".
84 */
recognizeLanguage(const QString & lang)85 bool CppCodeMarker::recognizeLanguage(const QString &lang)
86 {
87 return lang == QLatin1String("C") || lang == QLatin1String("Cpp");
88 }
89
90 /*!
91 Returns the type of atom used to represent C++ code in the documentation.
92 */
atomType() const93 Atom::AtomType CppCodeMarker::atomType() const
94 {
95 return Atom::Code;
96 }
97
markedUpCode(const QString & code,const Node * relative,const Location & location)98 QString CppCodeMarker::markedUpCode(const QString &code, const Node *relative,
99 const Location &location)
100 {
101 return addMarkUp(code, relative, location);
102 }
103
markedUpSynopsis(const Node * node,const Node *,Section::Style style)104 QString CppCodeMarker::markedUpSynopsis(const Node *node, const Node * /* relative */,
105 Section::Style style)
106 {
107 const int MaxEnumValues = 6;
108 const FunctionNode *func;
109 const PropertyNode *property;
110 const VariableNode *variable;
111 const EnumNode *enume;
112 const TypedefNode *typedeff;
113 QString synopsis;
114 QString extra;
115 QString name;
116
117 name = taggedNode(node);
118 if (style != Section::Details)
119 name = linkTag(node, name);
120 name = "<@name>" + name + "</@name>";
121
122 if (style == Section::Details) {
123 if (!node->isRelatedNonmember() && !node->isProxyNode() && !node->parent()->name().isEmpty()
124 && !node->parent()->isHeader() && !node->isProperty() && !node->isQmlNode()
125 && !node->isJsNode()) {
126 name.prepend(taggedNode(node->parent()) + "::");
127 }
128 }
129
130 switch (node->nodeType()) {
131 case Node::Namespace:
132 case Node::Class:
133 case Node::Struct:
134 case Node::Union:
135 synopsis = Node::nodeTypeString(node->nodeType());
136 synopsis += QLatin1Char(' ') + name;
137 break;
138 case Node::Function:
139 func = (const FunctionNode *)node;
140 if (style == Section::Details) {
141 QString templateDecl = node->templateDecl();
142 if (!templateDecl.isEmpty())
143 synopsis = templateDecl + QLatin1Char(' ');
144 }
145 if (style != Section::AllMembers && !func->returnType().isEmpty())
146 synopsis += typified(func->returnType(), true);
147 synopsis += name;
148 if (!func->isMacroWithoutParams()) {
149 synopsis += QLatin1Char('(');
150 if (!func->parameters().isEmpty()) {
151 const Parameters ¶meters = func->parameters();
152 for (int i = 0; i < parameters.count(); ++i) {
153 if (i > 0)
154 synopsis += ", ";
155 QString name = parameters.at(i).name();
156 QString type = parameters.at(i).type();
157 QString value = parameters.at(i).defaultValue();
158 QString paramName;
159 if (!name.isEmpty()) {
160 synopsis += typified(type, true);
161 paramName = name;
162 } else {
163 paramName = type;
164 }
165 if (style != Section::AllMembers || name.isEmpty())
166 synopsis += "<@param>" + protect(paramName) + "</@param>";
167 if (style != Section::AllMembers && !value.isEmpty())
168 synopsis += " = " + protect(value);
169 }
170 }
171 synopsis += QLatin1Char(')');
172 }
173 if (func->isConst())
174 synopsis += " const";
175
176 if (style == Section::Summary || style == Section::Accessors) {
177 if (!func->isNonvirtual())
178 synopsis.prepend("virtual ");
179 if (func->isFinal())
180 synopsis.append(" final");
181 if (func->isOverride())
182 synopsis.append(" override");
183 if (func->isPureVirtual())
184 synopsis.append(" = 0");
185 if (func->isRef())
186 synopsis.append(" &");
187 else if (func->isRefRef())
188 synopsis.append(" &&");
189 } else if (style == Section::AllMembers) {
190 if (!func->returnType().isEmpty() && func->returnType() != "void")
191 synopsis += " : " + typified(func->returnType());
192 } else {
193 if (func->isRef())
194 synopsis.append(" &");
195 else if (func->isRefRef())
196 synopsis.append(" &&");
197 QStringList bracketed;
198 if (func->isStatic()) {
199 bracketed += "static";
200 } else if (!func->isNonvirtual()) {
201 if (func->isFinal())
202 bracketed += "final";
203 if (func->isOverride())
204 bracketed += "override";
205 if (func->isPureVirtual())
206 bracketed += "pure";
207 bracketed += "virtual";
208 }
209
210 if (func->access() == Node::Protected)
211 bracketed += "protected";
212 else if (func->access() == Node::Private)
213 bracketed += "private";
214
215 if (func->isSignal())
216 bracketed += "signal";
217 else if (func->isSlot())
218 bracketed += "slot";
219 if (!bracketed.isEmpty())
220 extra += QLatin1Char('[') + bracketed.join(' ') + QStringLiteral("] ");
221 }
222 break;
223 case Node::Enum:
224 enume = static_cast<const EnumNode *>(node);
225 synopsis = "enum ";
226 if (enume->isScoped())
227 synopsis += "class ";
228 synopsis += name;
229 if (style == Section::Summary) {
230 synopsis += " { ";
231
232 QStringList documentedItems = enume->doc().enumItemNames();
233 if (documentedItems.isEmpty()) {
234 const auto &enumItems = enume->items();
235 for (const auto &item : enumItems)
236 documentedItems << item.name();
237 }
238 const QStringList omitItems = enume->doc().omitEnumItemNames();
239 for (const auto &item : omitItems)
240 documentedItems.removeAll(item);
241
242 if (documentedItems.size() > MaxEnumValues) {
243 // Take the last element and keep it safe, then elide the surplus.
244 const QString last = documentedItems.last();
245 documentedItems = documentedItems.mid(0, MaxEnumValues - 1);
246 documentedItems += "…";
247 documentedItems += last;
248 }
249 synopsis += documentedItems.join(QLatin1String(", "));
250
251 if (!documentedItems.isEmpty())
252 synopsis += QLatin1Char(' ');
253 synopsis += QLatin1Char('}');
254 }
255 break;
256 case Node::TypeAlias:
257 if (style == Section::Summary)
258 synopsis = "(alias) ";
259 else if (style == Section::Details) {
260 extra = QStringLiteral("[alias] ");
261 QString templateDecl = node->templateDecl();
262 if (!templateDecl.isEmpty())
263 synopsis = templateDecl + QLatin1Char(' ');
264 }
265 synopsis += name;
266 break;
267 case Node::Typedef:
268 typedeff = static_cast<const TypedefNode *>(node);
269 if (typedeff->associatedEnum()) {
270 synopsis = "flags " + name;
271 } else {
272 synopsis = "typedef " + name;
273 }
274 break;
275 case Node::Property:
276 property = static_cast<const PropertyNode *>(node);
277 synopsis = name + " : " + typified(property->qualifiedDataType());
278 break;
279 case Node::Variable:
280 variable = static_cast<const VariableNode *>(node);
281 if (style == Section::AllMembers) {
282 synopsis = name + " : " + typified(variable->dataType());
283 } else {
284 synopsis = typified(variable->leftType(), true) + name + protect(variable->rightType());
285 }
286 break;
287 default:
288 synopsis = name;
289 }
290
291 if (style == Section::Summary) {
292 if (node->isPreliminary())
293 extra += "(preliminary) ";
294 else if (node->isDeprecated())
295 extra += "(deprecated) ";
296 else if (node->isObsolete())
297 extra += "(obsolete) ";
298 }
299
300 if (!extra.isEmpty()) {
301 extra.prepend("<@extra>");
302 extra.append("</@extra>");
303 }
304 return extra + synopsis;
305 }
306
307 /*!
308 */
markedUpQmlItem(const Node * node,bool summary)309 QString CppCodeMarker::markedUpQmlItem(const Node *node, bool summary)
310 {
311 QString name = taggedQmlNode(node);
312 if (summary) {
313 name = linkTag(node, name);
314 } else if (node->isQmlProperty() || node->isJsProperty()) {
315 const QmlPropertyNode *pn = static_cast<const QmlPropertyNode *>(node);
316 if (pn->isAttached())
317 name.prepend(pn->element() + QLatin1Char('.'));
318 }
319 name = "<@name>" + name + "</@name>";
320 QString synopsis;
321 if (node->isQmlProperty() || node->isJsProperty()) {
322 const QmlPropertyNode *pn = static_cast<const QmlPropertyNode *>(node);
323 synopsis = name + " : " + typified(pn->dataType());
324 } else if (node->isFunction(Node::QML) || node->isFunction(Node::JS)) {
325 const FunctionNode *func = static_cast<const FunctionNode *>(node);
326 if (!func->returnType().isEmpty())
327 synopsis = typified(func->returnType(), true) + name;
328 else
329 synopsis = name;
330 synopsis += QLatin1Char('(');
331 if (!func->parameters().isEmpty()) {
332 const Parameters ¶meters = func->parameters();
333 for (int i = 0; i < parameters.count(); ++i) {
334 if (i > 0)
335 synopsis += ", ";
336 QString name = parameters.at(i).name();
337 QString type = parameters.at(i).type();
338 QString paramName;
339 if (!name.isEmpty()) {
340 synopsis += typified(type, true);
341 paramName = name;
342 } else {
343 paramName = type;
344 }
345 synopsis += "<@param>" + protect(paramName) + "</@param>";
346 }
347 }
348 synopsis += QLatin1Char(')');
349 } else {
350 synopsis = name;
351 }
352
353 QString extra;
354 if (summary) {
355 if (node->isPreliminary())
356 extra += " (preliminary)";
357 else if (node->isDeprecated())
358 extra += " (deprecated)";
359 else if (node->isObsolete())
360 extra += " (obsolete)";
361 }
362
363 if (!extra.isEmpty()) {
364 extra.prepend("<@extra>");
365 extra.append("</@extra>");
366 }
367 return synopsis + extra;
368 }
369
markedUpName(const Node * node)370 QString CppCodeMarker::markedUpName(const Node *node)
371 {
372 QString name = linkTag(node, taggedNode(node));
373 if (node->isFunction() && !node->isMacro())
374 name += "()";
375 return name;
376 }
377
markedUpFullName(const Node * node,const Node * relative)378 QString CppCodeMarker::markedUpFullName(const Node *node, const Node *relative)
379 {
380 if (node->name().isEmpty())
381 return "global";
382
383 QString fullName;
384 for (;;) {
385 fullName.prepend(markedUpName(node));
386 if (node->parent() == relative || node->parent()->name().isEmpty())
387 break;
388 fullName.prepend("<@op>::</@op>");
389 node = node->parent();
390 }
391 return fullName;
392 }
393
markedUpEnumValue(const QString & enumValue,const Node * relative)394 QString CppCodeMarker::markedUpEnumValue(const QString &enumValue, const Node *relative)
395 {
396 if (!relative->isEnumType())
397 return enumValue;
398
399 const Node *node = relative->parent();
400 QStringList parts;
401 while (!node->isHeader() && node->parent()) {
402 parts.prepend(markedUpName(node));
403 if (node->parent() == relative || node->parent()->name().isEmpty())
404 break;
405 node = node->parent();
406 }
407 if (static_cast<const EnumNode *>(relative)->isScoped())
408 parts.append(relative->name());
409
410 parts.append(enumValue);
411 return parts.join(QLatin1String("<@op>::</@op>"));
412 }
413
markedUpIncludes(const QStringList & includes)414 QString CppCodeMarker::markedUpIncludes(const QStringList &includes)
415 {
416 QString code;
417
418 for (const auto &include : includes)
419 code += "<@preprocessor>#include <<@headerfile>" + include
420 + "</@headerfile>></@preprocessor>\n";
421 return code;
422 }
423
functionBeginRegExp(const QString & funcName)424 QString CppCodeMarker::functionBeginRegExp(const QString &funcName)
425 {
426 return QLatin1Char('^') + QRegExp::escape(funcName) + QLatin1Char('$');
427 }
428
functionEndRegExp(const QString &)429 QString CppCodeMarker::functionEndRegExp(const QString & /* funcName */)
430 {
431 return "^\\}$";
432 }
433
434 /*
435 @char
436 @class
437 @comment
438 @function
439 @keyword
440 @number
441 @op
442 @preprocessor
443 @string
444 @type
445 */
446
addMarkUp(const QString & in,const Node *,const Location &)447 QString CppCodeMarker::addMarkUp(const QString &in, const Node * /* relative */,
448 const Location & /* location */)
449 {
450 static QSet<QString> types;
451 static QSet<QString> keywords;
452
453 if (types.isEmpty()) {
454 // initialize statics
455 Q_ASSERT(keywords.isEmpty());
456 static const QString typeTable[] = {
457 QLatin1String("bool"), QLatin1String("char"), QLatin1String("double"),
458 QLatin1String("float"), QLatin1String("int"), QLatin1String("long"),
459 QLatin1String("short"), QLatin1String("signed"), QLatin1String("unsigned"),
460 QLatin1String("uint"), QLatin1String("ulong"), QLatin1String("ushort"),
461 QLatin1String("uchar"), QLatin1String("void"), QLatin1String("qlonglong"),
462 QLatin1String("qulonglong"), QLatin1String("qint"), QLatin1String("qint8"),
463 QLatin1String("qint16"), QLatin1String("qint32"), QLatin1String("qint64"),
464 QLatin1String("quint"), QLatin1String("quint8"), QLatin1String("quint16"),
465 QLatin1String("quint32"), QLatin1String("quint64"), QLatin1String("qreal"),
466 QLatin1String("cond")
467 };
468
469 static const QString keywordTable[] = {
470 QLatin1String("and"), QLatin1String("and_eq"), QLatin1String("asm"),
471 QLatin1String("auto"), QLatin1String("bitand"), QLatin1String("bitor"),
472 QLatin1String("break"), QLatin1String("case"), QLatin1String("catch"),
473 QLatin1String("class"), QLatin1String("compl"), QLatin1String("const"),
474 QLatin1String("const_cast"), QLatin1String("continue"), QLatin1String("default"),
475 QLatin1String("delete"), QLatin1String("do"), QLatin1String("dynamic_cast"),
476 QLatin1String("else"), QLatin1String("enum"), QLatin1String("explicit"),
477 QLatin1String("export"), QLatin1String("extern"), QLatin1String("false"),
478 QLatin1String("for"), QLatin1String("friend"), QLatin1String("goto"),
479 QLatin1String("if"), QLatin1String("include"), QLatin1String("inline"),
480 QLatin1String("monitor"), QLatin1String("mutable"), QLatin1String("namespace"),
481 QLatin1String("new"), QLatin1String("not"), QLatin1String("not_eq"),
482 QLatin1String("operator"), QLatin1String("or"), QLatin1String("or_eq"),
483 QLatin1String("private"), QLatin1String("protected"), QLatin1String("public"),
484 QLatin1String("register"), QLatin1String("reinterpret_cast"), QLatin1String("return"),
485 QLatin1String("sizeof"), QLatin1String("static"), QLatin1String("static_cast"),
486 QLatin1String("struct"), QLatin1String("switch"), QLatin1String("template"),
487 QLatin1String("this"), QLatin1String("throw"), QLatin1String("true"),
488 QLatin1String("try"), QLatin1String("typedef"), QLatin1String("typeid"),
489 QLatin1String("typename"), QLatin1String("union"), QLatin1String("using"),
490 QLatin1String("virtual"), QLatin1String("volatile"), QLatin1String("wchar_t"),
491 QLatin1String("while"), QLatin1String("xor"), QLatin1String("xor_eq"),
492 QLatin1String("synchronized"),
493 // Qt specific
494 QLatin1String("signals"), QLatin1String("slots"), QLatin1String("emit")
495 };
496
497 types.reserve(sizeof(typeTable) / sizeof(QString));
498 for (int j = sizeof(typeTable) / sizeof(QString) - 1; j; --j)
499 types.insert(typeTable[j]);
500
501 keywords.reserve(sizeof(keywordTable) / sizeof(QString));
502 for (int j = sizeof(keywordTable) / sizeof(QString) - 1; j; --j)
503 keywords.insert(keywordTable[j]);
504 }
505 #define readChar() ch = (i < code.length()) ? code[i++].cell() : EOF
506
507 QString code = in;
508 QString out;
509 QStringRef text;
510 int braceDepth = 0;
511 int parenDepth = 0;
512 int i = 0;
513 int start = 0;
514 int finish = 0;
515 QChar ch;
516 QRegExp classRegExp("Qt?(?:[A-Z3]+[a-z][A-Za-z]*|t)");
517 QRegExp functionRegExp("q([A-Z][a-z]+)+");
518 QRegExp findFunctionRegExp(QStringLiteral("^\\s*\\("));
519
520 readChar();
521
522 while (ch != QChar(EOF)) {
523 QString tag;
524 bool target = false;
525
526 if (ch.isLetter() || ch == '_') {
527 QString ident;
528 do {
529 ident += ch;
530 finish = i;
531 readChar();
532 } while (ch.isLetterOrNumber() || ch == '_');
533
534 if (classRegExp.exactMatch(ident)) {
535 tag = QStringLiteral("type");
536 } else if (functionRegExp.exactMatch(ident)) {
537 tag = QStringLiteral("func");
538 target = true;
539 } else if (types.contains(ident)) {
540 tag = QStringLiteral("type");
541 } else if (keywords.contains(ident)) {
542 tag = QStringLiteral("keyword");
543 } else if (braceDepth == 0 && parenDepth == 0) {
544 if (code.indexOf(findFunctionRegExp, i - 1) == i - 1)
545 tag = QStringLiteral("func");
546 target = true;
547 }
548 } else if (ch.isDigit()) {
549 do {
550 finish = i;
551 readChar();
552 } while (ch.isLetterOrNumber() || ch == '.');
553 tag = QStringLiteral("number");
554 } else {
555 switch (ch.unicode()) {
556 case '+':
557 case '-':
558 case '!':
559 case '%':
560 case '^':
561 case '&':
562 case '*':
563 case ',':
564 case '.':
565 case '<':
566 case '=':
567 case '>':
568 case '?':
569 case '[':
570 case ']':
571 case '|':
572 case '~':
573 finish = i;
574 readChar();
575 tag = QStringLiteral("op");
576 break;
577 case '"':
578 finish = i;
579 readChar();
580
581 while (ch != QChar(EOF) && ch != '"') {
582 if (ch == '\\')
583 readChar();
584 readChar();
585 }
586 finish = i;
587 readChar();
588 tag = QStringLiteral("string");
589 break;
590 case '#':
591 finish = i;
592 readChar();
593 while (ch != QChar(EOF) && ch != '\n') {
594 if (ch == '\\')
595 readChar();
596 finish = i;
597 readChar();
598 }
599 tag = QStringLiteral("preprocessor");
600 break;
601 case '\'':
602 finish = i;
603 readChar();
604
605 while (ch != QChar(EOF) && ch != '\'') {
606 if (ch == '\\')
607 readChar();
608 readChar();
609 }
610 finish = i;
611 readChar();
612 tag = QStringLiteral("char");
613 break;
614 case '(':
615 finish = i;
616 readChar();
617 ++parenDepth;
618 break;
619 case ')':
620 finish = i;
621 readChar();
622 --parenDepth;
623 break;
624 case ':':
625 finish = i;
626 readChar();
627 if (ch == ':') {
628 finish = i;
629 readChar();
630 tag = QStringLiteral("op");
631 }
632 break;
633 case '/':
634 finish = i;
635 readChar();
636 if (ch == '/') {
637 do {
638 finish = i;
639 readChar();
640 } while (ch != QChar(EOF) && ch != '\n');
641 tag = QStringLiteral("comment");
642 } else if (ch == '*') {
643 bool metAster = false;
644 bool metAsterSlash = false;
645
646 finish = i;
647 readChar();
648
649 while (!metAsterSlash) {
650 if (ch == QChar(EOF))
651 break;
652
653 if (ch == '*')
654 metAster = true;
655 else if (metAster && ch == '/')
656 metAsterSlash = true;
657 else
658 metAster = false;
659 finish = i;
660 readChar();
661 }
662 tag = QStringLiteral("comment");
663 } else {
664 tag = QStringLiteral("op");
665 }
666 break;
667 case '{':
668 finish = i;
669 readChar();
670 braceDepth++;
671 break;
672 case '}':
673 finish = i;
674 readChar();
675 braceDepth--;
676 break;
677 default:
678 finish = i;
679 readChar();
680 }
681 }
682
683 text = code.midRef(start, finish - start);
684 start = finish;
685
686 if (!tag.isEmpty()) {
687 out += QStringLiteral("<@");
688 out += tag;
689 if (target) {
690 out += QStringLiteral(" target=\"");
691 out += text;
692 out += QStringLiteral("()\"");
693 }
694 out += QStringLiteral(">");
695 }
696
697 appendProtectedString(&out, text);
698
699 if (!tag.isEmpty()) {
700 out += QStringLiteral("</@");
701 out += tag;
702 out += QStringLiteral(">");
703 }
704 }
705
706 if (start < code.length()) {
707 appendProtectedString(&out, code.midRef(start));
708 }
709
710 return out;
711 }
712
713 QT_END_NAMESPACE
714