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 &parameters = 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 += "&hellip;";
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 &parameters = 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 &lt;<@headerfile>" + include
420                 + "</@headerfile>&gt;</@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