// Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "cppcodemarker.h" #include "access.h" #include "enumnode.h" #include "functionnode.h" #include "namespacenode.h" #include "propertynode.h" #include "qmlpropertynode.h" #include "text.h" #include "tree.h" #include "typedefnode.h" #include "variablenode.h" #include #include QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; /*! 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 VariableNode *variable; const EnumNode *enume; QString synopsis; 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()) { name.prepend(taggedNode(node->parent()) + "::"); } } switch (node->nodeType()) { case Node::Namespace: case Node::Class: case Node::Struct: case Node::Union: synopsis = Node::nodeTypeString(node->nodeType()); synopsis += QLatin1Char(' ') + name; break; case Node::Function: func = (const FunctionNode *)node; if (style == Section::Details) { auto templateDecl = node->templateDecl(); if (templateDecl) synopsis = protect((*templateDecl).to_qstring()) + 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(); bool trailingSpace = style != Section::AllMembers && !name.isEmpty(); synopsis += typified(type, trailingSpace); if (style != Section::AllMembers && !name.isEmpty()) synopsis += "<@param>" + protect(name) + ""; 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(" &&"); } 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::Details) { auto templateDecl = node->templateDecl(); if (templateDecl) synopsis += protect((*templateDecl).to_qstring()) + QLatin1Char(' '); } synopsis += name; break; case Node::Typedef: if (static_cast(node)->associatedEnum()) synopsis = "flags "; synopsis += name; break; case Node::Property: { auto property = static_cast(node); synopsis = name + " : " + typified(property->qualifiedDataType()); break; } case Node::QmlProperty: { auto property = static_cast(node); synopsis = name + " : " + typified(property->dataType()); 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; } QString extra = CodeMarker::extraSynopsis(node, style); if (!extra.isEmpty()) { extra.prepend(u"<@extra>"_s); extra.append(u" "_s); } return extra + synopsis; } /*! */ QString CppCodeMarker::markedUpQmlItem(const Node *node, bool summary) { QString name = taggedQmlNode(node); QString synopsis; if (summary) { name = linkTag(node, name); } else if (node->isQmlProperty()) { const auto *pn = static_cast(node); if (pn->isAttached()) name.prepend(pn->element() + QLatin1Char('.')); } name = "<@name>" + name + ""; if (node->isQmlProperty()) { const auto *pn = static_cast(node); synopsis = name + " : " + typified(pn->dataType()); } else if (node->isFunction(Node::QML)) { const auto *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 = CodeMarker::extraSynopsis(node, summary ? Section::Summary : Section::Details); if (!extra.isEmpty()) { extra.prepend(u" <@extra>"_s); extra.append(u""_s); } 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::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::markedUpInclude(const QString &include) { return "<@preprocessor>#include <<@headerfile>" + include + ">"; } /* @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{ 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 QSet keywords{ 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") }; QString code = in; QString out; QStringView text; int braceDepth = 0; int parenDepth = 0; int i = 0; int start = 0; int finish = 0; QChar ch; static const QRegularExpression classRegExp(QRegularExpression::anchoredPattern("Qt?(?:[A-Z3]+[a-z][A-Za-z]*|t)")); static const QRegularExpression functionRegExp(QRegularExpression::anchoredPattern("q([A-Z][a-z]+)+")); static const QRegularExpression findFunctionRegExp(QStringLiteral("^\\s*\\(")); bool atEOF = false; auto readChar = [&]() { if (i < code.size()) ch = code[i++]; else atEOF = true; }; readChar(); while (!atEOF) { QString tag; bool target = false; if (ch.isLetter() || ch == '_') { QString ident; do { ident += ch; finish = i; readChar(); } while (!atEOF && (ch.isLetterOrNumber() || ch == '_')); if (classRegExp.match(ident).hasMatch()) { tag = QStringLiteral("type"); } else if (functionRegExp.match(ident).hasMatch()) { 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 (!atEOF && (ch.isLetterOrNumber() || ch == '.' || 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 (!atEOF && ch != '"') { if (ch == '\\') readChar(); readChar(); } finish = i; readChar(); tag = QStringLiteral("string"); break; case '#': finish = i; readChar(); while (!atEOF && ch != '\n') { if (ch == '\\') readChar(); finish = i; readChar(); } tag = QStringLiteral("preprocessor"); break; case '\'': finish = i; readChar(); while (!atEOF && 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 (!atEOF && ch == ':') { finish = i; readChar(); tag = QStringLiteral("op"); } break; case '/': finish = i; readChar(); if (!atEOF && ch == '/') { do { finish = i; readChar(); } while (!atEOF && ch != '\n'); tag = QStringLiteral("comment"); } else if (ch == '*') { bool metAster = false; bool metAsterSlash = false; finish = i; readChar(); while (!metAsterSlash) { if (atEOF) 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 = QStringView{code}.mid(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.size()) { appendProtectedString(&out, QStringView{code}.mid(start)); } return out; } QT_END_NAMESPACE