/**************************************************************************** ** ** Copyright (C) 2019 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the tools applications of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ /* cppcodemarker.cpp */ #include "cppcodemarker.h" #include "generator.h" #include "text.h" #include "tree.h" #include #include #include QT_BEGIN_NAMESPACE /*! The constructor does nothing. */ CppCodeMarker::CppCodeMarker() { // nothing. } /*! The destructor does nothing. */ CppCodeMarker::~CppCodeMarker() { // nothing. } /*! Returns \c true. */ bool CppCodeMarker::recognizeCode(const QString & /* code */) { return true; } /*! Returns \c true if \a ext is any of a list of file extensions for the C++ language. */ bool CppCodeMarker::recognizeExtension(const QString &extension) { QByteArray ext = extension.toLatin1(); return ext == "c" || ext == "c++" || ext == "qdoc" || ext == "qtt" || ext == "qtx" || ext == "cc" || ext == "cpp" || ext == "cxx" || ext == "ch" || ext == "h" || ext == "h++" || ext == "hh" || ext == "hpp" || ext == "hxx"; } /*! Returns \c true if \a lang is either "C" or "Cpp". */ bool CppCodeMarker::recognizeLanguage(const QString &lang) { return lang == QLatin1String("C") || lang == QLatin1String("Cpp"); } /*! Returns the type of atom used to represent C++ code in the documentation. */ Atom::AtomType CppCodeMarker::atomType() const { return Atom::Code; } QString CppCodeMarker::markedUpCode(const QString &code, const Node *relative, const Location &location) { return addMarkUp(code, relative, location); } QString CppCodeMarker::markedUpSynopsis(const Node *node, const Node * /* relative */, Section::Style style) { const int MaxEnumValues = 6; const FunctionNode *func; const PropertyNode *property; const VariableNode *variable; const EnumNode *enume; const TypedefNode *typedeff; QString synopsis; QString extra; QString name; name = taggedNode(node); if (style != Section::Details) name = linkTag(node, name); name = "<@name>" + name + ""; if (style == Section::Details) { if (!node->isRelatedNonmember() && !node->isProxyNode() && !node->parent()->name().isEmpty() && !node->parent()->isHeader() && !node->isProperty() && !node->isQmlNode() && !node->isJsNode()) { name.prepend(taggedNode(node->parent()) + "::"); } } switch (node->nodeType()) { case Node::Namespace: synopsis = "namespace " + name; break; case Node::Class: synopsis = "class " + name; break; case Node::Function: func = (const FunctionNode *)node; if (style == Section::Details) { QString templateDecl = node->templateDecl(); if (!templateDecl.isEmpty()) synopsis = templateDecl + QLatin1Char(' '); } if (style != Section::AllMembers && !func->returnType().isEmpty()) synopsis += typified(func->returnType(), true); synopsis += name; if (!func->isMacroWithoutParams()) { synopsis += QLatin1Char('('); if (!func->parameters().isEmpty()) { const Parameters ¶meters = func->parameters(); for (int i = 0; i < parameters.count(); ++i) { if (i > 0) synopsis += ", "; QString name = parameters.at(i).name(); QString type = parameters.at(i).type(); QString value = parameters.at(i).defaultValue(); QString paramName; if (!name.isEmpty()) { synopsis += typified(type, true); paramName = name; } else { paramName = type; } if (style != Section::AllMembers || name.isEmpty()) synopsis += "<@param>" + protect(paramName) + ""; if (style != Section::AllMembers && !value.isEmpty()) synopsis += " = " + protect(value); } } synopsis += QLatin1Char(')'); } if (func->isConst()) synopsis += " const"; if (style == Section::Summary || style == Section::Accessors) { if (!func->isNonvirtual()) synopsis.prepend("virtual "); if (func->isFinal()) synopsis.append(" final"); if (func->isOverride()) synopsis.append(" override"); if (func->isPureVirtual()) synopsis.append(" = 0"); if (func->isRef()) synopsis.append(" &"); else if (func->isRefRef()) synopsis.append(" &&"); } else if (style == Section::AllMembers) { if (!func->returnType().isEmpty() && func->returnType() != "void") synopsis += " : " + typified(func->returnType()); } else { if (func->isRef()) synopsis.append(" &"); else if (func->isRefRef()) synopsis.append(" &&"); QStringList bracketed; if (func->isStatic()) { bracketed += "static"; } else if (!func->isNonvirtual()) { if (func->isFinal()) bracketed += "final"; if (func->isOverride()) bracketed += "override"; if (func->isPureVirtual()) bracketed += "pure"; bracketed += "virtual"; } if (func->access() == Node::Protected) bracketed += "protected"; else if (func->access() == Node::Private) bracketed += "private"; if (func->isSignal()) bracketed += "signal"; else if (func->isSlot()) bracketed += "slot"; if (!bracketed.isEmpty()) extra += QLatin1Char('[') + bracketed.join(' ') + QStringLiteral("] "); } break; case Node::Enum: enume = static_cast(node); synopsis = "enum "; if (enume->isScoped()) synopsis += "class "; synopsis += name; if (style == Section::Summary) { synopsis += " { "; QStringList documentedItems = enume->doc().enumItemNames(); if (documentedItems.isEmpty()) { const auto &enumItems = enume->items(); for (const auto &item : enumItems) documentedItems << item.name(); } const QStringList omitItems = enume->doc().omitEnumItemNames(); for (const auto &item : omitItems) documentedItems.removeAll(item); if (documentedItems.size() > MaxEnumValues) { // Take the last element and keep it safe, then elide the surplus. const QString last = documentedItems.last(); documentedItems = documentedItems.mid(0, MaxEnumValues - 1); documentedItems += "…"; documentedItems += last; } synopsis += documentedItems.join(QLatin1String(", ")); if (!documentedItems.isEmpty()) synopsis += QLatin1Char(' '); synopsis += QLatin1Char('}'); } break; case Node::TypeAlias: if (style == Section::Summary) synopsis = "(alias) "; else if (style == Section::Details) extra = QStringLiteral("[alias] "); synopsis += name; break; case Node::Typedef: typedeff = static_cast(node); if (typedeff->associatedEnum()) { synopsis = "flags " + name; } else { synopsis = "typedef " + name; } break; case Node::Property: property = static_cast(node); synopsis = name + " : " + typified(property->qualifiedDataType()); break; case Node::Variable: variable = static_cast(node); if (style == Section::AllMembers) { synopsis = name + " : " + typified(variable->dataType()); } else { synopsis = typified(variable->leftType(), true) + name + protect(variable->rightType()); } break; default: synopsis = name; } if (style == Section::Summary) { if (node->isPreliminary()) extra += "(preliminary) "; else if (node->isDeprecated()) extra += "(deprecated) "; else if (node->isObsolete()) extra += "(obsolete) "; } if (!extra.isEmpty()) { extra.prepend("<@extra>"); extra.append(""); } return extra + synopsis; } /*! */ QString CppCodeMarker::markedUpQmlItem(const Node *node, bool summary) { QString name = taggedQmlNode(node); if (summary) { name = linkTag(node, name); } else if (node->isQmlProperty() || node->isJsProperty()) { const QmlPropertyNode *pn = static_cast(node); if (pn->isAttached()) name.prepend(pn->element() + QLatin1Char('.')); } name = "<@name>" + name + ""; QString synopsis; if (node->isQmlProperty() || node->isJsProperty()) { const QmlPropertyNode *pn = static_cast(node); synopsis = name + " : " + typified(pn->dataType()); } else if (node->isFunction(Node::QML) || node->isFunction(Node::JS)) { const FunctionNode *func = static_cast(node); if (!func->returnType().isEmpty()) synopsis = typified(func->returnType(), true) + name; else synopsis = name; synopsis += QLatin1Char('('); if (!func->parameters().isEmpty()) { const Parameters ¶meters = func->parameters(); for (int i = 0; i < parameters.count(); ++i) { if (i > 0) synopsis += ", "; QString name = parameters.at(i).name(); QString type = parameters.at(i).type(); QString paramName; if (!name.isEmpty()) { synopsis += typified(type, true); paramName = name; } else { paramName = type; } synopsis += "<@param>" + protect(paramName) + ""; } } synopsis += QLatin1Char(')'); } else { synopsis = name; } QString extra; if (summary) { if (node->isPreliminary()) extra += " (preliminary)"; else if (node->isDeprecated()) extra += " (deprecated)"; else if (node->isObsolete()) extra += " (obsolete)"; } if (!extra.isEmpty()) { extra.prepend("<@extra>"); extra.append(""); } return synopsis + extra; } QString CppCodeMarker::markedUpName(const Node *node) { QString name = linkTag(node, taggedNode(node)); if (node->isFunction() && !node->isMacro()) name += "()"; return name; } QString CppCodeMarker::markedUpFullName(const Node *node, const Node *relative) { if (node->name().isEmpty()) return "global"; QString fullName; for (;;) { fullName.prepend(markedUpName(node)); if (node->parent() == relative || node->parent()->name().isEmpty()) break; fullName.prepend("<@op>::"); node = node->parent(); } return fullName; } QString CppCodeMarker::markedUpEnumValue(const QString &enumValue, const Node *relative) { if (!relative->isEnumType()) return enumValue; const Node *node = relative->parent(); QStringList parts; while (!node->isHeader() && node->parent()) { parts.prepend(markedUpName(node)); if (node->parent() == relative || node->parent()->name().isEmpty()) break; node = node->parent(); } if (static_cast(relative)->isScoped()) parts.append(relative->name()); parts.append(enumValue); return parts.join(QLatin1String("<@op>::")); } QString CppCodeMarker::markedUpIncludes(const QStringList &includes) { QString code; for (const auto &include : includes) code += "<@preprocessor>#include <<@headerfile>" + include + ">\n"; return code; } QString CppCodeMarker::functionBeginRegExp(const QString &funcName) { return QLatin1Char('^') + QRegExp::escape(funcName) + QLatin1Char('$'); } QString CppCodeMarker::functionEndRegExp(const QString & /* funcName */) { return "^\\}$"; } /* @char @class @comment @function @keyword @number @op @preprocessor @string @type */ QString CppCodeMarker::addMarkUp(const QString &in, const Node * /* relative */, const Location & /* location */) { static QSet types; static QSet keywords; if (types.isEmpty()) { // initialize statics Q_ASSERT(keywords.isEmpty()); static const QString typeTable[] = { QLatin1String("bool"), QLatin1String("char"), QLatin1String("double"), QLatin1String("float"), QLatin1String("int"), QLatin1String("long"), QLatin1String("short"), QLatin1String("signed"), QLatin1String("unsigned"), QLatin1String("uint"), QLatin1String("ulong"), QLatin1String("ushort"), QLatin1String("uchar"), QLatin1String("void"), QLatin1String("qlonglong"), QLatin1String("qulonglong"), QLatin1String("qint"), QLatin1String("qint8"), QLatin1String("qint16"), QLatin1String("qint32"), QLatin1String("qint64"), QLatin1String("quint"), QLatin1String("quint8"), QLatin1String("quint16"), QLatin1String("quint32"), QLatin1String("quint64"), QLatin1String("qreal"), QLatin1String("cond") }; static const QString keywordTable[] = { QLatin1String("and"), QLatin1String("and_eq"), QLatin1String("asm"), QLatin1String("auto"), QLatin1String("bitand"), QLatin1String("bitor"), QLatin1String("break"), QLatin1String("case"), QLatin1String("catch"), QLatin1String("class"), QLatin1String("compl"), QLatin1String("const"), QLatin1String("const_cast"), QLatin1String("continue"), QLatin1String("default"), QLatin1String("delete"), QLatin1String("do"), QLatin1String("dynamic_cast"), QLatin1String("else"), QLatin1String("enum"), QLatin1String("explicit"), QLatin1String("export"), QLatin1String("extern"), QLatin1String("false"), QLatin1String("for"), QLatin1String("friend"), QLatin1String("goto"), QLatin1String("if"), QLatin1String("include"), QLatin1String("inline"), QLatin1String("monitor"), QLatin1String("mutable"), QLatin1String("namespace"), QLatin1String("new"), QLatin1String("not"), QLatin1String("not_eq"), QLatin1String("operator"), QLatin1String("or"), QLatin1String("or_eq"), QLatin1String("private"), QLatin1String("protected"), QLatin1String("public"), QLatin1String("register"), QLatin1String("reinterpret_cast"), QLatin1String("return"), QLatin1String("sizeof"), QLatin1String("static"), QLatin1String("static_cast"), QLatin1String("struct"), QLatin1String("switch"), QLatin1String("template"), QLatin1String("this"), QLatin1String("throw"), QLatin1String("true"), QLatin1String("try"), QLatin1String("typedef"), QLatin1String("typeid"), QLatin1String("typename"), QLatin1String("union"), QLatin1String("using"), QLatin1String("virtual"), QLatin1String("volatile"), QLatin1String("wchar_t"), QLatin1String("while"), QLatin1String("xor"), QLatin1String("xor_eq"), QLatin1String("synchronized"), // Qt specific QLatin1String("signals"), QLatin1String("slots"), QLatin1String("emit") }; types.reserve(sizeof(typeTable) / sizeof(QString)); for (int j = sizeof(typeTable) / sizeof(QString) - 1; j; --j) types.insert(typeTable[j]); keywords.reserve(sizeof(keywordTable) / sizeof(QString)); for (int j = sizeof(keywordTable) / sizeof(QString) - 1; j; --j) keywords.insert(keywordTable[j]); } #define readChar() ch = (i < code.length()) ? code[i++].cell() : EOF QString code = in; QString out; QStringRef text; int braceDepth = 0; int parenDepth = 0; int i = 0; int start = 0; int finish = 0; QChar ch; QRegExp classRegExp("Qt?(?:[A-Z3]+[a-z][A-Za-z]*|t)"); QRegExp functionRegExp("q([A-Z][a-z]+)+"); QRegExp findFunctionRegExp(QStringLiteral("^\\s*\\(")); readChar(); while (ch != QChar(EOF)) { QString tag; bool target = false; if (ch.isLetter() || ch == '_') { QString ident; do { ident += ch; finish = i; readChar(); } while (ch.isLetterOrNumber() || ch == '_'); if (classRegExp.exactMatch(ident)) { tag = QStringLiteral("type"); } else if (functionRegExp.exactMatch(ident)) { tag = QStringLiteral("func"); target = true; } else if (types.contains(ident)) { tag = QStringLiteral("type"); } else if (keywords.contains(ident)) { tag = QStringLiteral("keyword"); } else if (braceDepth == 0 && parenDepth == 0) { if (code.indexOf(findFunctionRegExp, i - 1) == i - 1) tag = QStringLiteral("func"); target = true; } } else if (ch.isDigit()) { do { finish = i; readChar(); } while (ch.isLetterOrNumber() || ch == '.'); tag = QStringLiteral("number"); } else { switch (ch.unicode()) { case '+': case '-': case '!': case '%': case '^': case '&': case '*': case ',': case '.': case '<': case '=': case '>': case '?': case '[': case ']': case '|': case '~': finish = i; readChar(); tag = QStringLiteral("op"); break; case '"': finish = i; readChar(); while (ch != QChar(EOF) && ch != '"') { if (ch == '\\') readChar(); readChar(); } finish = i; readChar(); tag = QStringLiteral("string"); break; case '#': finish = i; readChar(); while (ch != QChar(EOF) && ch != '\n') { if (ch == '\\') readChar(); finish = i; readChar(); } tag = QStringLiteral("preprocessor"); break; case '\'': finish = i; readChar(); while (ch != QChar(EOF) && ch != '\'') { if (ch == '\\') readChar(); readChar(); } finish = i; readChar(); tag = QStringLiteral("char"); break; case '(': finish = i; readChar(); ++parenDepth; break; case ')': finish = i; readChar(); --parenDepth; break; case ':': finish = i; readChar(); if (ch == ':') { finish = i; readChar(); tag = QStringLiteral("op"); } break; case '/': finish = i; readChar(); if (ch == '/') { do { finish = i; readChar(); } while (ch != QChar(EOF) && ch != '\n'); tag = QStringLiteral("comment"); } else if (ch == '*') { bool metAster = false; bool metAsterSlash = false; finish = i; readChar(); while (!metAsterSlash) { if (ch == QChar(EOF)) break; if (ch == '*') metAster = true; else if (metAster && ch == '/') metAsterSlash = true; else metAster = false; finish = i; readChar(); } tag = QStringLiteral("comment"); } else { tag = QStringLiteral("op"); } break; case '{': finish = i; readChar(); braceDepth++; break; case '}': finish = i; readChar(); braceDepth--; break; default: finish = i; readChar(); } } text = code.midRef(start, finish - start); start = finish; if (!tag.isEmpty()) { out += QStringLiteral("<@"); out += tag; if (target) { out += QStringLiteral(" target=\""); out += text; out += QStringLiteral("()\""); } out += QStringLiteral(">"); } appendProtectedString(&out, text); if (!tag.isEmpty()) { out += QStringLiteral(""); } } if (start < code.length()) { appendProtectedString(&out, code.midRef(start)); } return out; } QT_END_NAMESPACE