diff options
Diffstat (limited to 'src')
30 files changed, 2475 insertions, 2131 deletions
diff --git a/src/qdoc/clangcodeparser.cpp b/src/qdoc/clangcodeparser.cpp new file mode 100644 index 000000000..44da9d7ed --- /dev/null +++ b/src/qdoc/clangcodeparser.cpp @@ -0,0 +1,1658 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + clangcodeparser.cpp +*/ + +#include <qfile.h> +#include <stdio.h> +#include <errno.h> +#include "codechunk.h" +#include "config.h" +#include "clangcodeparser.h" +#include "qdocdatabase.h" +#include <qdebug.h> +#include <qscopedvaluerollback.h> +#include <qelapsedtimer.h> +#include <qtemporarydir.h> +#include "generator.h" + +#include <clang-c/Index.h> + +QT_BEGIN_NAMESPACE + +static CXTranslationUnit_Flags flags_ = (CXTranslationUnit_Flags)0; +static CXIndex index_ = 0; + +/*! + Call clang_visitChildren on the given cursor with the lambda as a callback + T can be any functor that is callable with a CXCursor parameter and returns a CXChildVisitResult + (in other word compatible with function<CXChildVisitResult(CXCursor)> + */ +template <typename T> bool visitChildrenLambda(CXCursor cursor, T &&lambda) +{ + CXCursorVisitor visitor = [](CXCursor c, CXCursor , CXClientData client_data) -> CXChildVisitResult + { return (*static_cast<T*>(client_data))(c); }; + return clang_visitChildren(cursor, visitor, &lambda); +} + +/*! + convert a CXString to a QString, and dispose the CXString + */ +static QString fromCXString(CXString &&string) +{ + QString ret = QString::fromUtf8(clang_getCString(string)); + clang_disposeString(string); + return ret; +} + + +/*! + convert a CXSourceLocation to a qdoc Location + */ +static Location fromCXSourceLocation(CXSourceLocation location) +{ + unsigned int line, column; + CXString file; + clang_getPresumedLocation(location, &file, &line, &column); + Location l(fromCXString(std::move(file))); + l.setColumnNo(column); + l.setLineNo(line); + return l; +} + +/*! + convert a CX_CXXAccessSpecifier to Node::Access + */ +static Node::Access fromCX_CXXAccessSpecifier(CX_CXXAccessSpecifier spec) { + switch (spec) { + case CX_CXXPrivate: return Node::Private; + case CX_CXXProtected: return Node::Protected; + case CX_CXXPublic: return Node::Public; + default: return Node::Public; + } +} + +/*! + Returns the spelling in the file for a source range + */ +static QString getSpelling(CXSourceRange range) +{ + auto start = clang_getRangeStart(range); + auto end = clang_getRangeEnd(range); + CXFile file1, file2; + unsigned int offset1, offset2; + clang_getFileLocation(start, &file1, nullptr, nullptr, &offset1); + clang_getFileLocation(end, &file2, nullptr, nullptr, &offset2); + if (file1 != file2 || offset2 <= offset1) + return QString(); + QFile file(fromCXString(clang_getFileName(file1))); + if (!file.open(QFile::ReadOnly)) + return QString(); + file.seek(offset1); + return QString::fromUtf8(file.read(offset2 - offset1)); +} + + +/*! + Returns the function name from a given cursor representing a + function declaration. This is usually clang_getCursorSpelling, but + not for the conversion function in which case it is a bit more complicated + */ +QString functionName(CXCursor cursor) +{ + if (clang_getCursorKind(cursor) == CXCursor_ConversionFunction) { + // For a CXCursor_ConversionFunction we don't want the spelling which would be something like + // "operator type-parameter-0-0" or "operator unsigned int". + // we want the actual name as spelled; + QString type = fromCXString(clang_getTypeSpelling(clang_getCursorResultType(cursor))); + if (type.isEmpty()) + return fromCXString(clang_getCursorSpelling(cursor)); + return QLatin1String("operator ") + type; + } + + QString name = fromCXString(clang_getCursorSpelling(cursor)); + + // Remove template stuff from constructor and destructor but not from operator< + auto ltLoc = name.indexOf('<'); + if (ltLoc > 0 && !name.startsWith("operator<")) + name = name.left(ltLoc); + return name; +} + +/*! + Find the node from the QDocDatabase \a qdb that corrseponds to the declaration + represented by the cursor \a cur, if it exists. + */ +static Node *findNodeForCursor(QDocDatabase* qdb, CXCursor cur) { + auto kind = clang_getCursorKind(cur); + if (clang_isInvalid(kind)) + return nullptr; + if (kind == CXCursor_TranslationUnit) + return qdb->primaryTreeRoot(); + + Node *p = findNodeForCursor(qdb, clang_getCursorSemanticParent(cur)); + if (!p) + return nullptr; + if (!p->isAggregate()) + return nullptr; + auto parent = static_cast<Aggregate *>(p); + + switch (kind) { + case CXCursor_Namespace: + return parent->findChildNode(fromCXString(clang_getCursorSpelling(cur)), Node::Namespace); + case CXCursor_StructDecl: + case CXCursor_ClassDecl: + case CXCursor_UnionDecl: + case CXCursor_ClassTemplate: + return parent->findChildNode(fromCXString(clang_getCursorSpelling(cur)), Node::Class); + case CXCursor_FunctionDecl: + case CXCursor_FunctionTemplate: + case CXCursor_CXXMethod: + case CXCursor_Constructor: + case CXCursor_Destructor: + case CXCursor_ConversionFunction: { + NodeList candidates; + parent->findChildren(functionName(cur), candidates); + if (candidates.isEmpty()) + return nullptr; + CXType funcType = clang_getCursorType(cur); + auto numArg = clang_getNumArgTypes(funcType); + bool isVariadic = clang_isFunctionTypeVariadic(funcType); + QVarLengthArray<QString, 20> args; + for (Node *candidate : qAsConst(candidates)) { + if (!candidate->isFunction()) + continue; + auto fn = static_cast<FunctionNode*>(candidate); + const auto &funcParams = fn->parameters(); + const int actualArg = numArg - fn->isPrivateSignal(); + if (funcParams.count() != (actualArg + isVariadic)) + continue; + if (fn->isConst() != bool(clang_CXXMethod_isConst(cur))) + continue; + if (isVariadic && funcParams.last().dataType() != QLatin1String("...")) + continue; + bool different = false; + for (int i = 0; i < actualArg; i++) { + if (args.size() <= i) + args.append(fromCXString(clang_getTypeSpelling(clang_getArgType(funcType, i)))); + QString t1 = funcParams.at(i).dataType(); + QString t2 = args.at(i); + auto p2 = parent; + while (p2 && t1 != t2) { + QString parentScope = p2->name() + QLatin1String("::"); + t1 = t1.remove(parentScope); + t2 = t2.remove(parentScope); + p2 = p2->parent(); + } + if (t1 != t2) { + different = true; + break; + } + } + if (!different) + return fn; + } + return nullptr; + } + case CXCursor_EnumDecl: + return parent->findChildNode(fromCXString(clang_getCursorSpelling(cur)), Node::Enum); + case CXCursor_FieldDecl: + case CXCursor_VarDecl: + return parent->findChildNode(fromCXString(clang_getCursorSpelling(cur)), Node::Variable); + case CXCursor_TypedefDecl: + return parent->findChildNode(fromCXString(clang_getCursorSpelling(cur)), Node::Typedef); + default: + return nullptr; + } +} + +/*! + Find the function node from the QDocDatabase \a qdb that + corrseponds to the declaration represented by the cursor + \a cur, if it exists. + */ +static Node *findFunctionNodeForCursor(QDocDatabase* qdb, CXCursor cur) { + auto kind = clang_getCursorKind(cur); + if (clang_isInvalid(kind)) + return nullptr; + if (kind == CXCursor_TranslationUnit) + return qdb->primaryTreeRoot(); + + Node *p = findNodeForCursor(qdb, clang_getCursorSemanticParent(cur)); + if (!p || !p->isAggregate()) + return nullptr; + auto parent = static_cast<Aggregate *>(p); + + switch (kind) { + case CXCursor_FunctionDecl: + case CXCursor_FunctionTemplate: + case CXCursor_CXXMethod: + case CXCursor_Constructor: + case CXCursor_Destructor: + case CXCursor_ConversionFunction: { + NodeList candidates; + parent->findChildren(functionName(cur), candidates); + if (candidates.isEmpty()) + return nullptr; + CXType funcType = clang_getCursorType(cur); + auto numArg = clang_getNumArgTypes(funcType); + bool isVariadic = clang_isFunctionTypeVariadic(funcType); + QVarLengthArray<QString, 20> args; + for (Node *candidate : qAsConst(candidates)) { + if (!candidate->isFunction()) + continue; + auto fn = static_cast<FunctionNode*>(candidate); + const auto &funcParams = fn->parameters(); + if (funcParams.count() != (numArg + isVariadic)) + continue; + if (fn->isConst() != bool(clang_CXXMethod_isConst(cur))) + continue; + if (isVariadic && funcParams.last().dataType() != QLatin1String("...")) + continue; + bool different = false; + for (int i = 0; i < numArg; i++) { + if (args.size() <= i) + args.append(fromCXString(clang_getTypeSpelling(clang_getArgType(funcType, i)))); + QString t1 = funcParams.at(i).dataType(); + QString t2 = args.at(i); + auto p2 = parent; + while (p2 && t1 != t2) { + QString parentScope = p2->name() + QLatin1String("::"); + t1 = t1.remove(parentScope); + t2 = t2.remove(parentScope); + p2 = p2->parent(); + } + if (t1 != t2) { + different = true; + break; + } + } + if (!different) + return fn; + } + break; + } + default: + break; + } + return nullptr; +} + +class ClangVisitor { +public: + ClangVisitor(QDocDatabase *qdb, const QSet<QString> &allHeaders) + : qdb_(qdb), parent_(qdb->primaryTreeRoot()), allHeaders_(allHeaders) { } + + QDocDatabase* qdocDB() { return qdb_; } + + CXChildVisitResult visitChildren(CXCursor cursor) + { + auto ret = visitChildrenLambda(cursor, [&](CXCursor cur) { + auto loc = clang_getCursorLocation(cur); + if (clang_Location_isFromMainFile(loc)) + return visitSource(cur, loc); + CXFile file; + clang_getFileLocation(loc, &file, nullptr, nullptr, nullptr); + bool isInteresting = false; + auto it = isInterestingCache_.find(file); + if (it != isInterestingCache_.end()) { + isInteresting = *it; + } else { + QFileInfo fi(fromCXString(clang_getFileName(file))); + isInteresting = allHeaders_.contains(fi.canonicalFilePath()); + isInterestingCache_[file] = isInteresting; + } + + if (isInteresting) { + return visitHeader(cur, loc); + } + + return CXChildVisit_Continue; + }); + return ret ? CXChildVisit_Break : CXChildVisit_Continue; + } + + /* + Not sure about all the possibilities, when the cursor + location is not in the main file. + */ + CXChildVisitResult visitFnArg(CXCursor cursor, Node** fnNode, bool &ignoreSignature) + { + auto ret = visitChildrenLambda(cursor, [&](CXCursor cur) { + auto loc = clang_getCursorLocation(cur); + if (clang_Location_isFromMainFile(loc)) + return visitFnSignature(cur, loc, fnNode, ignoreSignature); + return CXChildVisit_Continue; + }); + return ret ? CXChildVisit_Break : CXChildVisit_Continue; + } + + Node *nodeForCommentAtLocation(CXSourceLocation loc, CXSourceLocation nextCommentLoc); +private: + /*! + SimpleLoc represents a simple location in the main source file, + which can be used as a key in a QMap. + */ + struct SimpleLoc + { + unsigned int line, column; + friend bool operator<(const SimpleLoc &a, const SimpleLoc &b) { + return a.line != b.line ? a.line < b.line : a.column < b.column; + } + }; + /*! + \variable ClangVisitor::declMap_ + Map of all the declarations in the source file so we can match them + with a documentation comment. + */ + QMap<SimpleLoc, CXCursor> declMap_; + + QDocDatabase* qdb_; + Aggregate *parent_; + QSet<QString> allHeaders_; + QHash<CXFile, bool> isInterestingCache_; // doing a canonicalFilePath is slow, so keep a cache. + + /*! + Returns true if the symbol should be ignored for the documentation. + */ + bool ignoredSymbol(const QString &symbolName) { + if (symbolName.startsWith(QLatin1String("qt_"))) + return true; + if (symbolName == QLatin1String("QPrivateSignal")) + return true; + if (parent_->name() != "QObject" && parent_->name() != "QMetaType" + && (symbolName == QLatin1String("metaObject") || symbolName == QLatin1String("tr") + || symbolName == QLatin1String("trUtf8"))) + return true; + return false; + } + + /*! + The type parameters do not need to be fully qualified + This function removes the ClassName:: if needed. + + example: 'QLinkedList::iterator' -> 'iterator' + */ + QString adjustTypeName(const QString &typeName) { + auto parent = parent_->parent(); + if (parent && parent->isClass()) { + QStringRef typeNameConstRemoved(&typeName); + if (typeNameConstRemoved.startsWith(QLatin1String("const "))) + typeNameConstRemoved = typeName.midRef(6); + + auto parentName = parent->fullName(); + if (typeNameConstRemoved.startsWith(parentName) + && typeNameConstRemoved.mid(parentName.size(), 2) == QLatin1String("::")) { + QString result = typeName; + result.remove(typeNameConstRemoved.position(), parentName.size() + 2); + return result; + } + } + return typeName; + } + + CXChildVisitResult visitSource(CXCursor cursor, CXSourceLocation loc); + CXChildVisitResult visitHeader(CXCursor cursor, CXSourceLocation loc); + CXChildVisitResult visitFnSignature(CXCursor cursor, CXSourceLocation loc, Node** fnNode, bool &ignoreSignature); + void parseProperty(const QString &spelling, const Location &loc); + void readParameterNamesAndAttributes(FunctionNode* fn, CXCursor cursor); + Aggregate *getSemanticParent(CXCursor cursor); +}; + +/*! + Visits a cursor in the .cpp file. + This fills the declMap_ + */ +CXChildVisitResult ClangVisitor::visitSource(CXCursor cursor, CXSourceLocation loc) +{ + auto kind = clang_getCursorKind(cursor); + if (clang_isDeclaration(kind)) { + SimpleLoc l; + clang_getPresumedLocation(loc, nullptr, &l.line, &l.column); + declMap_.insert(l, cursor); + return CXChildVisit_Recurse; + } + return CXChildVisit_Continue; +} + +/*! + If the semantic and lexical parent cursors of \a cursor are + not the same, find the Aggregate node for the semantic parent + cursor and return it. Otherwise return the current parent. + */ +Aggregate *ClangVisitor::getSemanticParent(CXCursor cursor) +{ + CXCursor sp = clang_getCursorSemanticParent(cursor); + CXCursor lp = clang_getCursorLexicalParent(cursor); + if (!clang_equalCursors(sp, lp) && clang_isDeclaration(clang_getCursorKind(sp))) { + Node* spn = findNodeForCursor(qdb_, sp); + if (spn && spn->isAggregate()) { + return static_cast<Aggregate*>(spn); + } + } + return parent_; +} + +CXChildVisitResult ClangVisitor::visitFnSignature(CXCursor cursor, CXSourceLocation , Node** fnNode, bool &ignoreSignature) +{ + switch (clang_getCursorKind(cursor)) { + case CXCursor_FunctionDecl: + case CXCursor_FunctionTemplate: + case CXCursor_CXXMethod: + case CXCursor_Constructor: + case CXCursor_Destructor: + case CXCursor_ConversionFunction: { + ignoreSignature = false; + if (ignoredSymbol(functionName(cursor))) { + *fnNode = 0; + ignoreSignature = true; + } else { + *fnNode = findFunctionNodeForCursor(qdb_, cursor); + if (*fnNode && (*fnNode)->isFunction()) { + FunctionNode* fn = static_cast<FunctionNode*>(*fnNode); + readParameterNamesAndAttributes(fn, cursor); + } + } + break; + } + default: + break; + } + return CXChildVisit_Continue; +} + +CXChildVisitResult ClangVisitor::visitHeader(CXCursor cursor, CXSourceLocation loc) +{ + auto kind = clang_getCursorKind(cursor); + switch (kind) { + case CXCursor_TypeAliasDecl: { + QString spelling = getSpelling(clang_getCursorExtent(cursor)); + QStringList typeAlias = spelling.split(QChar('=')); + if (typeAlias.size() == 2) { + typeAlias[0] = typeAlias[0].trimmed(); + typeAlias[1] = typeAlias[1].trimmed(); + int lastBlank = typeAlias[0].lastIndexOf(QChar(' ')); + if (lastBlank > 0) { + typeAlias[0] = typeAlias[0].right(typeAlias[0].size() - (lastBlank + 1)); + TypeAliasNode* ta = new TypeAliasNode(parent_, typeAlias[0], typeAlias[1]); + ta->setAccess(fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor))); + ta->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor))); + } + } + return CXChildVisit_Continue; + } + case CXCursor_StructDecl: + case CXCursor_UnionDecl: + if (fromCXString(clang_getCursorSpelling(cursor)).isEmpty()) // anonymous struct or union + return CXChildVisit_Continue; + case CXCursor_ClassDecl: + case CXCursor_ClassTemplate: { + + if (!clang_isCursorDefinition(cursor)) + return CXChildVisit_Continue; + + if (findNodeForCursor(qdb_, cursor)) // Was already parsed, propably in another translation unit + return CXChildVisit_Continue; + + QString className = fromCXString(clang_getCursorSpelling(cursor)); + + Aggregate* semanticParent = getSemanticParent(cursor); + if (semanticParent && semanticParent->findChildNode(className, Node::Class)) { + return CXChildVisit_Continue; + } + + ClassNode *classe = new ClassNode(semanticParent, className); + classe->setAccess(fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor))); + classe->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor))); + + if (kind == CXCursor_ClassTemplate) { + QString displayName = fromCXString(clang_getCursorSpelling(cursor)); + classe->setTemplateStuff(displayName.mid(className.size())); + } + + QScopedValueRollback<Aggregate *> setParent(parent_, classe); + return visitChildren(cursor); + + } + case CXCursor_CXXBaseSpecifier: { + if (!parent_->isClass()) + return CXChildVisit_Continue; + auto access = fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor)); + auto type = clang_getCursorType(cursor); + auto baseCursor = clang_getTypeDeclaration(type); + auto baseNode = findNodeForCursor(qdb_, baseCursor); + auto classe = static_cast<ClassNode*>(parent_); + if (!baseNode || !baseNode->isClass()) { + QString bcName = fromCXString(clang_getCursorSpelling(baseCursor)); + classe->addUnresolvedBaseClass(access, QStringList(bcName), bcName); + return CXChildVisit_Continue; + } + auto baseClasse = static_cast<ClassNode*>(baseNode); + classe->addResolvedBaseClass(access, baseClasse); + return CXChildVisit_Continue; + } + case CXCursor_Namespace: { + QString namespaceName = fromCXString(clang_getCursorDisplayName(cursor)); + NamespaceNode* ns = 0; + if (parent_) + ns = static_cast<NamespaceNode*>(parent_->findChildNode(namespaceName, Node::Namespace)); + if (!ns) { + ns = new NamespaceNode(parent_, namespaceName); + ns->setAccess(Node::Public); + ns->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor))); + } + QScopedValueRollback<Aggregate *> setParent(parent_, ns); + return visitChildren(cursor); + } + case CXCursor_FunctionDecl: + case CXCursor_FunctionTemplate: + case CXCursor_CXXMethod: + case CXCursor_Constructor: + case CXCursor_Destructor: + case CXCursor_ConversionFunction: { + if (findNodeForCursor(qdb_, cursor)) // Was already parsed, propably in another translation unit + return CXChildVisit_Continue; + QString name = functionName(cursor); + if (ignoredSymbol(name)) + return CXChildVisit_Continue; + + CXType funcType = clang_getCursorType(cursor); + + FunctionNode* fn = new FunctionNode(Node::Function, parent_, name, false); + + CXSourceRange range = clang_Cursor_getCommentRange(cursor); + if (!clang_Range_isNull(range)) { + QString comment = getSpelling(range); + if (comment.startsWith("//!")) { + int tag = comment.indexOf(QChar('[')); + if (tag > 0) { + int end = comment.indexOf(QChar(']'), tag); + if (end > 0) + fn->setTag(comment.mid(tag, 1 + end - tag)); + } + } + } + fn->setAccess(fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor))); + fn->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor))); + if (kind == CXCursor_Constructor + // a constructor template is classified as CXCursor_FunctionTemplate + || (kind == CXCursor_FunctionTemplate && name == parent_->name())) + fn->setMetaness(FunctionNode::Ctor); + else if (kind == CXCursor_Destructor) + fn->setMetaness(FunctionNode::Dtor); + else + fn->setReturnType(adjustTypeName(fromCXString( + clang_getTypeSpelling(clang_getResultType(funcType))))); + + fn->setStatic(clang_CXXMethod_isStatic(cursor)); + fn->setConst(clang_CXXMethod_isConst(cursor)); + fn->setVirtualness(!clang_CXXMethod_isVirtual(cursor) ? FunctionNode::NonVirtual + : clang_CXXMethod_isPureVirtual(cursor) ? FunctionNode::PureVirtual + : FunctionNode::NormalVirtual); + CXRefQualifierKind refQualKind = clang_Type_getCXXRefQualifier(funcType); + if (refQualKind == CXRefQualifier_LValue) + fn->setRef(true); + else if (refQualKind == CXRefQualifier_RValue) + fn->setRefRef(true); + // For virtual functions, determine what it overrides + // (except for destructor for which we do not want to classify as overridden) + if (!fn->isNonvirtual() && kind != CXCursor_Destructor) { + CXCursor *overridden; + unsigned int numOverridden = 0; + clang_getOverriddenCursors(cursor, &overridden, &numOverridden); + for (uint i = 0; i < numOverridden; ++i) { + auto n = findNodeForCursor(qdb_, overridden[i]); + if (n && n->isFunction()) { + fn->setReimplementedFrom(static_cast<FunctionNode *>(n)); + } + } + clang_disposeOverriddenCursors(overridden); + } + auto numArg = clang_getNumArgTypes(funcType); + QVector<Parameter> pvect; + pvect.reserve(numArg); + for (int i = 0; i < numArg; ++i) { + CXType argType = clang_getArgType(funcType, i); + if (fn->isCtor()) { + if (fromCXString(clang_getTypeSpelling(clang_getPointeeType(argType))) == name) { + if (argType.kind == CXType_RValueReference) + fn->setMetaness(FunctionNode::MCtor); + else if (argType.kind == CXType_LValueReference) + fn->setMetaness(FunctionNode::CCtor); + } + } else if ((kind == CXCursor_CXXMethod) && (name == QLatin1String("operator="))) { + if (argType.kind == CXType_RValueReference) + fn->setMetaness(FunctionNode::MAssign); + else if (argType.kind == CXType_LValueReference) + fn->setMetaness(FunctionNode::CAssign); + } + pvect.append(Parameter(adjustTypeName(fromCXString(clang_getTypeSpelling(argType))))); + } + if (pvect.size() > 0 && pvect.last().dataType().endsWith(QLatin1String("::QPrivateSignal"))) { + pvect.pop_back(); // remove the QPrivateSignal argument + fn->setPrivateSignal(); + } + if (clang_isFunctionTypeVariadic(funcType)) + pvect.append(Parameter(QStringLiteral("..."))); + fn->setParameters(pvect); + readParameterNamesAndAttributes(fn, cursor); + return CXChildVisit_Continue; + } +#if CINDEX_VERSION >= 36 + case CXCursor_FriendDecl: { + // Friend functions are declared in the enclosing namespace + Aggregate *ns = parent_; + while (ns && ns->isClass()) + ns = ns->parent(); + QScopedValueRollback<Aggregate *> setParent(parent_, ns); + // Visit the friend functions + return visitChildren(cursor); + } +#endif + case CXCursor_EnumDecl: { + if (findNodeForCursor(qdb_, cursor)) // Was already parsed, propably in another tu + return CXChildVisit_Continue; + QString enumTypeName = fromCXString(clang_getCursorSpelling(cursor)); + if (enumTypeName.isEmpty()) + enumTypeName = "anonymous"; + auto en = new EnumNode(parent_, enumTypeName); + en->setAccess(fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor))); + en->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor))); + // Enum values + visitChildrenLambda(cursor, [&](CXCursor cur) { + if (clang_getCursorKind(cur) != CXCursor_EnumConstantDecl) + return CXChildVisit_Continue; + + + QString value; + visitChildrenLambda(cur, [&](CXCursor cur) { + if (clang_isExpression(clang_getCursorKind(cur))) { + value = getSpelling(clang_getCursorExtent(cur)); + return CXChildVisit_Break; + } + return CXChildVisit_Continue; + }); + if (value.isEmpty()) { + QLatin1String hex("0x"); + if (!en->items().isEmpty() && en->items().last().value().startsWith(hex)) { + value = hex + QString::number(clang_getEnumConstantDeclValue(cur), 16); + } else { + value = QString::number(clang_getEnumConstantDeclValue(cur)); + } + } + + en->addItem(EnumItem(fromCXString(clang_getCursorSpelling(cur)), value)); + return CXChildVisit_Continue; + }); + return CXChildVisit_Continue; + } + case CXCursor_FieldDecl: + case CXCursor_VarDecl: { + if (findNodeForCursor(qdb_, cursor)) // Was already parsed, propably in another translation unit + return CXChildVisit_Continue; + auto access = fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor)); + auto var = new VariableNode(parent_, fromCXString(clang_getCursorSpelling(cursor))); + var->setAccess(access); + var->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor))); + var->setLeftType(fromCXString(clang_getTypeSpelling(clang_getCursorType(cursor)))); + var->setStatic(kind == CXCursor_VarDecl && parent_->isClass()); + return CXChildVisit_Continue; + } + case CXCursor_TypedefDecl: { + if (findNodeForCursor(qdb_, cursor)) // Was already parsed, propably in another translation unit + return CXChildVisit_Continue; + TypedefNode* td = new TypedefNode(parent_, fromCXString(clang_getCursorSpelling(cursor))); + td->setAccess(fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor))); + td->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor))); + // Search to see if this is a Q_DECLARE_FLAGS (if the type is QFlags<ENUM>) + visitChildrenLambda(cursor, [&](CXCursor cur) { + if (clang_getCursorKind(cur) != CXCursor_TemplateRef + || fromCXString(clang_getCursorSpelling(cur)) != QLatin1String("QFlags")) + return CXChildVisit_Continue; + // Found QFlags<XXX> + visitChildrenLambda(cursor, [&](CXCursor cur) { + if (clang_getCursorKind(cur) != CXCursor_TypeRef) + return CXChildVisit_Continue; + auto *en = findNodeForCursor(qdb_, clang_getTypeDeclaration(clang_getCursorType(cur))); + if (en && en->isEnumType()) + static_cast<EnumNode*>(en)->setFlagsType(td); + return CXChildVisit_Break; + }); + return CXChildVisit_Break; + }); + return CXChildVisit_Continue; + } + default: + if (clang_isDeclaration(kind) && parent_->isClass()) { + // maybe a static_assert (which is not exposed from the clang API) + QString spelling = getSpelling(clang_getCursorExtent(cursor)); + if (spelling.startsWith(QLatin1String("Q_PROPERTY")) + || spelling.startsWith(QLatin1String("QDOC_PROPERTY")) + || spelling.startsWith(QLatin1String("Q_OVERRIDE"))) { + parseProperty(spelling, fromCXSourceLocation(loc)); + } + } + return CXChildVisit_Continue; + } +} + +void ClangVisitor::readParameterNamesAndAttributes(FunctionNode* fn, CXCursor cursor) +{ + auto pvect = fn->parameters(); + // Visit the parameters and attributes + int i = 0; + visitChildrenLambda(cursor, [&](CXCursor cur) { + auto kind = clang_getCursorKind(cur); + if (kind == CXCursor_AnnotateAttr) { + QString annotation = fromCXString(clang_getCursorDisplayName(cur)); + if (annotation == QLatin1String("qt_slot")) { + fn->setMetaness(FunctionNode::Slot); + } else if (annotation == QLatin1String("qt_signal")) { + fn->setMetaness(FunctionNode::Signal); + } + } else if (kind == CXCursor_ParmDecl) { + if (i >= pvect.count()) + return CXChildVisit_Break; // Attributes comes before parameters so we can break. + QString name = fromCXString(clang_getCursorSpelling(cur)); + if (!name.isEmpty()) + pvect[i].setName(name); + // Find the default value + visitChildrenLambda(cur, [&](CXCursor cur) { + if (clang_isExpression(clang_getCursorKind(cur))) { + QString defaultValue = getSpelling(clang_getCursorExtent(cur)); + if (defaultValue.startsWith('=')) // In some cases, the = is part of the range. + defaultValue = defaultValue.midRef(1).trimmed().toString(); + if (defaultValue.isEmpty()) + defaultValue = QStringLiteral("..."); + pvect[i].setDefaultValue(defaultValue); + return CXChildVisit_Break; + } + return CXChildVisit_Continue; + }); + i++; + } + return CXChildVisit_Continue; + }); + fn->setParameters(pvect); +} + +void ClangVisitor::parseProperty(const QString& spelling, const Location& loc) +{ + int lpIdx = spelling.indexOf(QChar('(')); + int rpIdx = spelling.lastIndexOf(QChar(')')); + if (lpIdx <= 0 || rpIdx <= lpIdx) + return; + QString signature = spelling.mid(lpIdx + 1, rpIdx - lpIdx - 1); + signature = signature.simplified(); + QStringList part = signature.split(QChar(' ')); + if (part.size() < 2) + return; + QString type = part.at(0); + QString name = part.at(1); + if (name.at(0) == QChar('*')) { + type.append(QChar('*')); + name.remove(0,1); + } + auto *property = new PropertyNode(parent_, name); + property->setAccess(Node::Public); + property->setLocation(loc); + property->setDataType(type); + int i = 2; + while (i < part.size()) { + QString key = part.at(i++); + // Keywords with no associated values + if (key == "CONSTANT") { + property->setConstant(); + } else if (key == "FINAL") { + property->setFinal(); + } + if (i < part.size()) { + QString value = part.at(i++); + if (key == "READ") { + qdb_->addPropertyFunction(property, value, PropertyNode::Getter); + } else if (key == "WRITE") { + qdb_->addPropertyFunction(property, value, PropertyNode::Setter); + property->setWritable(true); + } else if (key == "STORED") { + property->setStored(value.toLower() == "true"); + } else if (key == "DESIGNABLE") { + QString v = value.toLower(); + if (v == "true") + property->setDesignable(true); + else if (v == "false") + property->setDesignable(false); + else { + property->setDesignable(false); + property->setRuntimeDesFunc(value); + } + } else if (key == "RESET") { + qdb_->addPropertyFunction(property, value, PropertyNode::Resetter); + } else if (key == "NOTIFY") { + qdb_->addPropertyFunction(property, value, PropertyNode::Notifier); + } else if (key == "REVISION") { + int revision; + bool ok; + revision = value.toInt(&ok); + if (ok) + property->setRevision(revision); + else + loc.warning(ClangCodeParser::tr("Invalid revision number: %1").arg(value)); + } else if (key == "SCRIPTABLE") { + QString v = value.toLower(); + if (v == "true") + property->setScriptable(true); + else if (v == "false") + property->setScriptable(false); + else { + property->setScriptable(false); + property->setRuntimeScrFunc(value); + } + } + } + } +} + +/*! + Given a comment at location \a loc, return a Node for this comment + \a nextCommentLoc is the location of the next comment so the declaration + must be inbetween. + Returns nullptr if no suitable declaration was found between the two comments. + */ +Node* ClangVisitor::nodeForCommentAtLocation(CXSourceLocation loc, CXSourceLocation nextCommentLoc) +{ + ClangVisitor::SimpleLoc docloc; + clang_getPresumedLocation(loc, nullptr, &docloc.line, &docloc.column); + auto decl_it = declMap_.upperBound(docloc); + if (decl_it == declMap_.end()) + return nullptr; + + unsigned int declLine = decl_it.key().line; + unsigned int nextCommentLine; + clang_getPresumedLocation(nextCommentLoc, nullptr, &nextCommentLine, nullptr); + if (nextCommentLine < declLine) + return nullptr; // there is another comment before the declaration, ignore it. + + // make sure the previous decl was finished. + if (decl_it != declMap_.begin()) { + CXSourceLocation prevDeclEnd = clang_getRangeEnd(clang_getCursorExtent(*(decl_it-1))); + unsigned int prevDeclLine; + clang_getPresumedLocation(prevDeclEnd, nullptr, &prevDeclLine, nullptr); + if (prevDeclLine >= docloc.line) { + // The previous declaration was still going. This is only valid if the previous + // declaration is a parent of the next declaration. + auto parent = clang_getCursorLexicalParent(*decl_it); + if (!clang_equalCursors(parent, *(decl_it-1))) + return nullptr; + } + } + auto *node = findNodeForCursor(qdb_, *decl_it); + // borrow the parameter name from the definition + if (node && node->isFunction()) + readParameterNamesAndAttributes(static_cast<FunctionNode*>(node), *decl_it); + return node; +} + +/*! + The destructor is trivial. + */ +ClangCodeParser::~ClangCodeParser() +{ + // nothing. +} + +/*! + Get the include paths from the qdoc configuration database + \a config. Call the initializeParser() in the base class. + Get the defines list from the qdocconf database. + */ +void ClangCodeParser::initializeParser(const Config &config) +{ + const auto args = config.getStringList(CONFIG_INCLUDEPATHS); + includePaths_.resize(args.size()); + std::transform(args.begin(), args.end(), includePaths_.begin(), + [](const QString &s) { return s.toUtf8(); }); + CppCodeParser::initializeParser(config); + pchFileDir_.reset(nullptr); + allHeaders_.clear(); + pchName_.clear(); + defines_.clear(); + QSet<QString> accepted; + { + const QStringList tmpDefines = config.getStringList(CONFIG_CLANGDEFINES); + for (const QString &def : tmpDefines) { + if (!accepted.contains(def)) { + QByteArray tmp("-D"); + tmp.append(def.toUtf8()); + defines_.append(tmp.constData()); + accepted.insert(def); + } + } + } + { + const QStringList tmpDefines = config.getStringList(CONFIG_DEFINES); + for (const QString &def : tmpDefines) { + if (!accepted.contains(def) && !def.contains(QChar('*'))) { + QByteArray tmp("-D"); + tmp.append(def.toUtf8()); + defines_.append(tmp.constData()); + accepted.insert(def); + } + } + } +} + +/*! + */ +void ClangCodeParser::terminateParser() +{ + CppCodeParser::terminateParser(); +} + +/*! + */ +QString ClangCodeParser::language() +{ + return "Clang"; +} + +/*! + Returns a list of extensions for header files. + */ +QStringList ClangCodeParser::headerFileNameFilter() +{ + return QStringList() << "*.ch" << "*.h" << "*.h++" << "*.hh" << "*.hpp" << "*.hxx"; +} + +/*! + Returns a list of extensions for source files, i.e. not + header files. + */ +QStringList ClangCodeParser::sourceFileNameFilter() +{ + return QStringList() << "*.c++" << "*.cc" << "*.cpp" << "*.cxx" << "*.mm"; +} + +/*! + Parse the C++ header file identified by \a filePath and add + the parsed contents to the database. The \a location is used + for reporting errors. + */ +void ClangCodeParser::parseHeaderFile(const Location & /*location*/, const QString &filePath) +{ + QFileInfo fi(filePath); + allHeaders_.insert(fi.canonicalFilePath()); +} + +static const char *defaultArgs_[] = { + "-std=c++14", + "-fPIC", + "-fno-exceptions", // Workaround for clang bug http://reviews.llvm.org/D17988 + "-DQ_QDOC", + "-DQT_DISABLE_DEPRECATED_BEFORE=0", + "-DQT_ANNOTATE_CLASS(type,...)=static_assert(sizeof(#__VA_ARGS__), #type);", + "-DQT_ANNOTATE_CLASS2(type,a1,a2)=static_assert(sizeof(#a1, #a2), #type);", + "-DQT_ANNOTATE_FUNCTION(a)=__attribute__((annotate(#a)))", + "-DQT_ANNOTATE_ACCESS_SPECIFIER(a)=__attribute__((annotate(#a)))", + "-Wno-constant-logical-operand", +#ifdef Q_OS_WIN + "-fms-compatibility-version=19", +#endif + "-I" CLANG_RESOURCE_DIR +}; + +/*! + Load the default arguments and the defines into \a args. + Clear \a args first. + */ +void ClangCodeParser::getDefaultArgs() +{ + args_.clear(); + args_.insert(args_.begin(), std::begin(defaultArgs_), std::end(defaultArgs_)); + // Add the defines from the qdocconf file. + for (const auto &p : qAsConst(defines_)) + args_.push_back(p.constData()); +} + +/*! + Load the include paths into \a moreArgs. + */ +void ClangCodeParser::getMoreArgs() +{ + if (includePaths_.isEmpty() || includePaths_.at(0) != QByteArray("-I")) { + /* + The include paths provided are inadequate. Make a list + of reasonable places to look for include files and use + that list instead. + */ + QList<QString> headers = allHeaders_.values(); + QString filePath = headers.at(0); + auto forest = qdb_->searchOrder(); + + QByteArray version = qdb_->version().toUtf8(); + QString basicIncludeDir = QDir::cleanPath(QString(Config::installDir + "/../include")); + moreArgs_ += "-I" + basicIncludeDir.toLatin1(); + moreArgs_ += "-I" + QDir::cleanPath(QString(filePath + "/../")).toLatin1(); + moreArgs_ += "-I" + QDir::cleanPath(QString(filePath + "/../../")).toLatin1(); + for (const auto &s : forest) { + QString module = basicIncludeDir +"/" + s->camelCaseModuleName(); + moreArgs_ += QString("-I" + module).toLatin1(); + moreArgs_ += QString("-I" + module + "/" + qdb_->version()).toLatin1(); + moreArgs_ += QString("-I" + module + "/" + qdb_->version() + "/" + module).toLatin1(); + } + for (int i=0; i<includePaths_.size(); ++i) { + if (!includePaths_.at(i).startsWith("-")) + moreArgs_ += "-I" + includePaths_.at(i); + else + moreArgs_ += includePaths_.at(i); + } + } + else { + moreArgs_ = includePaths_; + } +} + +/*! + Building the PCH must be possible when there are no .cpp + files, so it is moved here to its own member function, and + it is called after the list of header files is complete. + */ +void ClangCodeParser::buildPCH() +{ + if (!pchFileDir_) { + pchFileDir_.reset(new QTemporaryDir(QDir::tempPath() + QLatin1String("/qdoc_pch"))); + if (pchFileDir_->isValid()) { + //const QByteArray module = qdb_->primaryTreeRoot()->tree()->camelCaseModuleName().toUtf8(); + const QByteArray module = moduleHeader().toUtf8(); + QByteArray header; + QByteArray privateHeaderDir; + // Find the path to the module's header (e.g. QtGui/QtGui) to be used + // as pre-compiled header + for (const auto &p : qAsConst(includePaths_)) { + if (p.endsWith(module)) { + QByteArray candidate = p + "/" + module; + if (QFile::exists(QString::fromUtf8(candidate))) { + header = candidate; + break; + } + } + } + if (header.isEmpty()) { + for (const auto &p : qAsConst(includePaths_)) { + QByteArray candidate = p + "/" + module; + if (QFile::exists(QString::fromUtf8(candidate))) { + header = candidate; + break; + } + } + } + // Find the path to the module's private header directory (e.g. + // include/QtGui/5.8.0/QtGui/private) to use for including all + // the private headers in the PCH. + for (const auto &p : qAsConst(includePaths_)) { + if (p.endsWith(module)) { + QByteArray candidate = p + "/private"; + if (QFile::exists(QString::fromUtf8(candidate))) { + privateHeaderDir = candidate; + break; + } + } + } + if (header.isEmpty()) { + QByteArray installDocDir = Config::installDir.toUtf8(); + const QByteArray candidate = installDocDir + "/../include/" + module + "/" + module; + if (QFile::exists(QString::fromUtf8(candidate))) + header = candidate; + } + if (header.isEmpty()) { + qWarning() << "(qdoc) Could not find the module header in the include path for module" + << module << " (include paths: "<< includePaths_ << ")"; + } else { + args_.push_back("-xc++"); + CXTranslationUnit tu; + QString tmpHeader = pchFileDir_->path() + "/" + module; + if (QFile::copy(header, tmpHeader) && !privateHeaderDir.isEmpty()) { + privateHeaderDir = QDir::cleanPath(privateHeaderDir.constData()).toLatin1(); + const char *const headerPath = privateHeaderDir.constData(); + const QStringList pheaders = QDir(headerPath).entryList(); + QFile tmpHeaderFile(tmpHeader); + if (tmpHeaderFile.open(QIODevice::Text | QIODevice::Append)) { + for (const QString &phead : pheaders) { + if (phead.endsWith("_p.h")) { + QByteArray entry; + entry = "#include \""; + entry += headerPath; + entry += '/'; + entry += phead.toLatin1(); + entry += "\"\n"; + tmpHeaderFile.write(entry); + } + } + } + } + CXErrorCode err = clang_parseTranslationUnit2(index_, + tmpHeader.toLatin1().data(), + args_.data(), static_cast<int>(args_.size()), nullptr, 0, + flags_ | CXTranslationUnit_ForSerialization, &tu); + if (!err && tu) { + pchName_ = pchFileDir_->path().toUtf8() + "/" + module + ".pch"; + auto error = clang_saveTranslationUnit(tu, pchName_.constData(), clang_defaultSaveOptions(tu)); + if (error) { + qWarning() << "(qdoc) Could not save PCH file for " << module << error; + pchName_.clear(); + } + // Visit the header now, as token from pre-compiled header won't be visited later + CXCursor cur = clang_getTranslationUnitCursor(tu); + ClangVisitor visitor(qdb_, allHeaders_); + visitor.visitChildren(cur); + + clang_disposeTranslationUnit(tu); + } else { + pchFileDir_->remove(); + qWarning() << "(qdoc) Could not create PCH file for " + << tmpHeader + << " error code:" << err; + } + args_.pop_back(); // remove the "-xc++"; + } + } + qdb_->resolveInheritance(); + } +} + +/*! + Precompile the header files for the current module. + */ +void ClangCodeParser::precompileHeaders() +{ + getDefaultArgs(); + getMoreArgs(); + for (const auto &p : qAsConst(moreArgs_)) + args_.push_back(p.constData()); + + flags_ = (CXTranslationUnit_Flags) (CXTranslationUnit_Incomplete | CXTranslationUnit_SkipFunctionBodies | CXTranslationUnit_KeepGoing); + index_ = clang_createIndex(1, 0); + buildPCH(); + clang_disposeIndex(index_); +} + +/*! + Get ready to parse the C++ cpp file identified by \a filePath + and add its parsed contents to the database. \a location is + used for reporting errors. + + Call matchDocsAndStuff() to do all the parsing and tree building. + */ +void ClangCodeParser::parseSourceFile(const Location& /*location*/, const QString& filePath) +{ + currentFile_ = filePath; + flags_ = (CXTranslationUnit_Flags) (CXTranslationUnit_Incomplete | CXTranslationUnit_SkipFunctionBodies | CXTranslationUnit_KeepGoing); + index_ = clang_createIndex(1, 0); + + getDefaultArgs(); + if (!pchName_.isEmpty() && !filePath.endsWith(".mm")) { + args_.push_back("-w"); + args_.push_back("-include-pch"); + args_.push_back(pchName_.constData()); + } + for (const auto &p : qAsConst(moreArgs_)) + args_.push_back(p.constData()); + + CXTranslationUnit tu; + CXErrorCode err = clang_parseTranslationUnit2(index_, filePath.toLocal8Bit(), args_.data(), + static_cast<int>(args_.size()), nullptr, 0, flags_, &tu); + if (err || !tu) { + qWarning() << "(qdoc) Could not parse source file" << filePath << " error code:" << err; + clang_disposeIndex(index_); + return; + } + + CXCursor cur = clang_getTranslationUnitCursor(tu); + ClangVisitor visitor(qdb_, allHeaders_); + visitor.visitChildren(cur); + + CXToken *tokens; + unsigned int numTokens = 0; + clang_tokenize(tu, clang_getCursorExtent(cur), &tokens, &numTokens); + + const QSet<QString>& topicCommandsAllowed = topicCommands(); + const QSet<QString>& otherMetacommandsAllowed = otherMetaCommands(); + const QSet<QString>& metacommandsAllowed = topicCommandsAllowed + otherMetacommandsAllowed; + + for (unsigned int i = 0; i < numTokens; ++i) { + if (clang_getTokenKind(tokens[i]) != CXToken_Comment) + continue; + QString comment = fromCXString(clang_getTokenSpelling(tu, tokens[i])); + if (!comment.startsWith("/*!")) + continue; + + auto loc = fromCXSourceLocation(clang_getTokenLocation(tu, tokens[i])); + auto end_loc = fromCXSourceLocation(clang_getRangeEnd(clang_getTokenExtent(tu, tokens[i]))); + Doc::trimCStyleComment(loc,comment); + Doc doc(loc, end_loc, comment, metacommandsAllowed, topicCommandsAllowed); + + + /* + * Doc parses the comment. + */ + + QString topic; + bool isQmlPropertyTopic = false; + bool isJsPropertyTopic = false; + + const TopicList& topics = doc.topicsUsed(); + if (!topics.isEmpty()) { + topic = topics[0].topic; + if (topic.startsWith("qml")) { + if ((topic == COMMAND_QMLPROPERTY) || + (topic == COMMAND_QMLPROPERTYGROUP) || + (topic == COMMAND_QMLATTACHEDPROPERTY)) { + isQmlPropertyTopic = true; + } + } else if (topic.startsWith("js")) { + if ((topic == COMMAND_JSPROPERTY) || + (topic == COMMAND_JSPROPERTYGROUP) || + (topic == COMMAND_JSATTACHEDPROPERTY)) { + isJsPropertyTopic = true; + } + } + } + NodeList nodes; + DocList docs; + + if (topic.isEmpty()) { + CXSourceLocation commentLoc = clang_getTokenLocation(tu, tokens[i]); + Node *n = nullptr; + if (i + 1 < numTokens) { + // Try to find the next declaration. + CXSourceLocation nextCommentLoc = commentLoc; + while (i + 2 < numTokens && clang_getTokenKind(tokens[i+1]) != CXToken_Comment) + ++i; // already skip all the tokens that are not comments + nextCommentLoc = clang_getTokenLocation(tu, tokens[i+1]); + n = visitor.nodeForCommentAtLocation(commentLoc, nextCommentLoc); + } + + if (n) { + nodes.append(n); + docs.append(doc); + } else if (CodeParser::isWorthWarningAbout(doc)) { + doc.location().warning(tr("Cannot tie this documentation to anything"), + tr("I found a /*! ... */ comment, but there was no " + "topic command (e.g., '\\%1', '\\%2') in the " + "comment and no function definition following " + "the comment.") + .arg(COMMAND_FN).arg(COMMAND_PAGE)); + } + } else if (isQmlPropertyTopic || isJsPropertyTopic) { + Doc nodeDoc = doc; + processQmlProperties(nodeDoc, nodes, docs, isJsPropertyTopic); + } else { + ArgList args; + const QSet<QString>& topicCommandsUsed = topicCommandsAllowed & doc.metaCommandsUsed(); + if (topicCommandsUsed.count() > 0) { + topic = *topicCommandsUsed.constBegin(); + args = doc.metaCommandArgs(topic); + } + if (topicCommandsUsed.count() > 1) { + QString topicList; + QSet<QString>::ConstIterator t = topicCommandsUsed.constBegin(); + while (t != topicCommandsUsed.constEnd()) { + topicList += " \\" + *t + QLatin1Char(','); + ++t; + } + topicList[topicList.lastIndexOf(',')] = '.'; + int i = topicList.lastIndexOf(','); + topicList[i] = ' '; + topicList.insert(i+1,"and"); + doc.location().warning(tr("Multiple topic commands found in comment: %1").arg(topicList)); + } + ArgList::ConstIterator a = args.constBegin(); + Node *node = 0; + SharedCommentNode* scn = 0; + int count = args.size(); + while (a != args.constEnd()) { + Doc nodeDoc = doc; + if ((count > 1) && (topic == COMMAND_FN)) { + node = parseFnArg(doc.location(), a->first); + if (node != 0) { + if (scn == 0) { + scn = new SharedCommentNode(node->parent(), count); + nodes.append(scn); + docs.append(nodeDoc); + } + scn->append(node); + node->setCollectiveNode(scn); + } + } + else { + if (topic == COMMAND_FN) { + node = parseFnArg(doc.location(), a->first); + } else if (topic == COMMAND_MACRO) { + node = parseMacroArg(doc.location(), a->first); + } else if (topic == COMMAND_QMLSIGNAL || + topic == COMMAND_QMLMETHOD || + topic == COMMAND_QMLATTACHEDSIGNAL || + topic == COMMAND_QMLATTACHEDMETHOD || + topic == COMMAND_JSSIGNAL || + topic == COMMAND_JSMETHOD || + topic == COMMAND_JSATTACHEDSIGNAL || + topic == COMMAND_JSATTACHEDMETHOD) { + node = parseOtherFuncArg(topic, doc.location(), a->first); + } else { + node = processTopicCommand(nodeDoc, topic, *a); + } + if (node != 0) { + nodes.append(node); + docs.append(nodeDoc); + } + } + ++a; + } + } + + NodeList::Iterator n = nodes.begin(); + QList<Doc>::Iterator d = docs.begin(); + while (n != nodes.end()) { + processOtherMetaCommands(*d, *n); + (*n)->setDoc(*d); + checkModuleInclusion(*n); + if ((*n)->isAggregate() && ((Aggregate *)*n)->includes().isEmpty()) { + Aggregate *m = static_cast<Aggregate *>(*n); + while (m->parent() && m->physicalModuleName().isEmpty()) { + m = m->parent(); + } + if (m == *n) + ((Aggregate *)*n)->addInclude((*n)->name()); + else + ((Aggregate *)*n)->setIncludes(m->includes()); + } + ++d; + ++n; + } + } + + clang_disposeTokens(tu, tokens, numTokens); + clang_disposeTranslationUnit(tu); + clang_disposeIndex(index_); +} + +/*! + */ +Node* ClangCodeParser::parseOtherFuncArg(const QString& topic, + const Location& location, + const QString& funcArg) +{ + if (Generator::preparing() && !Generator::singleExec()) + return 0; + + QString funcName; + QString returnType; + + int leftParen = funcArg.indexOf(QChar('(')); + if (leftParen > 0) + funcName = funcArg.left(leftParen); + else + funcName = funcArg; + int firstBlank = funcName.indexOf(QChar(' ')); + if (firstBlank > 0) { + returnType = funcName.left(firstBlank); + funcName = funcName.right(funcName.length() - firstBlank - 1); + } + + QStringList colonSplit(funcName.split("::")); + if (colonSplit.size() < 2) { + QString msg = "Unrecognizable QML module/component qualifier for " + funcArg; + location.warning(tr(msg.toLatin1().data())); + return 0; + } + QString moduleName; + QString elementName; + if (colonSplit.size() > 2) { + moduleName = colonSplit[0]; + elementName = colonSplit[1]; + } else { + elementName = colonSplit[0]; + } + funcName = colonSplit.last(); + + Aggregate *aggregate = qdb_->findQmlType(moduleName, elementName); + bool attached = false; + if (!aggregate) + aggregate = qdb_->findQmlBasicType(moduleName, elementName); + if (!aggregate) + return 0; + + Node::NodeType nodeType = Node::QmlMethod; + if (topic == COMMAND_QMLSIGNAL || topic == COMMAND_JSSIGNAL) { + nodeType = Node::QmlSignal; + } else if (topic == COMMAND_QMLATTACHEDSIGNAL || topic == COMMAND_JSATTACHEDSIGNAL) { + nodeType = Node::QmlSignal; + attached = true; + } else if (topic == COMMAND_QMLATTACHEDMETHOD || topic == COMMAND_JSATTACHEDMETHOD) { + attached = true; + } else { + Q_ASSERT(topic == COMMAND_QMLMETHOD || topic == COMMAND_JSMETHOD); + } + + QString params; + QStringList leftParenSplit = funcArg.split('('); + if (leftParenSplit.size() > 1) { + QStringList rightParenSplit = leftParenSplit[1].split(')'); + if (rightParenSplit.size() > 0) + params = rightParenSplit[0]; + } + FunctionNode *funcNode = static_cast<FunctionNode*>(new FunctionNode(nodeType, aggregate, funcName, attached)); + funcNode->setAccess(Node::Public); + funcNode->setLocation(location); + funcNode->setReturnType(returnType); + funcNode->setParameters(params); + return funcNode; +} + +/*! + Parse the macroArg ad hoc, without using clang and without + using the old qdoc C++ parser. + */ +Node* ClangCodeParser::parseMacroArg(const Location& location, const QString& macroArg) +{ + if (Generator::preparing() && !Generator::singleExec()) + return 0; + FunctionNode* newMacroNode = 0; + QStringList leftParenSplit = macroArg.split('('); + if (leftParenSplit.size() > 0) { + QString macroName; + FunctionNode* oldMacroNode = 0; + QStringList blankSplit = leftParenSplit[0].split(' '); + if (blankSplit.size() > 0) { + macroName = blankSplit.last(); + oldMacroNode = static_cast<FunctionNode*>(qdb_->findMacroNode(macroName)); + } + QString returnType; + if (blankSplit.size() > 1) { + blankSplit.removeLast(); + returnType = blankSplit.join(' '); + } + QString params; + if (leftParenSplit.size() > 1) { + const QString &afterParen = leftParenSplit.at(1); + int rightParen = afterParen.indexOf(')'); + if (rightParen >= 0) + params = afterParen.left(rightParen); + } + int i = 0; + while (i < macroName.length() && !macroName.at(i).isLetter()) + i++; + if (i > 0) { + returnType += QChar(' ') + macroName.left(i); + macroName = macroName.mid(i); + } + newMacroNode = static_cast<FunctionNode*>(new FunctionNode(qdb_->primaryTreeRoot(), macroName)); + newMacroNode->setAccess(Node::Public); + newMacroNode->setLocation(location); + if (params.isEmpty()) + newMacroNode->setMetaness(FunctionNode::MacroWithoutParams); + else + newMacroNode->setMetaness(FunctionNode::MacroWithParams); + newMacroNode->setReturnType(returnType); + newMacroNode->setParameters(params); + if (oldMacroNode && newMacroNode->compare(oldMacroNode)) { + location.warning(ClangCodeParser::tr("\\macro %1 documented more than once").arg(macroArg)); + oldMacroNode->doc().location().warning(tr("(The previous doc is here)")); + } + } + return newMacroNode; + } + +/*! + Use clang to parse the function signature from a function + command. \a location is used for reporting errors. \a fnArg + is the string to parse. It is always a function decl. + */ +Node* ClangCodeParser::parseFnArg(const Location& location, const QString& fnArg) +{ + Node* fnNode = 0; + if (Generator::preparing() && !Generator::singleExec()) + return fnNode; + /* + If the \fn command begins with a tag, then don't try to + parse the \fn command with clang. Use the tag to search + for the correct function node. It is an error if it can + not be found. Return 0 in that case. + */ + if (fnArg.startsWith('[')) { + int end = fnArg.indexOf(QChar(']', 0)); + if (end > 1) { + QString tag = fnArg.left(end + 1); + fnNode = qdb_->findFunctionNodeForTag(tag); + if (!fnNode) { + location.error(ClangCodeParser::tr("tag \\fn %1 not used in any include file in current module").arg(tag)); + } else { + /* + The function node was found. Use the formal + parameter names from the \FN command, because + they will be the names used in the documentation. + */ + FunctionNode* fn = static_cast<FunctionNode*>(fnNode); + QStringList leftParenSplit = fnArg.split('('); + if (leftParenSplit.size() > 1) { + QStringList rightParenSplit = leftParenSplit[1].split(')'); + if (rightParenSplit.size() > 0) { + QString params = rightParenSplit[0]; + if (!params.isEmpty()) { + QStringList commaSplit = params.split(','); + QVector<Parameter>& pvect = fn->parameters(); + if (pvect.size() == commaSplit.size()) { + for (int i = 0; i < pvect.size(); ++i) { + QStringList blankSplit = commaSplit[i].split(' '); + if (blankSplit.size() > 0) { + QString pName = blankSplit.last(); + int j = 0; + while (j < pName.length() && !pName.at(i).isLetter()) + j++; + if (j > 0) + pName = pName.mid(j); + if (!pName.isEmpty() && pName != pvect[i].name()) + pvect[i].setName(pName); + } + } + } + } + } + } + } + } + return fnNode; + } + CXTranslationUnit_Flags flags = (CXTranslationUnit_Flags) (CXTranslationUnit_Incomplete | + CXTranslationUnit_SkipFunctionBodies | + CXTranslationUnit_KeepGoing); + CXIndex index = clang_createIndex(1, 0); + + std::vector<const char *> args(std::begin(defaultArgs_), std::end(defaultArgs_)); + // Add the defines from the qdocconf file. + for (const auto &p : qAsConst(defines_)) + args.push_back(p.constData()); + if (!pchName_.isEmpty()) { + args.push_back("-w"); + args.push_back("-include-pch"); + args.push_back(pchName_.constData()); + } + CXTranslationUnit tu; + QByteArray fn = fnArg.toUtf8(); + if (!fn.endsWith(";")) + fn += "{ }"; + const char *dummyFileName = "/fn_dummyfile.cpp"; + CXUnsavedFile unsavedFile { dummyFileName, fn.constData(), + static_cast<unsigned long>(fn.size()) }; + CXErrorCode err = clang_parseTranslationUnit2(index, dummyFileName, + args.data(), + args.size(), + &unsavedFile, + 1, + flags, + &tu); + + if (err || !tu) { + location.error(ClangCodeParser::tr("clang could not parse \\fn %1").arg(fnArg)); + clang_disposeTranslationUnit(tu); + clang_disposeIndex(index); + return fnNode; + } else { + /* + Always visit the tu if one is constructed, because + it might be possible to find the correct node, even + if clang detected diagnostics. Only bother to report + the diagnostics if they stop us finding the node. + */ + CXCursor cur = clang_getTranslationUnitCursor(tu); + ClangVisitor visitor(qdb_, allHeaders_); + bool ignoreSignature = false; + visitor.visitFnArg(cur, &fnNode, ignoreSignature); + if (fnNode == 0) { + unsigned diagnosticCount = clang_getNumDiagnostics(tu); + if (diagnosticCount > 0) { + location.warning(ClangCodeParser::tr("clang found diagnostics parsing \\fn %1").arg(fnArg)); + for (unsigned i = 0; i < diagnosticCount; ++i) { + CXDiagnostic diagnostic = clang_getDiagnostic(tu, i); + location.report(tr(" %1").arg(fromCXString(clang_formatDiagnostic(diagnostic, 0)))); + } + } + } + } + clang_disposeTranslationUnit(tu); + clang_disposeIndex(index); + return fnNode; +} + +QT_END_NAMESPACE diff --git a/src/qdoc/clangcodeparser.h b/src/qdoc/clangcodeparser.h new file mode 100644 index 000000000..7d9cf31cf --- /dev/null +++ b/src/qdoc/clangcodeparser.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef CLANGCODEPARSER_H +#define CLANGCODEPARSER_H + +#include <QTemporaryDir> + +#include "cppcodeparser.h" + +QT_BEGIN_NAMESPACE + +class ClangCodeParser : public CppCodeParser +{ + Q_DECLARE_TR_FUNCTIONS(QDoc::ClangCodeParser) + +public: + ~ClangCodeParser(); + + virtual void initializeParser(const Config& config) Q_DECL_OVERRIDE; + virtual void terminateParser() Q_DECL_OVERRIDE; + virtual QString language() Q_DECL_OVERRIDE; + virtual QStringList headerFileNameFilter() Q_DECL_OVERRIDE; + virtual QStringList sourceFileNameFilter() Q_DECL_OVERRIDE; + virtual void parseHeaderFile(const Location& location, const QString& filePath) Q_DECL_OVERRIDE; + virtual void parseSourceFile(const Location& location, const QString& filePath) Q_DECL_OVERRIDE; + virtual void precompileHeaders() Q_DECL_OVERRIDE; + virtual Node *parseFnArg(const Location &location, const QString &fnArg) Q_DECL_OVERRIDE; + virtual Node *parseMacroArg(const Location &location, const QString ¯oArg) Q_DECL_OVERRIDE; + virtual Node *parseOtherFuncArg(const QString &topic, + const Location &location, + const QString &funcArg) Q_DECL_OVERRIDE; + + private: + void getDefaultArgs(); + void getMoreArgs(); + void buildPCH(); + +private: + QSet<QString> allHeaders_; + QVector<QByteArray> includePaths_; + QScopedPointer<QTemporaryDir> pchFileDir_; + QByteArray pchName_; + QVector<QByteArray> defines_; + std::vector<const char *> args_; + QVector<QByteArray> moreArgs_; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/qdoc/codechunk.cpp b/src/qdoc/codechunk.cpp index e3b130da1..36865f782 100644 --- a/src/qdoc/codechunk.cpp +++ b/src/qdoc/codechunk.cpp @@ -37,7 +37,7 @@ QT_BEGIN_NAMESPACE -enum { Other, Alnum, Gizmo, Comma, LParen, RParen, RAngle, Colon }; +enum { Other, Alnum, Gizmo, Comma, LBrace, RBrace, RAngle, Colon, Paren }; // entries 128 and above are Other static const int charCategory[256] = { @@ -48,7 +48,7 @@ static const int charCategory[256] = { // ! " # $ % & ' Other, Other, Other, Other, Other, Gizmo, Gizmo, Other, // ( ) * + , - . / - LParen, RParen, Gizmo, Gizmo, Comma, Other, Other, Gizmo, + Paren, Paren, Gizmo, Gizmo, Comma, Other, Other, Gizmo, // 0 1 2 3 4 5 6 7 Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, // 8 9 : ; < = > ? @@ -68,19 +68,20 @@ static const int charCategory[256] = { // p q r s t u v w Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, // x y z { | } ~ - Alnum, Alnum, Alnum, LParen, Gizmo, RParen, Other, Other + Alnum, Alnum, Alnum, LBrace, Gizmo, RBrace, Other, Other }; -static const bool needSpace[8][8] = { - /* [ a + , ( ) > : */ - /* [ */ { false, false, false, false, false, true, false, false }, - /* a */ { false, true, true, false, false, true, false, false }, - /* + */ { false, true, false, false, false, true, false, true }, - /* , */ { true, true, true, true, true, true, true, true }, - /* ( */ { true, true, true, false, true, false, true, true }, - /* ) */ { true, true, true, false, true, true, true, true }, - /* > */ { true, true, true, false, true, true, true, false }, - /* : */ { false, false, true, true, true, true, true, false } +static const bool needSpace[9][9] = { + /* [ a + , { } > : ) */ + /* [ */ { false, false, false, false, false, true, false, false, false }, + /* a */ { false, true, true, false, false, true, false, false, false }, + /* + */ { false, true, false, false, false, true, false, true, false }, + /* , */ { true, true, true, true, true, true, true, true, false }, + /* { */ { false, false, false, false, false, false, false, false, false }, + /* } */ { false, false, false, false, false, false, false, false, false }, + /* > */ { true, true, true, false, true, true, true, false, false }, + /* : */ { false, false, true, true, true, true, true, false, false }, + /* ( */ { false, false, false, false, false, false, false, false, false }, }; static int category( QChar ch ) diff --git a/src/qdoc/codeparser.cpp b/src/qdoc/codeparser.cpp index 2b8e28f91..c344ad763 100644 --- a/src/qdoc/codeparser.cpp +++ b/src/qdoc/codeparser.cpp @@ -112,11 +112,6 @@ void CodeParser::parseHeaderFile(const Location& location, const QString& filePa parseSourceFile(location, filePath); } -void CodeParser::doneParsingHeaderFiles() -{ - doneParsingSourceFiles(); -} - /*! All the code parsers in the static list are initialized here, after the qdoc configuration variables have been set. @@ -359,6 +354,21 @@ void CodeParser::setLink(Node* node, Node::LinkType linkType, const QString& arg } /*! + \brief Test for whether a doc comment warrants warnings. + + Returns true if qdoc should report that it has found something + wrong with the qdoc comment in \a doc. Sometimes, qdoc should + not report the warning, for example, when the comment contains + the \c internal command, which normally means qdoc will not use + the comment in the documentation anyway, so there is no point + in reporting warnings about it. + */ +bool CodeParser::isWorthWarningAbout(const Doc &doc) +{ + return (showInternal_ || !doc.metaCommandsUsed().contains(QStringLiteral("internal"))); +} + +/*! Returns \c true if the file being parsed is a .h file. */ bool CodeParser::isParsingH() const diff --git a/src/qdoc/codeparser.h b/src/qdoc/codeparser.h index cd24987c6..6659e3eed 100644 --- a/src/qdoc/codeparser.h +++ b/src/qdoc/codeparser.h @@ -53,13 +53,17 @@ public: virtual QStringList sourceFileNameFilter() = 0; virtual void parseHeaderFile(const Location& location, const QString& filePath); virtual void parseSourceFile(const Location& location, const QString& filePath) = 0; - virtual void doneParsingHeaderFiles(); - virtual void doneParsingSourceFiles() = 0; + virtual void precompileHeaders() { } + virtual Node *parseFnArg(const Location &, const QString &) { return 0; } + virtual Node *parseMacroArg(const Location &, const QString &) { return 0; } + virtual Node *parseOtherFuncArg(const QString &, const Location &, const QString &) { return 0; } bool isParsingH() const; bool isParsingCpp() const; bool isParsingQdoc() const; const QString& currentFile() const { return currentFile_; } + const QString& moduleHeader() const { return moduleHeader_; } + void setModuleHeader(const QString& t) { moduleHeader_ = t; } void checkModuleInclusion(Node* n); static void initialize(const Config& config); @@ -68,6 +72,7 @@ public: static CodeParser *parserForHeaderFile(const QString &filePath); static CodeParser *parserForSourceFile(const QString &filePath); static void setLink(Node* node, Node::LinkType linkType, const QString& arg); + static bool isWorthWarningAbout(const Doc &doc); protected: const QSet<QString>& commonMetaCommands(); @@ -78,6 +83,7 @@ protected: static void extractPageLinkAndDesc(const QString& arg, QString* link, QString* desc); + QString moduleHeader_; QString currentFile_; QDocDatabase* qdb_; diff --git a/src/qdoc/config.cpp b/src/qdoc/config.cpp index e116b24fb..4df13f7a8 100644 --- a/src/qdoc/config.cpp +++ b/src/qdoc/config.cpp @@ -47,6 +47,7 @@ QString ConfigStrings::AUTOLINKERRORS = QStringLiteral("autolinkerrors"); QString ConfigStrings::BASE = QStringLiteral("base"); QString ConfigStrings::BASEDIR = QStringLiteral("basedir"); QString ConfigStrings::BUILDVERSION = QStringLiteral("buildversion"); +QString ConfigStrings::CLANGDEFINES = QStringLiteral("clangdefines"); QString ConfigStrings::CODEINDENT = QStringLiteral("codeindent"); QString ConfigStrings::CODEPREFIX = QStringLiteral("codeprefix"); QString ConfigStrings::CODESUFFIX = QStringLiteral("codesuffix"); @@ -75,11 +76,13 @@ QString ConfigStrings::IGNOREDIRECTIVES = QStringLiteral("ignoredirectives"); QString ConfigStrings::IGNORETOKENS = QStringLiteral("ignoretokens"); QString ConfigStrings::IMAGEDIRS = QStringLiteral("imagedirs"); QString ConfigStrings::IMAGES = QStringLiteral("images"); +QString ConfigStrings::INCLUDEPATHS = QStringLiteral("includepaths"); QString ConfigStrings::INDEXES = QStringLiteral("indexes"); QString ConfigStrings::LANDINGPAGE = QStringLiteral("landingpage"); QString ConfigStrings::LANGUAGE = QStringLiteral("language"); QString ConfigStrings::MACRO = QStringLiteral("macro"); QString ConfigStrings::MANIFESTMETA = QStringLiteral("manifestmeta"); +QString ConfigStrings::MODULEHEADER = QStringLiteral("moduleheader"); QString ConfigStrings::NATURALLANGUAGE = QStringLiteral("naturallanguage"); QString ConfigStrings::NAVIGATION = QStringLiteral("navigation"); QString ConfigStrings::NOLINKERRORS = QStringLiteral("nolinkerrors"); diff --git a/src/qdoc/config.h b/src/qdoc/config.h index f4781eb10..71dbb658a 100644 --- a/src/qdoc/config.h +++ b/src/qdoc/config.h @@ -160,6 +160,7 @@ struct ConfigStrings static QString BASE; static QString BASEDIR; static QString BUILDVERSION; + static QString CLANGDEFINES; static QString CODEINDENT; static QString CODEPREFIX; static QString CODESUFFIX; @@ -188,11 +189,13 @@ struct ConfigStrings static QString IGNORETOKENS; static QString IMAGEDIRS; static QString IMAGES; + static QString INCLUDEPATHS; static QString INDEXES; static QString LANDINGPAGE; static QString LANGUAGE; static QString MACRO; static QString MANIFESTMETA; + static QString MODULEHEADER; static QString NATURALLANGUAGE; static QString NAVIGATION; static QString NOLINKERRORS; @@ -241,6 +244,7 @@ struct ConfigStrings #define CONFIG_BASE ConfigStrings::BASE #define CONFIG_BASEDIR ConfigStrings::BASEDIR #define CONFIG_BUILDVERSION ConfigStrings::BUILDVERSION +#define CONFIG_CLANGDEFINES ConfigStrings::CLANGDEFINES #define CONFIG_CODEINDENT ConfigStrings::CODEINDENT #define CONFIG_CODEPREFIX ConfigStrings::CODEPREFIX #define CONFIG_CODESUFFIX ConfigStrings::CODESUFFIX @@ -269,11 +273,13 @@ struct ConfigStrings #define CONFIG_IGNORETOKENS ConfigStrings::IGNORETOKENS #define CONFIG_IMAGEDIRS ConfigStrings::IMAGEDIRS #define CONFIG_IMAGES ConfigStrings::IMAGES +#define CONFIG_INCLUDEPATHS ConfigStrings::INCLUDEPATHS #define CONFIG_INDEXES ConfigStrings::INDEXES #define CONFIG_LANDINGPAGE ConfigStrings::LANDINGPAGE #define CONFIG_LANGUAGE ConfigStrings::LANGUAGE #define CONFIG_MACRO ConfigStrings::MACRO #define CONFIG_MANIFESTMETA ConfigStrings::MANIFESTMETA +#define CONFIG_MODULEHEADER ConfigStrings::MODULEHEADER #define CONFIG_NATURALLANGUAGE ConfigStrings::NATURALLANGUAGE #define CONFIG_NAVIGATION ConfigStrings::NAVIGATION #define CONFIG_NOLINKERRORS ConfigStrings::NOLINKERRORS diff --git a/src/qdoc/cppcodemarker.cpp b/src/qdoc/cppcodemarker.cpp index 40a3e8b81..8488260fd 100644 --- a/src/qdoc/cppcodemarker.cpp +++ b/src/qdoc/cppcodemarker.cpp @@ -163,7 +163,6 @@ QString CppCodeMarker::markedUpSynopsis(const Node *node, const QString ¶mName = hasName ? (*p).name() : (*p).dataType(); if (style != Subpage || !hasName) synopsis += "<@param>" + protect(paramName) + "</@param>"; - synopsis += protect((*p).rightType()); if (style != Subpage && !(*p).defaultValue().isEmpty()) synopsis += " = " + protect((*p).defaultValue()); ++p; @@ -185,14 +184,24 @@ QString CppCodeMarker::markedUpSynopsis(const Node *node, synopsis.append(" = 0"); else if (func->isDeleted()) synopsis.append(" = delete"); - else if (func->isDefaulted()) + else if (func->isImplicit() || func->isDefaulted()) synopsis.append(" = default"); + if (func->isRef()) + synopsis.append(" &"); + else if (func->isRefRef()) + synopsis.append(" &&"); } else if (style == Subpage) { if (!func->returnType().isEmpty() && func->returnType() != "void") synopsis += " : " + typified(func->returnType()); } else { + if (func->isRef()) + synopsis.append(" &"); + else if (func->isRefRef()) + synopsis.append(" &&"); + if (func->isImplicit() || func->isDefaulted()) + synopsis.append(" = default"); QStringList bracketed; if (func->isStatic()) { bracketed += "static"; @@ -349,7 +358,6 @@ QString CppCodeMarker::markedUpQmlItem(const Node* node, bool summary) synopsis += typified((*p).dataType(), true); const QString ¶mName = hasName ? (*p).name() : (*p).dataType(); synopsis += "<@param>" + protect(paramName) + "</@param>"; - synopsis += protect((*p).rightType()); ++p; } } @@ -381,7 +389,7 @@ QString CppCodeMarker::markedUpQmlItem(const Node* node, bool summary) QString CppCodeMarker::markedUpName(const Node *node) { QString name = linkTag(node, taggedNode(node)); - if (node->type() == Node::Function) + if (node->isFunction() && !node->isMacro()) name += "()"; return name; } @@ -582,7 +590,7 @@ QList<Section> CppCodeMarker::sections(const Aggregate *inner, insert(publicFunctions, *c, style, status); } } - else { + else if ((*c)->type() != Node::SharedComment) { insert(publicTypes, *c, style, status); } break; @@ -694,8 +702,15 @@ QList<Section> CppCodeMarker::sections(const Aggregate *inner, } else if ((*c)->type() == Node::Function) { FunctionNode *function = static_cast<FunctionNode *>(*c); - if (!function->hasAssociatedProperties() || !function->doc().isEmpty()) - insert(memberFunctions, function, style, status); + if (!function->isInCollective()) { + if (!function->hasAssociatedProperties() || !function->doc().isEmpty()) + insert(memberFunctions, function, style, status); + } + } + else if ((*c)->type() == Node::SharedComment) { + SharedCommentNode *scn = static_cast<SharedCommentNode *>(*c); + if (!scn->doc().isEmpty()) + insert(memberFunctions, scn, style, status); } ++c; } @@ -918,7 +933,7 @@ QString CppCodeMarker::addMarkUp(const QString &in, readChar(); - while (ch != EOF) { + while (ch != QChar(EOF)) { QString tag; bool target = false; @@ -977,7 +992,7 @@ QString CppCodeMarker::addMarkUp(const QString &in, finish = i; readChar(); - while (ch != EOF && ch != '"') { + while (ch != QChar(EOF) && ch != '"') { if (ch == '\\') readChar(); readChar(); @@ -989,7 +1004,7 @@ QString CppCodeMarker::addMarkUp(const QString &in, case '#': finish = i; readChar(); - while (ch != EOF && ch != '\n') { + while (ch != QChar(EOF) && ch != '\n') { if (ch == '\\') readChar(); finish = i; @@ -1001,7 +1016,7 @@ QString CppCodeMarker::addMarkUp(const QString &in, finish = i; readChar(); - while (ch != EOF && ch != '\'') { + while (ch != QChar(EOF) && ch != '\'') { if (ch == '\\') readChar(); readChar(); @@ -1036,7 +1051,7 @@ QString CppCodeMarker::addMarkUp(const QString &in, do { finish = i; readChar(); - } while (ch != EOF && ch != '\n'); + } while (ch != QChar(EOF) && ch != '\n'); tag = QStringLiteral("comment"); } else if (ch == '*') { bool metAster = false; @@ -1046,7 +1061,7 @@ QString CppCodeMarker::addMarkUp(const QString &in, readChar(); while (!metAsterSlash) { - if (ch == EOF) + if (ch == QChar(EOF)) break; if (ch == '*') diff --git a/src/qdoc/cppcodeparser.cpp b/src/qdoc/cppcodeparser.cpp index 29e1c0fe6..c81a385a3 100644 --- a/src/qdoc/cppcodeparser.cpp +++ b/src/qdoc/cppcodeparser.cpp @@ -46,12 +46,10 @@ QT_BEGIN_NAMESPACE /* qmake ignore Q_OBJECT */ static bool inMacroCommand_ = false; -static bool parsingHeaderFile_ = false; QStringList CppCodeParser::exampleFiles; QStringList CppCodeParser::exampleDirs; QSet<QString> CppCodeParser::excludeDirs; QSet<QString> CppCodeParser::excludeFiles; -CppCodeParser* CppCodeParser::cppParser_ = 0; /*! The constructor initializes some regular expressions @@ -61,7 +59,6 @@ CppCodeParser::CppCodeParser() : varComment("/\\*\\s*([a-zA-Z_0-9]+)\\s*\\*/"), sep("(?:<[^>]+>)?::") { reset(); - cppParser_ = this; } /*! @@ -88,6 +85,7 @@ void CppCodeParser::initializeParser(const Config &config) nodeTypeMap.insert(COMMAND_NAMESPACE, Node::Namespace); nodeTypeMap.insert(COMMAND_CLASS, Node::Class); nodeTypeMap.insert(COMMAND_ENUM, Node::Enum); + nodeTypeMap.insert(COMMAND_TYPEALIAS, Node::Typedef); nodeTypeMap.insert(COMMAND_TYPEDEF, Node::Typedef); nodeTypeMap.insert(COMMAND_PROPERTY, Node::Property); nodeTypeMap.insert(COMMAND_VARIABLE, Node::Variable); @@ -140,7 +138,7 @@ QString CppCodeParser::language() */ QStringList CppCodeParser::headerFileNameFilter() { - return QStringList() << "*.ch" << "*.h" << "*.h++" << "*.hh" << "*.hpp" << "*.hxx"; + return QStringList(); } /*! @@ -149,121 +147,9 @@ QStringList CppCodeParser::headerFileNameFilter() */ QStringList CppCodeParser::sourceFileNameFilter() { - return QStringList() << "*.c++" << "*.cc" << "*.cpp" << "*.cxx" << "*.mm"; + return QStringList(); } -/*! - Parse the C++ header file identified by \a filePath and add - the parsed contents to the database. The \a location is used - for reporting errors. - */ -void CppCodeParser::parseHeaderFile(const Location& location, const QString& filePath) -{ - QFile in(filePath); - currentFile_ = filePath; - if (!in.open(QIODevice::ReadOnly)) { - location.error(tr("Cannot open C++ header file '%1'").arg(filePath)); - currentFile_.clear(); - return; - } - - reset(); - Location fileLocation(filePath); - Tokenizer fileTokenizer(fileLocation, in); - tokenizer = &fileTokenizer; - readToken(); - parsingHeaderFile_ = true; - matchDeclList(qdb_->primaryTreeRoot()); - parsingHeaderFile_ = false; - if (!fileTokenizer.version().isEmpty()) - qdb_->setVersion(fileTokenizer.version()); - in.close(); - - if (fileLocation.fileName() == "qiterator.h") - parseQiteratorDotH(location, filePath); - currentFile_.clear(); -} - -/*! - Get ready to parse the C++ cpp file identified by \a filePath - and add its parsed contents to the database. \a location is - used for reporting errors. - - Call matchDocsAndStuff() to do all the parsing and tree building. - */ -void CppCodeParser::parseSourceFile(const Location& location, const QString& filePath) -{ - QFile in(filePath); - currentFile_ = filePath; - if (!in.open(QIODevice::ReadOnly)) { - location.error(tr("Cannot open C++ source file '%1' (%2)").arg(filePath).arg(strerror(errno))); - currentFile_.clear(); - return; - } - - reset(); - Location fileLocation(filePath); - Tokenizer fileTokenizer(fileLocation, in); - tokenizer = &fileTokenizer; - readToken(); - - /* - The set of open namespaces is cleared before parsing - each source file. The word "source" here means cpp file. - */ - qdb_->clearOpenNamespaces(); - - matchDocsAndStuff(); - in.close(); - currentFile_.clear(); -} - -/*! - This is called after all the C++ header files have been - parsed. The most important thing it does is resolve C++ - class inheritance links in the tree. It also initializes - a bunch of other collections. - */ -void CppCodeParser::doneParsingHeaderFiles() -{ - QMapIterator<QString, QString> i(sequentialIteratorClasses); - while (i.hasNext()) { - i.next(); - instantiateIteratorMacro(i.key(), i.value(), sequentialIteratorDefinition); - } - i = mutableSequentialIteratorClasses; - while (i.hasNext()) { - i.next(); - instantiateIteratorMacro(i.key(), i.value(), mutableSequentialIteratorDefinition); - } - i = associativeIteratorClasses; - while (i.hasNext()) { - i.next(); - instantiateIteratorMacro(i.key(), i.value(), associativeIteratorDefinition); - } - i = mutableAssociativeIteratorClasses; - while (i.hasNext()) { - i.next(); - instantiateIteratorMacro(i.key(), i.value(), mutableAssociativeIteratorDefinition); - } - sequentialIteratorDefinition.clear(); - mutableSequentialIteratorDefinition.clear(); - associativeIteratorDefinition.clear(); - mutableAssociativeIteratorDefinition.clear(); - sequentialIteratorClasses.clear(); - mutableSequentialIteratorClasses.clear(); - associativeIteratorClasses.clear(); - mutableAssociativeIteratorClasses.clear(); -} - -/*! - This is called after all the source files (i.e., not the - header files) have been parsed. Currently nothing to do. - */ -void CppCodeParser::doneParsingSourceFiles() -{ - // contents moved to QdocDatabase::resolveIssues() -} static QSet<QString> topicCommands_; /*! @@ -286,6 +172,7 @@ const QSet<QString>& CppCodeParser::topicCommands() << COMMAND_NAMESPACE << COMMAND_PAGE << COMMAND_PROPERTY + << COMMAND_TYPEALIAS << COMMAND_TYPEDEF << COMMAND_VARIABLE << COMMAND_QMLTYPE @@ -321,86 +208,7 @@ Node* CppCodeParser::processTopicCommand(const Doc& doc, { ExtraFuncData extra; if (command == COMMAND_FN) { - QStringList parentPath; - FunctionNode *func = 0; - FunctionNode *clone = 0; - - if (!makeFunctionNode(arg.first, &parentPath, &clone, extra) && - !makeFunctionNode("void " + arg.first, &parentPath, &clone, extra)) { - doc.startLocation().warning(tr("Invalid syntax in '\\%1'").arg(COMMAND_FN)); - } - else { - func = qdb_->findFunctionNode(parentPath, clone); - if (func == 0) { - if (parentPath.isEmpty() && !lastPath_.isEmpty()) - func = qdb_->findFunctionNode(lastPath_, clone); - } - - /* - If the node was not found, then search for it in the - open C++ namespaces. We don't expect this search to - be necessary often. Nor do we expect it to succeed - very often. - */ - if (func == 0) - func = qdb_->findNodeInOpenNamespace(parentPath, clone); - - if (func == 0) { - doc.location().warning(tr("Cannot find '%1' in '\\%2' %3") - .arg(clone->name() + "(...)") - .arg(COMMAND_FN) - .arg(arg.first), - tr("I cannot find any function of that name with the " - "specified signature. Make sure that the signature " - "is identical to the declaration, including 'const' " - "qualifiers.")); - } - else - lastPath_ = parentPath; - if (func) { - func->borrowParameterNames(clone); - func->setParentPath(clone->parentPath()); - } - delete clone; - } - return func; - } - else if (command == COMMAND_MACRO) { - QStringList parentPath; - FunctionNode *func = 0; - - extra.root = qdb_->primaryTreeRoot(); - extra.isMacro = true; - if (makeFunctionNode(arg.first, &parentPath, &func, extra)) { - if (!parentPath.isEmpty()) { - doc.startLocation().warning(tr("Invalid syntax in '\\%1'").arg(COMMAND_MACRO)); - delete func; - func = 0; - } - else { - func->setMetaness(FunctionNode::MacroWithParams); - QVector<Parameter> params = func->parameters(); - for (int i = 0; i < params.size(); ++i) { - Parameter ¶m = params[i]; - if (param.name().isEmpty() && !param.dataType().isEmpty() - && param.dataType() != "...") - param = Parameter("", "", param.dataType()); - } - func->setParameters(params); - } - return func; - } - else if (QRegExp("[A-Za-z_][A-Za-z0-9_]+").exactMatch(arg.first)) { - func = new FunctionNode(qdb_->primaryTreeRoot(), arg.first); - func->setAccess(Node::Public); - func->setLocation(doc.startLocation()); - func->setMetaness(FunctionNode::MacroWithoutParams); - } - else { - doc.location().warning(tr("Invalid syntax in '\\%1'").arg(COMMAND_MACRO)); - - } - return func; + Q_UNREACHABLE(); } else if (nodeTypeMap.contains(command)) { /* @@ -420,8 +228,10 @@ Node* CppCodeParser::processTopicCommand(const Doc& doc, if (node == 0) node = qdb_->findNodeByNameAndType(path, type); if (node == 0) { - doc.location().warning(tr("Cannot find '%1' specified with '\\%2' in any header file") - .arg(arg.first).arg(command)); + if (isWorthWarningAbout(doc)) { + doc.location().warning(tr("Cannot find '%1' specified with '\\%2' in any header file") + .arg(arg.first).arg(command)); + } lastPath_ = path; } @@ -560,50 +370,7 @@ Node* CppCodeParser::processTopicCommand(const Doc& doc, (command == COMMAND_JSMETHOD) || (command == COMMAND_JSATTACHEDSIGNAL) || (command == COMMAND_JSATTACHEDMETHOD)) { - QString module; - QString name; - QString type; - if (splitQmlMethodArg(arg.first, type, module, name)) { - Aggregate* aggregate = qdb_->findQmlType(module, name); - if (!aggregate) - aggregate = qdb_->findQmlBasicType(module, name); - if (aggregate) { - bool attached = false; - Node::NodeType nodeType = Node::QmlMethod; - if ((command == COMMAND_QMLSIGNAL) || - (command == COMMAND_JSSIGNAL)) - nodeType = Node::QmlSignal; - else if ((command == COMMAND_QMLATTACHEDSIGNAL) || - (command == COMMAND_JSATTACHEDSIGNAL)) { - nodeType = Node::QmlSignal; - attached = true; - } - else if ((command == COMMAND_QMLMETHOD) || - (command == COMMAND_JSMETHOD)) { - // do nothing - } - else if ((command == COMMAND_QMLATTACHEDMETHOD) || - (command == COMMAND_JSATTACHEDMETHOD)) - attached = true; - else - return 0; // never get here. - FunctionNode* fn = makeFunctionNode(doc, - arg.first, - aggregate, - nodeType, - attached, - command); - if (fn) { - fn->setLocation(doc.startLocation()); - if ((command == COMMAND_JSSIGNAL) || - (command == COMMAND_JSMETHOD) || - (command == COMMAND_JSATTACHEDSIGNAL) || - (command == COMMAND_JSATTACHEDMETHOD)) - fn->setGenus(Node::JS); - } - return fn; - } - } + Q_UNREACHABLE(); } return 0; } @@ -623,7 +390,8 @@ Node* CppCodeParser::processTopicCommand(const Doc& doc, bool CppCodeParser::splitQmlPropertyGroupArg(const QString& arg, QString& module, QString& qmlTypeName, - QString& name) + QString& name, + const Location& location) { QStringList colonSplit = arg.split("::"); if (colonSplit.size() == 3) { @@ -633,7 +401,7 @@ bool CppCodeParser::splitQmlPropertyGroupArg(const QString& arg, return true; } QString msg = "Unrecognizable QML module/component qualifier for " + arg; - location().warning(tr(msg.toLatin1().data())); + location.warning(tr(msg.toLatin1().data())); return false; } @@ -660,7 +428,8 @@ bool CppCodeParser::splitQmlPropertyArg(const QString& arg, QString& type, QString& module, QString& qmlTypeName, - QString& name) + QString& name, + const Location &location) { QStringList blankSplit = arg.split(QLatin1Char(' ')); if (blankSplit.size() > 1) { @@ -679,66 +448,16 @@ bool CppCodeParser::splitQmlPropertyArg(const QString& arg, return true; } QString msg = "Unrecognizable QML module/component qualifier for " + arg; - location().warning(tr(msg.toLatin1().data())); + location.warning(tr(msg.toLatin1().data())); } else { QString msg = "Missing property type for " + arg; - location().warning(tr(msg.toLatin1().data())); + location.warning(tr(msg.toLatin1().data())); } return false; } /*! - A QML signal or method argument has the form... - - <type> <QML-type>::<name>(<param>, <param>, ...) - <type> <QML-module>::<QML-type>::<name>(<param>, <param>, ...) - - This function splits the \a{arg}ument into one of those - two forms, sets \a type, \a module, and \a qmlTypeName, - and returns true. If the argument doesn't match either - form, an error message is emitted and false is returned. - - \note The two QML types \e{Component} and \e{QtObject} never - have a module qualifier. - */ -bool CppCodeParser::splitQmlMethodArg(const QString& arg, - QString& type, - QString& module, - QString& qmlTypeName) -{ - QString name; - int leftParen = arg.indexOf(QChar('(')); - if (leftParen > 0) - name = arg.left(leftParen); - else - name = arg; - int firstBlank = name.indexOf(QChar(' ')); - if (firstBlank > 0) { - type = name.left(firstBlank); - name = name.right(name.length() - firstBlank - 1); - } - else - type.clear(); - - QStringList colonSplit(name.split("::")); - if (colonSplit.size() > 1) { - if (colonSplit.size() > 2) { - module = colonSplit[0]; - qmlTypeName = colonSplit[1]; - } - else { - module.clear(); - qmlTypeName = colonSplit[0]; - } - return true; - } - QString msg = "Unrecognizable QML module/component qualifier for " + arg; - location().warning(tr(msg.toLatin1().data())); - return false; -} - -/*! Process the topic \a command group found in the \a doc with arguments \a args. Currently, this function is called only for \e{qmlproperty} @@ -775,7 +494,7 @@ void CppCodeParser::processQmlProperties(const Doc& doc, else qmlPropertyGroupTopic.topic = COMMAND_QMLPROPERTYGROUP; arg = qmlPropertyGroupTopic.args; - if (splitQmlPropertyArg(arg, type, module, qmlTypeName, property)) { + if (splitQmlPropertyArg(arg, type, module, qmlTypeName, property, doc.location())) { int i = property.indexOf('.'); if (i != -1) { property = property.left(i); @@ -795,7 +514,7 @@ void CppCodeParser::processQmlProperties(const Doc& doc, if (!qmlPropertyGroupTopic.isEmpty()) { arg = qmlPropertyGroupTopic.args; - if (splitQmlPropertyGroupArg(arg, module, qmlTypeName, property)) { + if (splitQmlPropertyGroupArg(arg, module, qmlTypeName, property, doc.location())) { qmlType = qdb_->findQmlType(module, qmlTypeName); if (qmlType) { qpgn = new QmlPropertyGroupNode(qmlType, property); @@ -817,7 +536,7 @@ void CppCodeParser::processQmlProperties(const Doc& doc, (topic == COMMAND_JSPROPERTY) || (topic == COMMAND_JSATTACHEDPROPERTY)) { bool attached = ((topic == COMMAND_QMLATTACHEDPROPERTY) || (topic == COMMAND_JSATTACHEDPROPERTY)); - if (splitQmlPropertyArg(arg, type, module, qmlTypeName, property)) { + if (splitQmlPropertyArg(arg, type, module, qmlTypeName, property, doc.location())) { Aggregate* aggregate = qdb_->findQmlType(module, qmlTypeName); if (!aggregate) aggregate = qdb_->findQmlBasicType(module, qmlTypeName); @@ -909,11 +628,13 @@ void CppCodeParser::processOtherMetaCommand(const Doc& doc, FunctionNode *func = (FunctionNode *) node; const FunctionNode *from = func->reimplementedFrom(); if (from == 0) { - doc.location().warning(tr("Cannot find base function for '\\%1' in %2()") - .arg(COMMAND_REIMP).arg(node->name()), - tr("The function either doesn't exist in any " - "base class with the same signature or it " - "exists but isn't virtual.")); + if (isWorthWarningAbout(doc)) { + doc.location().warning(tr("Cannot find base function for '\\%1' in %2()") + .arg(COMMAND_REIMP).arg(node->name()), + tr("The function either doesn't exist in any " + "base class with the same signature or it " + "exists but isn't virtual.")); + } } /* Ideally, we would enable this check to warn whenever @@ -921,8 +642,9 @@ void CppCodeParser::processOtherMetaCommand(const Doc& doc, internal if the function is a reimplementation of another function in a base class. */ - else if (from->access() == Node::Private - || from->parent()->access() == Node::Private) { + else if ((from->access() == Node::Private + || from->parent()->access() == Node::Private) + && isWorthWarningAbout(doc)) { doc.location().warning(tr("'\\%1' in %2() should be '\\internal' " "because its base function is private " "or internal").arg(COMMAND_REIMP).arg(node->name())); @@ -1179,15 +901,6 @@ bool CppCodeParser::matchTemplateAngles(CodeChunk *dataType) return matches; } -/* - This function is no longer used. - */ -bool CppCodeParser::matchTemplateHeader() -{ - readToken(); - return matchTemplateAngles(); -} - bool CppCodeParser::matchDataType(CodeChunk *dataType, QString *var, bool qProp) { /* @@ -1212,11 +925,24 @@ bool CppCodeParser::matchDataType(CodeChunk *dataType, QString *var, bool qProp) */ while (match(Tok_const) || match(Tok_volatile)) dataType->append(previousLexeme()); - while (match(Tok_signed) || match(Tok_unsigned) || - match(Tok_short) || match(Tok_long) || match(Tok_int64)) { - dataType->append(previousLexeme()); + QString pending; + while (tok == Tok_signed || tok == Tok_int || tok == Tok_unsigned || + tok == Tok_short || tok == Tok_long || tok == Tok_int64) { + if (tok == Tok_signed) + pending = lexeme(); + else { + if (tok == Tok_unsigned && !pending.isEmpty()) + dataType->append(pending); + pending.clear(); + dataType->append(lexeme()); + } + readToken(); virgin = false; } + if (!pending.isEmpty()) { + dataType->append(pending); + pending.clear(); + } while (match(Tok_const) || match(Tok_volatile)) dataType->append(previousLexeme()); @@ -1274,22 +1000,29 @@ bool CppCodeParser::matchDataType(CodeChunk *dataType, QString *var, bool qProp) in some cases (e.g., 'operator int()'). The tokenizer recognizes '(*' as a single token. */ + dataType->append(" "); // force a space after the type dataType->append(previousLexeme()); dataType->appendHotspot(); if (var != 0 && match(Tok_Ident)) *var = previousLexeme(); - if (!match(Tok_RightParen) || tok != Tok_LeftParen) { + if (!match(Tok_RightParen)) + return false; + dataType->append(previousLexeme()); + if (!match(Tok_LeftParen)) return false; - } dataType->append(previousLexeme()); - int parenDepth0 = tokenizer->parenDepth(); - while (tokenizer->parenDepth() >= parenDepth0 && tok != Tok_Eoi) { - dataType->append(lexeme()); - readToken(); + /* parse the parameters. Ignore the parameter name from the type */ + while (tok != Tok_RightParen && tok != Tok_Eoi) { + QString dummy; + if (!matchDataType(dataType, &dummy)) + return false; + if (match(Tok_Comma)) + dataType->append(previousLexeme()); } - if (match(Tok_RightParen)) - dataType->append(previousLexeme()); + if (!match(Tok_RightParen)) + return false; + dataType->append(previousLexeme()); } else { /* @@ -1314,6 +1047,24 @@ bool CppCodeParser::matchDataType(CodeChunk *dataType, QString *var, bool qProp) if (varComment.exactMatch(previousLexeme())) *var = varComment.cap(1); } + else if (match(Tok_LeftParen)) { + *var = "("; + while (tok != Tok_RightParen && tok != Tok_Eoi) { + (*var).append(lexeme()); + readToken(); + } + (*var).append(")"); + readToken(); + if (match(Tok_LeftBracket)) { + (*var).append("["); + while (tok != Tok_RightBracket && tok != Tok_Eoi) { + (*var).append(lexeme()); + readToken(); + } + (*var).append("]"); + readToken(); + } + } else if (qProp && (match(Tok_default) || match(Tok_final) || match(Tok_override))) { // Hack to make 'default', 'final' and 'override' work again in Q_PROPERTY *var = previousLexeme(); @@ -1369,495 +1120,6 @@ bool CppCodeParser::matchParameter(QVector<Parameter>& pvect, bool& isQPrivateSi } /*! - If the current token is any of several function modifiers, - return that token value after reading the next token. If it - is not one of the function modieifer tokens, return -1 but - don\t read the next token. - */ -int CppCodeParser::matchFunctionModifier() -{ - switch (tok) { - case Tok_friend: - case Tok_inline: - case Tok_explicit: - case Tok_static: - case Tok_QT_DEPRECATED: - readToken(); - return tok; - case Tok_QT_COMPAT: - case Tok_QT_COMPAT_CONSTRUCTOR: - case Tok_QT_MOC_COMPAT: - case Tok_QT3_SUPPORT: - case Tok_QT3_SUPPORT_CONSTRUCTOR: - case Tok_QT3_MOC_SUPPORT: - readToken(); - return Tok_QT_COMPAT; - default: - break; - } - return -1; -} - -bool CppCodeParser::matchFunctionDecl(Aggregate *parent, - QStringList *parentPathPtr, - FunctionNode **funcPtr, - const QString &templateStuff, - ExtraFuncData& extra) -{ - CodeChunk returnType; - QStringList parentPath; - QString name; - - bool matched_QT_DEPRECATED = false; - bool matched_friend = false; - bool matched_static = false; - bool matched_inline = false; - bool matched_explicit = false; - bool matched_compat = false; - - int token = tok; - while (token != -1) { - switch (token) { - case Tok_friend: - matched_friend = true; - break; - case Tok_inline: - matched_inline = true; - break; - case Tok_explicit: - matched_explicit = true; - break; - case Tok_static: - matched_static = true; - break; - case Tok_QT_DEPRECATED: - matched_QT_DEPRECATED = true; - Q_FALLTHROUGH(); // no break here. - case Tok_QT_COMPAT: - matched_compat = true; - break; - } - token = matchFunctionModifier(); - } - - FunctionNode::Virtualness virtuality = FunctionNode::NonVirtual; - if (match(Tok_virtual)) { - virtuality = FunctionNode::NormalVirtual; - if (!matched_compat) - matched_compat = matchCompat(); - } - - if (!matchDataType(&returnType)) { - if (tokenizer->parsingFnOrMacro() - && (match(Tok_Q_DECLARE_FLAGS) || - match(Tok_Q_PROPERTY) || - match(Tok_Q_PRIVATE_PROPERTY))) - returnType = CodeChunk(previousLexeme()); - else { - return false; - } - } - - if (returnType.toString() == "QBool") - returnType = CodeChunk("bool"); - - if (!matched_compat) - matched_compat = matchCompat(); - - if (tok == Tok_operator && - (returnType.toString().isEmpty() || - returnType.toString().endsWith("::"))) { - // 'QString::operator const char *()' - parentPath = returnType.toString().split(sep); - parentPath.removeAll(QString()); - returnType = CodeChunk(); - readToken(); - - CodeChunk restOfName; - if (tok != Tok_Tilde && matchDataType(&restOfName)) { - name = "operator " + restOfName.toString(); - } - else { - name = previousLexeme() + lexeme(); - readToken(); - while (tok != Tok_LeftParen && tok != Tok_Eoi) { - name += lexeme(); - readToken(); - } - } - if (tok != Tok_LeftParen) { - return false; - } - } - else if (tok == Tok_LeftParen) { - // constructor or destructor - parentPath = returnType.toString().split(sep); - if (!parentPath.isEmpty()) { - name = parentPath.last(); - parentPath.erase(parentPath.end() - 1); - } - returnType = CodeChunk(); - } - else { - while (match(Tok_Ident)) { - name = previousLexeme(); - /* - This is a hack to let QML module identifiers through. - */ - matchModuleQualifier(name); - matchTemplateAngles(); - - if (match(Tok_Gulbrandsen)) - parentPath.append(name); - else - break; - } - - if (tok == Tok_operator) { - name = lexeme(); - readToken(); - while (tok != Tok_Eoi) { - name += lexeme(); - readToken(); - if (tok == Tok_LeftParen) - break; - } - } - if (parent && (tok == Tok_Semicolon || - tok == Tok_LeftBracket || - tok == Tok_Colon || tok == Tok_Equal) - && access != Node::Private) { - if (tok == Tok_LeftBracket) { - returnType.appendHotspot(); - - int bracketDepth0 = tokenizer->bracketDepth(); - while ((tokenizer->bracketDepth() >= bracketDepth0 && - tok != Tok_Eoi) || - tok == Tok_RightBracket) { - returnType.append(lexeme()); - readToken(); - } - if (tok != Tok_Semicolon) { - return false; - } - } - else if (tok == Tok_Colon || tok == Tok_Equal) { - returnType.appendHotspot(); - - while (tok != Tok_Semicolon && tok != Tok_Eoi) { - returnType.append(lexeme()); - readToken(); - } - if (tok != Tok_Semicolon) { - return false; - } - } - - VariableNode *var = new VariableNode(parent, name); - var->setAccess(access); - if (parsingHeaderFile_) - var->setLocation(declLoc()); - else - var->setLocation(location()); - var->setLeftType(returnType.left()); - var->setRightType(returnType.right()); - if (matched_compat) - var->setStatus(Node::Compat); - var->setStatic(matched_static); - return false; - } - if (tok != Tok_LeftParen) - return false; - } - readToken(); - - // A left paren was seen. Parse the parameters - bool isQPrivateSignal = false; - QVector<Parameter> pvect; - if (tok != Tok_RightParen) { - do { - if (!matchParameter(pvect, isQPrivateSignal)) - return false; - } while (match(Tok_Comma)); - } - // The parameters must end with a right paren - if (!match(Tok_RightParen)) - return false; - - // look for const - bool matchedConst = match(Tok_const); - bool matchFinal = match(Tok_final); - bool matchOverride = match(Tok_override); - - bool isDeleted = false; - bool isDefaulted = false; - if (match(Tok_Equal)) { - if (match(Tok_Number)) // look for 0 indicating pure virtual - virtuality = FunctionNode::PureVirtual; - else if (match(Tok_delete)) - isDeleted = true; - else if (match(Tok_default)) - isDefaulted = true; - } - // look for colon indicating ctors which must be skipped - if (match(Tok_Colon)) { - while (tok != Tok_LeftBrace && tok != Tok_Eoi) - readToken(); - } - - // If no ';' expect a body, which must be skipped. - bool body_expected = false; - bool body_present = false; - if (!match(Tok_Semicolon) && tok != Tok_Eoi) { - body_expected = true; - int nesting = tokenizer->braceDepth(); - if (!match(Tok_LeftBrace)) - return false; - // skip the body - while (tokenizer->braceDepth() >= nesting && tok != Tok_Eoi) - readToken(); - body_present = true; - match(Tok_RightBrace); - } - - FunctionNode *func = 0; - bool createFunctionNode = false; - if (parsingHeaderFile_) { - if (matched_friend) { - if (matched_inline) { - // nothing yet - } - if (body_present) { - if (body_expected) { - // nothing yet - } - createFunctionNode = true; - if (parent && parent->parent()) - parent = parent->parent(); - else - return false; - } - } - else - createFunctionNode = true; - } - else - createFunctionNode = true; - - if (createFunctionNode) { - func = new FunctionNode(extra.type, parent, name, extra.isAttached); - if (matched_friend) - access = Node::Public; - func->setAccess(access); - if (parsingHeaderFile_) - func->setLocation(declLoc()); - else - func->setLocation(location()); - func->setReturnType(returnType.toString()); - func->setParentPath(parentPath); - func->setTemplateStuff(templateStuff); - if (matched_compat) - func->setStatus(Node::Compat); - if (matched_QT_DEPRECATED) - func->setStatus(Node::Deprecated); - if (matched_explicit) { /* What can be done? */ } - if (!pvect.isEmpty()) { - func->setParameters(pvect); - } - func->setMetaness(metaness_); - if (parent && (name == parent->name())) { - FunctionNode::Metaness m = FunctionNode::Ctor; - if (!pvect.isEmpty()) { - for (int i=0; i<pvect.size(); i++) { - const Parameter& p = pvect.at(i); - if (p.dataType().contains(name)) { - if (p.dataType().endsWith(QLatin1String("&&"))) { - m = FunctionNode::MCtor; - break; - } - if (p.dataType().endsWith(QLatin1String("&"))) { - m = FunctionNode::CCtor; - break; - } - } - } - } - func->setMetaness(m); - } - else if (name.startsWith(QLatin1Char('~'))) - func->setMetaness(FunctionNode::Dtor); - else if (name == QLatin1String("operator=")) { - FunctionNode::Metaness m = FunctionNode::Plain; - if (parent && pvect.size() == 1) { - const Parameter& p = pvect.at(0); - if (p.dataType().contains(parent->name())) { - if (p.dataType().endsWith(QLatin1String("&&"))) { - m = FunctionNode::MAssign; - } - else if (p.dataType().endsWith(QLatin1String("&"))) { - m = FunctionNode::CAssign; - } - } - } - func->setMetaness(m); - } - func->setStatic(matched_static); - func->setConst(matchedConst); - func->setVirtualness(virtuality); - func->setIsDeleted(isDeleted); - func->setIsDefaulted(isDefaulted); - func->setFinal(matchFinal); - func->setOverride(matchOverride); - if (isQPrivateSignal) - func->setPrivateSignal(); - } - if (parentPathPtr != 0) - *parentPathPtr = parentPath; - if (funcPtr != 0) - *funcPtr = func; - return true; -} - -bool CppCodeParser::matchBaseSpecifier(ClassNode *classe, bool isClass) -{ - Node::Access access; - - switch (tok) { - case Tok_public: - access = Node::Public; - readToken(); - break; - case Tok_protected: - access = Node::Protected; - readToken(); - break; - case Tok_private: - access = Node::Private; - readToken(); - break; - default: - access = isClass ? Node::Private : Node::Public; - } - - if (tok == Tok_virtual) - readToken(); - - CodeChunk baseClass; - if (!matchDataType(&baseClass)) - return false; - - classe->addUnresolvedBaseClass(access, baseClass.toPath(), baseClass.toString()); - return true; -} - -bool CppCodeParser::matchBaseList(ClassNode *classe, bool isClass) -{ - for (;;) { - if (!matchBaseSpecifier(classe, isClass)) - return false; - if (tok == Tok_LeftBrace) - return true; - if (!match(Tok_Comma)) - return false; - } -} - -/*! - Parse a C++ class, union, or struct declaration. - - This function only handles one level of class nesting, but that is - sufficient for Qt because there are no cases of class nesting more - than one level deep. - */ -bool CppCodeParser::matchClassDecl(Aggregate *parent, - const QString &templateStuff) -{ - bool isClass = (tok == Tok_class); - readToken(); - - bool compat = matchCompat(); - - if (tok != Tok_Ident) - return false; - while (tok == Tok_Ident) - readToken(); - if (tok == Tok_Gulbrandsen) { - Node* n = parent->findChildNode(previousLexeme(),Node::Class); - if (n) { - parent = static_cast<Aggregate*>(n); - if (parent) { - readToken(); - if (tok != Tok_Ident) - return false; - readToken(); - } - } - } - - const QString className = previousLexeme(); - match(Tok_final); // ignore C++11 final class-virt-specifier - if (tok != Tok_Colon && tok != Tok_LeftBrace) - return false; - - /* - So far, so good. We have 'class Foo {' or 'class Foo :'. - This is enough to recognize a class definition. - */ - ClassNode *classe = new ClassNode(parent, className); - classe->setAccess(access); - classe->setLocation(declLoc()); - if (compat) - classe->setStatus(Node::Compat); - if (!physicalModuleName.isEmpty()) - classe->setPhysicalModuleName(physicalModuleName); - classe->setTemplateStuff(templateStuff); - - if (match(Tok_Colon) && !matchBaseList(classe, isClass)) - return false; - if (!match(Tok_LeftBrace)) - return false; - - Node::Access outerAccess = access; - access = isClass ? Node::Private : Node::Public; - FunctionNode::Metaness outerMetaness = metaness_; - metaness_ = FunctionNode::Plain; - - bool matches = (matchDeclList(classe) && match(Tok_RightBrace) && - match(Tok_Semicolon)); - access = outerAccess; - metaness_ = outerMetaness; - return matches; -} - -bool CppCodeParser::matchNamespaceDecl(Aggregate *parent) -{ - readToken(); // skip 'namespace' - if (tok != Tok_Ident) - return false; - while (tok == Tok_Ident) - readToken(); - if (tok != Tok_LeftBrace) - return false; - - /* - So far, so good. We have 'namespace Foo {'. - */ - QString namespaceName = previousLexeme(); - NamespaceNode* ns = 0; - if (parent) - ns = static_cast<NamespaceNode*>(parent->findChildNode(namespaceName, Node::Namespace)); - if (!ns) { - ns = new NamespaceNode(parent, namespaceName); - ns->setAccess(access); - ns->setLocation(declLoc()); - } - - readToken(); // skip '{' - bool matched = matchDeclList(ns); - return matched && match(Tok_RightBrace); -} - -/*! Match a C++ \c using clause. Return \c true if the match is successful. Otherwise false. @@ -1930,585 +1192,6 @@ bool CppCodeParser::matchUsingDecl(Aggregate* parent) return true; } -bool CppCodeParser::matchEnumItem(Aggregate *parent, EnumNode *enume) -{ - if (!match(Tok_Ident)) - return false; - - QString name = previousLexeme(); - CodeChunk val; - int parenLevel = 0; - - if (match(Tok_Equal)) { - while (tok != Tok_RightBrace && tok != Tok_Eoi) { - if (tok == Tok_LeftParen) - parenLevel++; - else if (tok == Tok_RightParen) - parenLevel--; - else if (tok == Tok_Comma) { - if (parenLevel <= 0) - break; - } - val.append(lexeme()); - readToken(); - } - } - - if (enume) { - QString strVal = val.toString(); - if (strVal.isEmpty()) { - if (enume->items().isEmpty()) { - strVal = "0"; - } - else { - QString last = enume->items().last().value(); - bool ok; - int n = last.toInt(&ok); - if (ok) { - if (last.startsWith(QLatin1Char('0')) && last.size() > 1) { - if (last.startsWith("0x") || last.startsWith("0X")) - strVal = last.left(2) + QString::number(n + 1, 16); - else - strVal = QLatin1Char('0') + QString::number(n + 1, 8); - } - else - strVal = QString::number(n + 1); - } - } - } - - enume->addItem(EnumItem(name, strVal)); - } - else { - VariableNode *var = new VariableNode(parent, name); - var->setAccess(access); - var->setLocation(location()); - var->setLeftType("const int"); - var->setStatic(true); - } - return true; -} - -bool CppCodeParser::matchEnumDecl(Aggregate *parent) -{ - QString name; - - if (!match(Tok_enum)) - return false; - if (tok == Tok_struct || tok == Tok_class) - readToken(); // ignore C++11 struct or class attribute - if (match(Tok_Ident)) - name = previousLexeme(); - if (match(Tok_Colon)) { // ignore C++11 enum-base - CodeChunk dataType; - if (!matchDataType(&dataType)) - return false; - } - if (tok != Tok_LeftBrace) - return false; - - EnumNode *enume = 0; - - if (!name.isEmpty()) { - enume = new EnumNode(parent, name); - enume->setAccess(access); - enume->setLocation(declLoc()); - } - - readToken(); - - if (!matchEnumItem(parent, enume)) - return false; - - while (match(Tok_Comma)) { - if (!matchEnumItem(parent, enume)) - return false; - } - return match(Tok_RightBrace) && match(Tok_Semicolon); -} - -bool CppCodeParser::matchTypedefDecl(Aggregate *parent) -{ - CodeChunk dataType; - QString name; - - if (!match(Tok_typedef)) - return false; - if (!matchDataType(&dataType, &name)) - return false; - if (!match(Tok_Semicolon)) - return false; - - if (parent && !parent->findChildNode(name, Node::Typedef)) { - TypedefNode* td = new TypedefNode(parent, name); - td->setAccess(access); - td->setLocation(declLoc()); - } - return true; -} - -bool CppCodeParser::matchProperty(Aggregate *parent) -{ - int expected_tok = Tok_LeftParen; - if (match(Tok_Q_PRIVATE_PROPERTY)) { - expected_tok = Tok_Comma; - if (!skipTo(Tok_Comma)) - return false; - } - else if (!match(Tok_Q_PROPERTY) && - !match(Tok_Q_OVERRIDE) && - !match(Tok_QDOC_PROPERTY)) { - return false; - } - - if (!match(expected_tok)) - return false; - - QString name; - CodeChunk dataType; - if (!matchDataType(&dataType, &name, true)) - return false; - - PropertyNode *property = new PropertyNode(parent, name); - property->setAccess(Node::Public); - property->setLocation(declLoc()); - property->setDataType(dataType.toString()); - - while (tok != Tok_RightParen && tok != Tok_Eoi) { - if (!match(Tok_Ident) && !match(Tok_default) && !match(Tok_final) && !match(Tok_override)) - return false; - QString key = previousLexeme(); - QString value; - - // Keywords with no associated values - if (key == "CONSTANT") { - property->setConstant(); - continue; - } - else if (key == "FINAL") { - property->setFinal(); - continue; - } - - if (match(Tok_Ident) || match(Tok_Number)) { - value = previousLexeme(); - } - else if (match(Tok_LeftParen)) { - int depth = 1; - while (tok != Tok_Eoi) { - if (tok == Tok_LeftParen) { - readToken(); - ++depth; - } else if (tok == Tok_RightParen) { - readToken(); - if (--depth == 0) - break; - } else { - readToken(); - } - } - value = "?"; - } - - if (key == "READ") - qdb_->addPropertyFunction(property, value, PropertyNode::Getter); - else if (key == "WRITE") { - qdb_->addPropertyFunction(property, value, PropertyNode::Setter); - property->setWritable(true); - } - else if (key == "STORED") - property->setStored(value.toLower() == "true"); - else if (key == "DESIGNABLE") { - QString v = value.toLower(); - if (v == "true") - property->setDesignable(true); - else if (v == "false") - property->setDesignable(false); - else { - property->setDesignable(false); - property->setRuntimeDesFunc(value); - } - } - else if (key == "RESET") - qdb_->addPropertyFunction(property, value, PropertyNode::Resetter); - else if (key == "NOTIFY") { - qdb_->addPropertyFunction(property, value, PropertyNode::Notifier); - } else if (key == "REVISION") { - int revision; - bool ok; - revision = value.toInt(&ok); - if (ok) - property->setRevision(revision); - else - location().warning(tr("Invalid revision number: %1").arg(value)); - } else if (key == "SCRIPTABLE") { - QString v = value.toLower(); - if (v == "true") - property->setScriptable(true); - else if (v == "false") - property->setScriptable(false); - else { - property->setScriptable(false); - property->setRuntimeScrFunc(value); - } - } - } - match(Tok_RightParen); - return true; -} - -/*! - Parse a C++ declaration. - */ -bool CppCodeParser::matchDeclList(Aggregate *parent) -{ - ExtraFuncData extra; - QString templateStuff; - int braceDepth0 = tokenizer->braceDepth(); - if (tok == Tok_RightBrace) // prevents failure on empty body - braceDepth0++; - - while (tokenizer->braceDepth() >= braceDepth0 && tok != Tok_Eoi) { - switch (tok) { - case Tok_Colon: - readToken(); - break; - case Tok_class: - case Tok_struct: - case Tok_union: - setDeclLoc(); - matchClassDecl(parent, templateStuff); - break; - case Tok_namespace: - setDeclLoc(); - matchNamespaceDecl(parent); - break; - case Tok_using: - setDeclLoc(); - matchUsingDecl(parent); - break; - case Tok_template: - { - CodeChunk dataType; - readToken(); - matchTemplateAngles(&dataType); - templateStuff = dataType.toString(); - } - continue; - case Tok_enum: - setDeclLoc(); - matchEnumDecl(parent); - break; - case Tok_typedef: - setDeclLoc(); - matchTypedefDecl(parent); - break; - case Tok_private: - readToken(); - access = Node::Private; - metaness_ = FunctionNode::Plain; - break; - case Tok_protected: - readToken(); - access = Node::Protected; - metaness_ = FunctionNode::Plain; - break; - case Tok_public: - readToken(); - access = Node::Public; - metaness_ = FunctionNode::Plain; - break; - case Tok_signals: - case Tok_Q_SIGNALS: - readToken(); - access = Node::Public; - metaness_ = FunctionNode::Signal; - break; - case Tok_slots: - case Tok_Q_SLOTS: - readToken(); - metaness_ = FunctionNode::Slot; - break; - case Tok_Q_OBJECT: - readToken(); - break; - case Tok_Q_OVERRIDE: - case Tok_Q_PROPERTY: - case Tok_Q_PRIVATE_PROPERTY: - case Tok_QDOC_PROPERTY: - setDeclLoc(); - if (!matchProperty(parent)) { - location().warning(tr("Failed to parse token %1 in property declaration").arg(lexeme())); - skipTo(Tok_RightParen); - match(Tok_RightParen); - } - break; - case Tok_Q_DECLARE_SEQUENTIAL_ITERATOR: - readToken(); - if (match(Tok_LeftParen) && match(Tok_Ident)) - sequentialIteratorClasses.insert(previousLexeme(), location().fileName()); - match(Tok_RightParen); - break; - case Tok_Q_DECLARE_MUTABLE_SEQUENTIAL_ITERATOR: - readToken(); - if (match(Tok_LeftParen) && match(Tok_Ident)) - mutableSequentialIteratorClasses.insert(previousLexeme(), location().fileName()); - match(Tok_RightParen); - break; - case Tok_Q_DECLARE_ASSOCIATIVE_ITERATOR: - readToken(); - if (match(Tok_LeftParen) && match(Tok_Ident)) - associativeIteratorClasses.insert(previousLexeme(), location().fileName()); - match(Tok_RightParen); - break; - case Tok_Q_DECLARE_MUTABLE_ASSOCIATIVE_ITERATOR: - readToken(); - if (match(Tok_LeftParen) && match(Tok_Ident)) - mutableAssociativeIteratorClasses.insert(previousLexeme(), location().fileName()); - match(Tok_RightParen); - break; - case Tok_Q_DECLARE_FLAGS: - readToken(); - setDeclLoc(); - if (match(Tok_LeftParen) && match(Tok_Ident)) { - QString flagsType = previousLexeme(); - if (match(Tok_Comma) && match(Tok_Ident)) { - QString name = previousLexeme(); - TypedefNode *flagsNode = new TypedefNode(parent, flagsType); - flagsNode->setAccess(access); - flagsNode->setLocation(declLoc()); - EnumNode* en = static_cast<EnumNode*>(parent->findChildNode(name, Node::Enum)); - if (en) - en->setFlagsType(flagsNode); - } - } - match(Tok_RightParen); - break; - case Tok_QT_MODULE: - readToken(); - setDeclLoc(); - if (match(Tok_LeftParen) && match(Tok_Ident)) - physicalModuleName = previousLexeme(); - if (!physicalModuleName.startsWith("Qt")) - physicalModuleName.prepend("Qt"); - match(Tok_RightParen); - break; - default: - if (parsingHeaderFile_) - setDeclLoc(); - if (!matchFunctionDecl(parent, 0, 0, templateStuff, extra)) { - while (tok != Tok_Eoi && - (tokenizer->braceDepth() > braceDepth0 || - (!match(Tok_Semicolon) && - tok != Tok_public && tok != Tok_protected && - tok != Tok_private))) { - readToken(); - } - } - } - templateStuff.clear(); - } - return true; -} - -/*! - This is called by parseSourceFile() to do the actual parsing - and tree building. - */ -bool CppCodeParser::matchDocsAndStuff() -{ - ExtraFuncData extra; - const QSet<QString>& topicCommandsAllowed = topicCommands(); - const QSet<QString>& otherMetacommandsAllowed = otherMetaCommands(); - const QSet<QString>& metacommandsAllowed = topicCommandsAllowed + otherMetacommandsAllowed; - - while (tok != Tok_Eoi) { - if (tok == Tok_Doc) { - /* - lexeme() returns an entire qdoc comment. - */ - QString comment = lexeme(); - Location start_loc(location()); - readToken(); - - Doc::trimCStyleComment(start_loc,comment); - Location end_loc(location()); - - /* - Doc parses the comment. - */ - Doc doc(start_loc,end_loc,comment,metacommandsAllowed, topicCommandsAllowed); - QString topic; - bool isQmlPropertyTopic = false; - bool isJsPropertyTopic = false; - - const TopicList& topics = doc.topicsUsed(); - if (!topics.isEmpty()) { - topic = topics[0].topic; - if ((topic == COMMAND_QMLPROPERTY) || - (topic == COMMAND_QMLPROPERTYGROUP) || - (topic == COMMAND_QMLATTACHEDPROPERTY)) { - isQmlPropertyTopic = true; - } - else if ((topic == COMMAND_JSPROPERTY) || - (topic == COMMAND_JSPROPERTYGROUP) || - (topic == COMMAND_JSATTACHEDPROPERTY)) { - isJsPropertyTopic = true; - } - } - NodeList nodes; - DocList docs; - - if (topic.isEmpty()) { - QStringList parentPath; - FunctionNode *clone; - FunctionNode *func = 0; - - if (matchFunctionDecl(0, &parentPath, &clone, QString(), extra)) { - func = qdb_->findFunctionNode(parentPath, clone); - /* - If the node was not found, then search for it in the - open C++ namespaces. We don't expect this search to - be necessary often. Nor do we expect it to succeed - very often. - */ - if (func == 0) - func = qdb_->findNodeInOpenNamespace(parentPath, clone); - - if (func) { - func->borrowParameterNames(clone); - nodes.append(func); - docs.append(doc); - } - delete clone; - } - else { - doc.location().warning(tr("Cannot tie this documentation to anything"), - tr("I found a /*! ... */ comment, but there was no " - "topic command (e.g., '\\%1', '\\%2') in the " - "comment and no function definition following " - "the comment.") - .arg(COMMAND_FN).arg(COMMAND_PAGE)); - } - } - else if (isQmlPropertyTopic || isJsPropertyTopic) { - Doc nodeDoc = doc; - processQmlProperties(nodeDoc, nodes, docs, isJsPropertyTopic); - } - else { - ArgList args; - const QSet<QString>& topicCommandsUsed = topicCommandsAllowed & doc.metaCommandsUsed(); - if (topicCommandsUsed.count() > 0) { - topic = *topicCommandsUsed.constBegin(); - args = doc.metaCommandArgs(topic); - } - if (topicCommandsUsed.count() > 1) { - QString topics; - QSet<QString>::ConstIterator t = topicCommandsUsed.constBegin(); - while (t != topicCommandsUsed.constEnd()) { - topics += " \\" + *t + QLatin1Char(','); - ++t; - } - topics[topics.lastIndexOf(',')] = '.'; - int i = topics.lastIndexOf(','); - topics[i] = ' '; - topics.insert(i+1,"and"); - doc.location().warning(tr("Multiple topic commands found in comment: %1").arg(topics)); - } - ArgList::ConstIterator a = args.constBegin(); - while (a != args.constEnd()) { - Doc nodeDoc = doc; - Node *node = processTopicCommand(nodeDoc,topic,*a); - if (node != 0) { - nodes.append(node); - docs.append(nodeDoc); - } - ++a; - } - } - - NodeList::Iterator n = nodes.begin(); - QList<Doc>::Iterator d = docs.begin(); - while (n != nodes.end()) { - processOtherMetaCommands(*d, *n); - (*n)->setDoc(*d); - checkModuleInclusion(*n); - if ((*n)->isAggregate() && ((Aggregate *)*n)->includes().isEmpty()) { - Aggregate *m = static_cast<Aggregate *>(*n); - while (m->parent() && m->physicalModuleName().isEmpty()) { - m = m->parent(); - } - if (m == *n) - ((Aggregate *)*n)->addInclude((*n)->name()); - else - ((Aggregate *)*n)->setIncludes(m->includes()); - } - ++d; - ++n; - } - } - else if (tok == Tok_using) { - matchUsingDecl(0); - } - else { - QStringList parentPath; - FunctionNode *clone; - FunctionNode *fnode = 0; - - if (matchFunctionDecl(0, &parentPath, &clone, QString(), extra)) { - /* - The location of the definition is more interesting - than that of the declaration. People equipped with - a sophisticated text editor can respond to warnings - concerning undocumented functions very quickly. - - Signals are implemented in uninteresting files - generated by moc. - */ - fnode = qdb_->findFunctionNode(parentPath, clone); - if (fnode != 0 && !fnode->isSignal()) - fnode->setLocation(clone->location()); - delete clone; - } - else { - if (tok != Tok_Doc) - readToken(); - } - } - } - return true; -} - -/*! - This function uses a Tokenizer to parse the function \a signature - in an attempt to match it to the signature of a child node of \a root. - If a match is found, \a funcPtr is set to point to the matching node - and true is returned. - */ -bool CppCodeParser::makeFunctionNode(const QString& signature, - QStringList* parentPathPtr, - FunctionNode** funcPtr, - ExtraFuncData& extra) -{ - Tokenizer* outerTokenizer = tokenizer; - int outerTok = tok; - - QByteArray latin1 = signature.toLatin1(); - Tokenizer stringTokenizer(location(), latin1); - stringTokenizer.setParsingFnOrMacro(true); - tokenizer = &stringTokenizer; - readToken(); - - inMacroCommand_ = extra.isMacro; - bool ok = matchFunctionDecl(extra.root, parentPathPtr, funcPtr, QString(), extra); - inMacroCommand_ = false; - // potential memory leak with funcPtr - - tokenizer = outerTokenizer; - tok = outerTok; - return ok; -} - /*! This function uses a Tokenizer to parse the \a parameters of a function into the parameter vector \a {pvect}. @@ -2537,74 +1220,6 @@ bool CppCodeParser::parseParameters(const QString& parameters, return true; } -/*! - Create a new FunctionNode for a QML method or signal, as - specified by \a type, as a child of \a parent. \a sig is - the complete signature, and if \a attached is true, the - method or signal is "attached". \a qdoctag is the text of - the \a type. - - \a parent is the QML class node. The QML module and QML - type names have already been consumed to find \a parent. - What remains in \a sig is the method signature. The method - must be a child of \a parent. - */ -FunctionNode* CppCodeParser::makeFunctionNode(const Doc& doc, - const QString& sig, - Aggregate* parent, - Node::NodeType type, - bool attached, - QString qdoctag) -{ - QStringList pp; - FunctionNode* fn = 0; - ExtraFuncData extra(parent, type, attached); - if (!makeFunctionNode(sig, &pp, &fn, extra) && !makeFunctionNode("void " + sig, &pp, &fn, extra)) { - doc.location().warning(tr("Invalid syntax in '\\%1'").arg(qdoctag)); - } - return fn; -} - -void CppCodeParser::parseQiteratorDotH(const Location &location, const QString &filePath) -{ - QFile file(filePath); - if (!file.open(QFile::ReadOnly)) - return; - - QString text = file.readAll(); - text.remove("\r"); - text.remove("\\\n"); - QStringList lines = text.split(QLatin1Char('\n')); - lines = lines.filter("Q_DECLARE"); - lines.replaceInStrings(QRegExp("#define Q[A-Z_]*\\(C\\)"), QString()); - - if (lines.size() == 4) { - sequentialIteratorDefinition = lines[0]; - mutableSequentialIteratorDefinition = lines[1]; - associativeIteratorDefinition = lines[2]; - mutableAssociativeIteratorDefinition = lines[3]; - } - else { - location.warning(tr("The qiterator.h hack failed")); - } -} - -void CppCodeParser::instantiateIteratorMacro(const QString &container, - const QString &includeFile, - const QString ¯oDef) -{ - QString resultingCode = macroDef; - resultingCode.replace(QRegExp("\\bC\\b"), container); - resultingCode.remove(QRegExp("\\s*##\\s*")); - - Location loc(includeFile); // hack to get the include file for free - QByteArray latin1 = resultingCode.toLatin1(); - Tokenizer stringTokenizer(loc, latin1); - tokenizer = &stringTokenizer; - readToken(); - matchDeclList(QDocDatabase::qdocDB()->primaryTreeRoot()); -} - void CppCodeParser::createExampleFileNodes(DocumentNode *dn) { QString examplePath = dn->name(); diff --git a/src/qdoc/cppcodeparser.h b/src/qdoc/cppcodeparser.h index c5f90f68d..a8ffba89d 100644 --- a/src/qdoc/cppcodeparser.h +++ b/src/qdoc/cppcodeparser.h @@ -59,17 +59,12 @@ class CppCodeParser : public CodeParser public: CppCodeParser(); ~CppCodeParser(); - static CppCodeParser* cppParser() { return cppParser_; } void initializeParser(const Config& config) override; void terminateParser() override; QString language() override; QStringList headerFileNameFilter() override; QStringList sourceFileNameFilter() override; - void parseHeaderFile(const Location& location, const QString& filePath) override; - void parseSourceFile(const Location& location, const QString& filePath) override; - void doneParsingHeaderFiles() override; - void doneParsingSourceFiles() override; bool parseParameters(const QString& parameters, QVector<Parameter>& pvect, bool& isQPrivateSignal); const Location& declLoc() const { return declLoc_; } void setDeclLoc() { declLoc_ = location(); } @@ -84,16 +79,14 @@ protected: bool splitQmlPropertyGroupArg(const QString& arg, QString& module, QString& element, - QString& name); + QString& name, + const Location& location); bool splitQmlPropertyArg(const QString& arg, QString& type, QString& module, QString& element, - QString& name); - bool splitQmlMethodArg(const QString& arg, - QString& type, - QString& module, - QString& element); + QString& name, + const Location& location); virtual void processOtherMetaCommand(const Doc& doc, const QString& command, const ArgLocPair& argLocPair, @@ -113,42 +106,10 @@ protected: bool matchCompat(); bool matchModuleQualifier(QString& name); bool matchTemplateAngles(CodeChunk *type = 0); - bool matchTemplateHeader(); bool matchDataType(CodeChunk *type, QString *var = 0, bool qProp = false); bool matchParameter(QVector<Parameter>& pvect, bool& isQPrivateSignal); - bool matchFunctionDecl(Aggregate *parent, - QStringList *parentPathPtr, - FunctionNode **funcPtr, - const QString &templateStuff, - ExtraFuncData& extra); - bool matchBaseSpecifier(ClassNode *classe, bool isClass); - bool matchBaseList(ClassNode *classe, bool isClass); - bool matchClassDecl(Aggregate *parent, - const QString &templateStuff = QString()); - bool matchNamespaceDecl(Aggregate *parent); bool matchUsingDecl(Aggregate* parent); - bool matchEnumItem(Aggregate *parent, EnumNode *enume); - bool matchEnumDecl(Aggregate *parent); - bool matchTypedefDecl(Aggregate *parent); - bool matchProperty(Aggregate *parent); - bool matchDeclList(Aggregate *parent); - bool matchDocsAndStuff(); - bool makeFunctionNode(const QString &synopsis, - QStringList *parentPathPtr, - FunctionNode **funcPtr, - ExtraFuncData& params); - FunctionNode* makeFunctionNode(const Doc& doc, - const QString& sig, - Aggregate* parent, - Node::NodeType type, - bool attached, - QString qdoctag); - void parseQiteratorDotH(const Location &location, const QString &filePath); - void instantiateIteratorMacro(const QString &container, - const QString &includeFile, - const QString ¯oDef); void createExampleFileNodes(DocumentNode *dn); - int matchFunctionModifier(); protected: QMap<QString, Node::NodeType> nodeTypeMap; @@ -163,20 +124,11 @@ protected: Location declLoc_; private: - QString sequentialIteratorDefinition; - QString mutableSequentialIteratorDefinition; - QString associativeIteratorDefinition; - QString mutableAssociativeIteratorDefinition; - QMap<QString, QString> sequentialIteratorClasses; - QMap<QString, QString> mutableSequentialIteratorClasses; - QMap<QString, QString> associativeIteratorClasses; - QMap<QString, QString> mutableAssociativeIteratorClasses; static QStringList exampleFiles; static QStringList exampleDirs; static QSet<QString> excludeDirs; static QSet<QString> excludeFiles; - static CppCodeParser* cppParser_; QString exampleNameFilter; QString exampleImageFilter; }; @@ -205,6 +157,7 @@ protected: #define COMMAND_REIMP Doc::alias("reimp") #define COMMAND_RELATES Doc::alias("relates") #define COMMAND_STARTPAGE Doc::alias("startpage") +#define COMMAND_TYPEALIAS Doc::alias("typealias") #define COMMAND_TYPEDEF Doc::alias("typedef") #define COMMAND_VARIABLE Doc::alias("variable") #define COMMAND_QMLABSTRACT Doc::alias("qmlabstract") diff --git a/src/qdoc/doc.cpp b/src/qdoc/doc.cpp index 3300da593..5f9ca669a 100644 --- a/src/qdoc/doc.cpp +++ b/src/qdoc/doc.cpp @@ -72,14 +72,12 @@ enum { CMD_BRIEF, CMD_C, CMD_CAPTION, - CMD_CHAPTER, CMD_CODE, CMD_CODELINE, CMD_DIV, CMD_DOTS, CMD_E, CMD_ELSE, - CMD_ENDCHAPTER, CMD_ENDCODE, CMD_ENDDIV, CMD_ENDFOOTNOTE, @@ -89,7 +87,6 @@ enum { CMD_ENDLIST, CMD_ENDMAPREF, CMD_ENDOMIT, - CMD_ENDPART, CMD_ENDQUOTATION, CMD_ENDRAW, CMD_ENDSECTION1, @@ -127,7 +124,6 @@ enum { CMD_OMIT, CMD_OMITVALUE, CMD_OVERLOAD, - CMD_PART, CMD_PRINTLINE, CMD_PRINTTO, CMD_PRINTUNTIL, @@ -188,14 +184,12 @@ static struct { { "brief", CMD_BRIEF, 0 }, { "c", CMD_C, 0 }, { "caption", CMD_CAPTION, 0 }, - { "chapter", CMD_CHAPTER, 0 }, { "code", CMD_CODE, 0 }, { "codeline", CMD_CODELINE, 0}, { "div", CMD_DIV, 0 }, { "dots", CMD_DOTS, 0 }, { "e", CMD_E, 0 }, { "else", CMD_ELSE, 0 }, - { "endchapter", CMD_ENDCHAPTER, 0 }, { "endcode", CMD_ENDCODE, 0 }, { "enddiv", CMD_ENDDIV, 0 }, { "endfootnote", CMD_ENDFOOTNOTE, 0 }, @@ -205,7 +199,6 @@ static struct { { "endlist", CMD_ENDLIST, 0 }, { "endmapref", CMD_ENDMAPREF, 0 }, { "endomit", CMD_ENDOMIT, 0 }, - { "endpart", CMD_ENDPART, 0 }, { "endquotation", CMD_ENDQUOTATION, 0 }, { "endraw", CMD_ENDRAW, 0 }, { "endsection1", CMD_ENDSECTION1, 0 }, // ### don't document for now @@ -243,7 +236,6 @@ static struct { { "omit", CMD_OMIT, 0 }, { "omitvalue", CMD_OMITVALUE, 0 }, { "overload", CMD_OVERLOAD, 0 }, - { "part", CMD_PART, 0 }, { "printline", CMD_PRINTLINE, 0 }, { "printto", CMD_PRINTTO, 0 }, { "printuntil", CMD_PRINTUNTIL, 0 }, @@ -501,8 +493,10 @@ private: QStack<int> openedInputs; - QString in; + QString input_; int pos; + int backslashPos; + int endPos; int len; Location cachedLoc; int cachedPos; @@ -554,9 +548,9 @@ void DocParser::parse(const QString& source, const QSet<QString>& metaCommandSet, const QSet<QString>& possibleTopics) { - in = source; + input_ = source; pos = 0; - len = in.length(); + len = input_.length(); cachedLoc = docPrivate->start_loc; cachedPos = 0; priv = docPrivate; @@ -583,15 +577,16 @@ void DocParser::parse(const QString& source, int numPreprocessorSkipping = 0; while (pos < len) { - QChar ch = in.at(pos); + QChar ch = input_.at(pos); switch (ch.unicode()) { case '\\': { QString cmdStr; + backslashPos = pos; pos++; while (pos < len) { - ch = in.at(pos); + ch = input_.at(pos); if (ch.isLetterOrNumber()) { cmdStr += ch; pos++; @@ -600,15 +595,16 @@ void DocParser::parse(const QString& source, break; } } + endPos = pos; if (cmdStr.isEmpty()) { if (pos < len) { enterPara(); - if (in.at(pos).isSpace()) { + if (input_.at(pos).isSpace()) { skipAllSpaces(); appendChar(QLatin1Char(' ')); } else { - appendChar(in.at(pos++)); + appendChar(input_.at(pos++)); } } } @@ -656,9 +652,6 @@ void DocParser::parse(const QString& source, leavePara(); enterPara(Atom::CaptionLeft, Atom::CaptionRight); break; - case CMD_CHAPTER: - startSection(Doc::Chapter, cmd); - break; case CMD_CODE: leavePara(); append(Atom::Code, getCode(CMD_CODE, 0)); @@ -731,9 +724,6 @@ void DocParser::parse(const QString& source, location().warning(tr("Unexpected '\\%1'").arg(cmdName(CMD_ELSE))); } break; - case CMD_ENDCHAPTER: - endSection(Doc::Chapter, cmd); - break; case CMD_ENDCODE: closeCommand(cmd); break; @@ -800,9 +790,6 @@ void DocParser::parse(const QString& source, case CMD_ENDOMIT: closeCommand(cmd); break; - case CMD_ENDPART: - endSection(Doc::Part, cmd); - break; case CMD_ENDQUOTATION: if (closeCommand(cmd)) { leavePara(); @@ -1098,9 +1085,6 @@ void DocParser::parse(const QString& source, if (!priv->omitEnumItemList.contains(p1)) priv->omitEnumItemList.append(p1); break; - case CMD_PART: - startSection(Doc::Part, cmd); - break; case CMD_PRINTLINE: leavePara(); { @@ -1429,10 +1413,12 @@ void DocParser::parse(const QString& source, "syntax definitions")); } else { - location().push(macro.defaultDefLocation.filePath()); - in.insert(pos, expandMacroToString(cmdStr, macro.defaultDef, macro.numParams)); - len = in.length(); - openedInputs.push(pos + macro.defaultDef.length()); + QString expanded = expandMacroToString(cmdStr, + macro.defaultDef, + macro.numParams); + input_.replace(backslashPos, endPos - backslashPos, expanded); + len = input_.length(); + pos = backslashPos; } } } @@ -1488,8 +1474,8 @@ void DocParser::parse(const QString& source, case '/': { if (pos + 2 < len) - if (in.at(pos + 1) == '/') - if (in.at(pos + 2) == '!') { + if (input_.at(pos + 1) == '/') + if (input_.at(pos + 2) == '!') { pos += 2; getRestOfLine(); break; @@ -1543,7 +1529,7 @@ void DocParser::parse(const QString& source, int numStrangeSymbols = 0; while (pos < len) { - unsigned char latin1Ch = in.at(pos).toLatin1(); + unsigned char latin1Ch = input_.at(pos).toLatin1(); if (islower(latin1Ch)) { ++numLowercase; ++pos; @@ -1566,14 +1552,14 @@ void DocParser::parse(const QString& source, ++pos; } else if (latin1Ch == ':' && pos < len - 1 - && in.at(pos + 1) == QLatin1Char(':')) { + && input_.at(pos + 1) == QLatin1Char(':')) { ++numStrangeSymbols; pos += 2; } else if (latin1Ch == '(') { if (pos > startPos) { if (pos < len - 1 && - in.at(pos + 1) == QLatin1Char(')')) { + input_.at(pos + 1) == QLatin1Char(')')) { ++numStrangeSymbols; pos += 2; break; @@ -1600,7 +1586,7 @@ void DocParser::parse(const QString& source, } } else { - QString word = in.mid(startPos, pos - startPos); + QString word = input_.mid(startPos, pos - startPos); // is word a C++ symbol or an English word? if ((numInternalUppercase >= 1 && numLowercase >= 2) || numStrangeSymbols > 0) { @@ -1651,7 +1637,7 @@ Location &DocParser::location() cachedPos = openedInputs.pop(); } while (cachedPos < pos) - cachedLoc.advance(in.at(cachedPos++)); + cachedLoc.advance(input_.at(cachedPos++)); return cachedLoc; } @@ -1727,8 +1713,8 @@ void DocParser::include(const QString& fileName, const QString& identifier) inFile.close(); if (identifier.isEmpty()) { - in.insert(pos, includedStuff); - len = in.length(); + input_.insert(pos, includedStuff); + len = input_.length(); openedInputs.push(pos + includedStuff.length()); } else { @@ -1771,8 +1757,8 @@ void DocParser::include(const QString& fileName, const QString& identifier) .arg(userFriendlyFilePath)); } else { - in.insert(pos, result); - len = in.length(); + input_.insert(pos, result); + len = input_.length(); openedInputs.push(pos + result.length()); } } @@ -1918,14 +1904,14 @@ void DocParser::parseAlso() { leavePara(); skipSpacesOnLine(); - while (pos < len && in[pos] != '\n') { + while (pos < len && input_[pos] != '\n') { QString target; QString str; - if (in[pos] == '{') { + if (input_[pos] == '{') { target = getArgument(); skipSpacesOnLine(); - if (in[pos] == '{') { + if (input_[pos] == '{') { str = getArgument(); // hack for C++ to support links like \l{QString::}{count()} @@ -1937,12 +1923,12 @@ void DocParser::parseAlso() } #ifdef QDOC2_COMPAT } - else if (in[pos] == '\\' && in.mid(pos, 5) == "\\link") { + else if (input_[pos] == '\\' && input_.mid(pos, 5) == "\\link") { pos += 6; target = getArgument(); - int endPos = in.indexOf("\\endlink", pos); + int endPos = input_.indexOf("\\endlink", pos); if (endPos != -1) { - str = in.mid(pos, endPos - pos).trimmed(); + str = input_.mid(pos, endPos - pos).trimmed(); pos = endPos + 8; } #endif @@ -1960,11 +1946,11 @@ void DocParser::parseAlso() priv->addAlso(also); skipSpacesOnLine(); - if (pos < len && in[pos] == ',') { + if (pos < len && input_[pos] == ',') { pos++; skipSpacesOrOneEndl(); } - else if (in[pos] != '\n') { + else if (input_[pos] != '\n') { location().warning(tr("Missing comma in '\\%1'").arg(cmdName(CMD_SA))); } } @@ -2239,13 +2225,7 @@ Doc::Sections DocParser::getSectioningUnit() { QString name = getOptionalArgument(); - if (name == "part") { - return Doc::Part; - } - else if (name == "chapter") { - return Doc::Chapter; - } - else if (name == "section1") { + if (name == "section1") { return Doc::Section1; } else if (name == "section2") { @@ -2279,10 +2259,10 @@ QString DocParser::getBracedArgument(bool verbatim) { QString arg; int delimDepth = 0; - if (pos < (int) in.length() && in[pos] == '{') { + if (pos < (int) input_.length() && input_[pos] == '{') { pos++; - while (pos < (int) in.length() && delimDepth >= 0) { - switch (in[pos].unicode()) { + while (pos < (int) input_.length() && delimDepth >= 0) { + switch (input_[pos].unicode()) { case '{': delimDepth++; arg += QLatin1Char('{'); @@ -2296,16 +2276,16 @@ QString DocParser::getBracedArgument(bool verbatim) break; case '\\': if (verbatim) { - arg += in[pos]; + arg += input_[pos]; pos++; } else { pos++; - if (pos < (int) in.length()) { - if (in[pos].isLetterOrNumber()) + if (pos < (int) input_.length()) { + if (input_[pos].isLetterOrNumber()) break; - arg += in[pos]; - if (in[pos].isSpace()) { + arg += input_[pos]; + if (input_[pos].isSpace()) { skipAllSpaces(); } else { @@ -2315,16 +2295,17 @@ QString DocParser::getBracedArgument(bool verbatim) } break; default: - if (in[pos].isSpace() && !verbatim) + if (input_[pos].isSpace() && !verbatim) arg += QChar(' '); else - arg += in[pos]; + arg += input_[pos]; pos++; } } if (delimDepth > 0) location().warning(tr("Missing '}'")); } + endPos = pos; return arg; } @@ -2347,16 +2328,17 @@ QString DocParser::getArgument(bool verbatim) int delimDepth = 0; int startPos = pos; + endPos = pos; QString arg = getBracedArgument(verbatim); if (arg.isEmpty()) { - while ((pos < in.length()) && - ((delimDepth > 0) || ((delimDepth == 0) && !in[pos].isSpace()))) { - switch (in[pos].unicode()) { + while ((pos < input_.length()) && + ((delimDepth > 0) || ((delimDepth == 0) && !input_[pos].isSpace()))) { + switch (input_[pos].unicode()) { case '(': case '[': case '{': delimDepth++; - arg += in[pos]; + arg += input_[pos]; pos++; break; case ')': @@ -2364,22 +2346,22 @@ QString DocParser::getArgument(bool verbatim) case '}': delimDepth--; if (pos == startPos || delimDepth >= 0) { - arg += in[pos]; + arg += input_[pos]; pos++; } break; case '\\': if (verbatim) { - arg += in[pos]; + arg += input_[pos]; pos++; } else { pos++; - if (pos < (int) in.length()) { - if (in[pos].isLetterOrNumber()) + if (pos < (int) input_.length()) { + if (input_[pos].isLetterOrNumber()) break; - arg += in[pos]; - if (in[pos].isSpace()) { + arg += input_[pos]; + if (input_[pos].isSpace()) { skipAllSpaces(); } else { @@ -2389,17 +2371,18 @@ QString DocParser::getArgument(bool verbatim) } break; default: - arg += in[pos]; + arg += input_[pos]; pos++; } } + endPos = pos; if ((arg.length() > 1) && - (QString(".,:;!?").indexOf(in[pos - 1]) != -1) && + (QString(".,:;!?").indexOf(input_[pos - 1]) != -1) && !arg.endsWith("...")) { arg.truncate(arg.length() - 1); pos--; } - if (arg.length() > 2 && in.mid(pos - 2, 2) == "'s") { + if (arg.length() > 2 && input_.mid(pos - 2, 2) == "'s") { arg.truncate(arg.length() - 2); pos -= 2; } @@ -2418,10 +2401,10 @@ QString DocParser::getBracketedArgument() QString arg; int delimDepth = 0; skipSpacesOrOneEndl(); - if (pos < in.length() && in[pos] == '[') { + if (pos < input_.length() && input_[pos] == '[') { pos++; - while (pos < in.length() && delimDepth >= 0) { - switch (in[pos].unicode()) { + while (pos < input_.length() && delimDepth >= 0) { + switch (input_[pos].unicode()) { case '[': delimDepth++; arg += QLatin1Char('['); @@ -2434,11 +2417,11 @@ QString DocParser::getBracketedArgument() pos++; break; case '\\': - arg += in[pos]; + arg += input_[pos]; pos++; break; default: - arg += in[pos]; + arg += input_[pos]; pos++; } } @@ -2451,8 +2434,8 @@ QString DocParser::getBracketedArgument() QString DocParser::getOptionalArgument() { skipSpacesOrOneEndl(); - if (pos + 1 < (int) in.length() && in[pos] == '\\' && - in[pos + 1].isLetterOrNumber()) { + if (pos + 1 < (int) input_.length() && input_[pos] == '\\' && + input_[pos + 1].isLetterOrNumber()) { return QString(); } else { @@ -2471,13 +2454,13 @@ QString DocParser::getRestOfLine() do { int begin = pos; - while (pos < in.size() && in[pos] != '\n') { - if (in[pos] == '\\' && !trailingSlash) { + while (pos < input_.size() && input_[pos] != '\n') { + if (input_[pos] == '\\' && !trailingSlash) { trailingSlash = true; ++pos; - while ((pos < in.size()) && - in[pos].isSpace() && - (in[pos] != '\n')) + while ((pos < input_.size()) && + input_[pos].isSpace() && + (input_[pos] != '\n')) ++pos; } else { @@ -2488,15 +2471,15 @@ QString DocParser::getRestOfLine() if (!t.isEmpty()) t += QLatin1Char(' '); - t += in.mid(begin, pos - begin).simplified(); + t += input_.mid(begin, pos - begin).simplified(); if (trailingSlash) { t.chop(1); t = t.simplified(); } - if (pos < in.size()) + if (pos < input_.size()) ++pos; - } while (pos < in.size() && trailingSlash); + } while (pos < input_.size() && trailingSlash); return t; } @@ -2513,20 +2496,20 @@ QString DocParser::getMetaCommandArgument(const QString &cmdStr) int begin = pos; int parenDepth = 0; - while (pos < in.size() && (in[pos] != '\n' || parenDepth > 0)) { - if (in.at(pos) == '(') + while (pos < input_.size() && (input_[pos] != '\n' || parenDepth > 0)) { + if (input_.at(pos) == '(') ++parenDepth; - else if (in.at(pos) == ')') + else if (input_.at(pos) == ')') --parenDepth; ++pos; } - if (pos == in.size() && parenDepth > 0) { + if (pos == input_.size() && parenDepth > 0) { pos = begin; location().warning(tr("Unbalanced parentheses in '%1'").arg(cmdStr)); } - QString t = in.mid(begin, pos - begin).simplified(); + QString t = input_.mid(begin, pos - begin).simplified(); skipSpacesOnLine(); return t; } @@ -2536,14 +2519,14 @@ QString DocParser::getUntilEnd(int cmd) int endCmd = endCmdFor(cmd); QRegExp rx("\\\\" + cmdName(endCmd) + "\\b"); QString t; - int end = rx.indexIn(in, pos); + int end = rx.indexIn(input_, pos); if (end == -1) { location().warning(tr("Missing '\\%1'").arg(cmdName(endCmd))); - pos = in.length(); + pos = input_.length(); } else { - t = in.mid(pos, end - pos); + t = input_.mid(pos, end - pos); pos = end + rx.matchedLength(); } return t; @@ -2572,8 +2555,8 @@ bool DocParser::isBlankLine() { int i = pos; - while (i < len && in[i].isSpace()) { - if (in[i] == '\n') + while (i < len && input_[i].isSpace()) { + if (input_[i] == '\n') return true; i++; } @@ -2585,13 +2568,13 @@ bool DocParser::isLeftBraceAhead() int numEndl = 0; int i = pos; - while (i < len && in[i].isSpace() && numEndl < 2) { + while (i < len && input_[i].isSpace() && numEndl < 2) { // ### bug with '\\' - if (in[i] == '\n') + if (input_[i] == '\n') numEndl++; i++; } - return numEndl < 2 && i < len && in[i] == '{'; + return numEndl < 2 && i < len && input_[i] == '{'; } bool DocParser::isLeftBracketAhead() @@ -2599,13 +2582,13 @@ bool DocParser::isLeftBracketAhead() int numEndl = 0; int i = pos; - while (i < len && in[i].isSpace() && numEndl < 2) { + while (i < len && input_[i].isSpace() && numEndl < 2) { // ### bug with '\\' - if (in[i] == '\n') + if (input_[i] == '\n') numEndl++; i++; } - return numEndl < 2 && i < len && in[i] == '['; + return numEndl < 2 && i < len && input_[i] == '['; } /*! @@ -2613,9 +2596,9 @@ bool DocParser::isLeftBracketAhead() */ void DocParser::skipSpacesOnLine() { - while ((pos < in.length()) && - in[pos].isSpace() && - (in[pos].unicode() != '\n')) + while ((pos < input_.length()) && + input_[pos].isSpace() && + (input_[pos].unicode() != '\n')) ++pos; } @@ -2625,8 +2608,8 @@ void DocParser::skipSpacesOnLine() void DocParser::skipSpacesOrOneEndl() { int firstEndl = -1; - while (pos < (int) in.length() && in[pos].isSpace()) { - QChar ch = in[pos]; + while (pos < (int) input_.length() && input_[pos].isSpace()) { + QChar ch = input_[pos]; if (ch == '\n') { if (firstEndl == -1) { firstEndl = pos; @@ -2642,7 +2625,7 @@ void DocParser::skipSpacesOrOneEndl() void DocParser::skipAllSpaces() { - while (pos < len && in[pos].isSpace()) + while (pos < len && input_[pos].isSpace()) pos++; } @@ -2651,10 +2634,10 @@ void DocParser::skipToNextPreprocessorCommand() QRegExp rx("\\\\(?:" + cmdName(CMD_IF) + QLatin1Char('|') + cmdName(CMD_ELSE) + QLatin1Char('|') + cmdName(CMD_ENDIF) + ")\\b"); - int end = rx.indexIn(in, pos + 1); // ### + 1 necessary? + int end = rx.indexIn(input_, pos + 1); // ### + 1 necessary? if (end == -1) - pos = in.length(); + pos = input_.length(); else pos = end; } @@ -2664,8 +2647,6 @@ int DocParser::endCmdFor(int cmd) switch (cmd) { case CMD_BADCODE: return CMD_ENDCODE; - case CMD_CHAPTER: - return CMD_ENDCHAPTER; case CMD_CODE: return CMD_ENDCODE; case CMD_DIV: @@ -2690,8 +2671,6 @@ int DocParser::endCmdFor(int cmd) return CMD_NEWCODE; case CMD_OMIT: return CMD_ENDOMIT; - case CMD_PART: - return CMD_ENDPART; case CMD_QUOTATION: return CMD_ENDQUOTATION; case CMD_RAW: diff --git a/src/qdoc/doc/qdoc-manual-cmdindex.qdoc b/src/qdoc/doc/qdoc-manual-cmdindex.qdoc index feac8816a..85f5f75d9 100644 --- a/src/qdoc/doc/qdoc-manual-cmdindex.qdoc +++ b/src/qdoc/doc/qdoc-manual-cmdindex.qdoc @@ -46,7 +46,6 @@ \li \l {brief-command} {\\brief} \li \l {c-command} {\\c} \li \l {caption-command} {\\caption} - \li \l {chapter-command} {\\chapter} \li \l {class-command} {\\class} \li \l {code-command} {\\code} \li \l {codeline-command} {\\codeline} @@ -101,7 +100,6 @@ \li \l {omitvalue-command} {\\omitvalue} \li \l {overload-command} {\\overload} \li \l {page-command} {\\page} - \li \l {part-command} {\\part} \li \l {preliminary-command} {\\preliminary} \li \l {previouspage-command} {\\previouspage} \li \l {printline-command} {\\printline} diff --git a/src/qdoc/doc/qdoc-manual-markupcmds.qdoc b/src/qdoc/doc/qdoc-manual-markupcmds.qdoc index 2360f6630..3366c52c5 100644 --- a/src/qdoc/doc/qdoc-manual-markupcmds.qdoc +++ b/src/qdoc/doc/qdoc-manual-markupcmds.qdoc @@ -45,7 +45,6 @@ \li \l {brief-command} {\\brief} \li \l {c-command} {\\c} \li \l {caption-command} {\\caption} - \li \l {chapter-command} {\\chapter} \li \l {code-command} {\\code} \li \l {codeline-command} {\\codeline} \li \l {div-command} {\\div} @@ -74,7 +73,6 @@ \li \l {note-command} {\\note} \li \l {oldcode-command} {\\oldcode} \li \l {omit-command} {\\omit} - \li \l {part-command} {\\part} \li \l {printline-command} {\\printline} \li \l {printto-command} {\\printto} \li \l {printuntil-command} {\\printuntil} @@ -730,17 +728,13 @@ \title Document Structure The document structuring commands are for dividing your document - into sections. QDoc supports six kinds of sections: \c \part, \c - \chapter, \c \section1, \c \section2, \c \section3, and \c - \section4. The \c \section1..4 commands are the most useful. They + into sections. QDoc supports four levels of section: \c \section1, + \c \section2, \c \section3, and \c \section4. The section commands correspond to the traditional section, subsection, etc used in outlining. - \target part-command - \section1 \\part - - The \\part command is intended for use in a large document, like a - book. + \target section-commands + \section1 Section commands In general a document structuring command considers everything that follows it until the first line break as its argument. The @@ -748,88 +742,77 @@ spanned over several lines, make sure that each line (except the last one) is ended with a backslash. - In total, there are six levels of sections in QDoc: \c \part, \c - \chapter, \c \section1, \c \section2, \c \section3 and \c - \section4. \c \section1 to \c \section4 correspond to the - traditional section, subsection, subsubsection and - subsubsubsection. + In total, there are four levels for sections in QDoc: \c \section1, + \c \section2, \c \section3 and \c \section4. These correspond to the + traditional section, subsection, subsubsection and subsubsubsection. There is a strict ordering of the section units: \code - part - | - chapter - | - section1 - | - section2 - | - section3 - | - section4 + section1 + | + section2 + | + section3 + | + section4 \endcode - For example, a \c section1 unit can only appear as the top level - section or inside a \c chapter unit. Skipping a section unit, for - example from \c part to \c section1, is not allowed. - - You can \e begin with either of the three: \c part, \c chapter or - \c section1. + When sections are used, the first section command should be \c section1. \code / *! - \part Basic Qt + \section1 Basic Qt - This is the first part. + This is the first section. - \chapter Getting Started + \section2 Getting Started - This is the first part's first chapter. + This is the first subsection. - \section1 Hello Qt + \section3 Hello Qt - This is the first chapter's first section. + This is the first subsubsection. - \section1 Making Connections + \section3 Making Connections - This is the first chapter's second section. + This is the second subsubsection. - \section1 Using the Reference Documentation + \section3 Using the Reference Documentation - This is the first chapter's third section. + This is the third subsubsection. - \chapter Creating Dialogs + \section2 Creating Dialogs - This is the first part's second chapter. + This is the second subsection. - \section1 Subclassing QDialog + \section3 Subclassing QDialog - This is the second chapter's first section. + This is the first subsubsection. ... - \part Intermediate Qt + \section1 Intermediate Qt - This is the second part. + This is the second section. - \chapter Layout Management + \section2 Layout Management - This is the second part's first chapter. + This is the second section's first subsection. - \section1 Basic Layouts + \section3 Basic Layouts - This is the first chapter's first section. + This is the first subsubsection. ... * / @@ -842,54 +825,54 @@ <a name="Basic Qt"> <h1>Basic Qt</h1> </a> - <p>This is the first part.</p> + <p>This is the first section.</p> <a name="Getting started"> <h2>Getting Started</h2> </a> - This is the first part's first chapter.</p> + This is the first subsection.</p> <a name="Hello Qt"> <h3>Hello Qt</h3> </a> - <p>This is the first chapter's first section.</p> + <p>This is the first subsubsection.</p> <a name="Making Connections"> <h3>Making Connections</h3> </a> - <p>This is the first chapter's second section.</p> + <p>This is the second subsubsection.</p> <a name="Using the Reference Documentation"> <h3>Using the Reference Documentation</h3> </a> - <p>This is the first chapter's third section.</p> + <p>This is the third subsubsection.</p> <a name="Creating Dialogs"> <h2>Creating Dialogs</h2> </a> - <p>This is the first part's second chapter.</p> + <p>This is the second subsection.</p> <a name="Subclassing QDialog"> <h3>Subclassing QDialog</h3> </a> - <p>This is the second chapter's first section.</p> + <p>This is the first subsubsection.</p> ... <a name="Intermediate Qt"> <h1>Intermediate Qt</h1> </a> - <p>This is the second part.</p> + <p>This is the second section.</p> <a name="Layout Management"> <h2>Layout Management</h2> </a> - <p>This is the second part's first chapter.</p> + <p>This is the second section's first subsection.</p> <a name="Basic Layouts"> <h3>Basic Layouts</h3> </a> - <p>This is the first chapter's first section.</p> + <p>This is the first subsubsection.</p> ... @@ -900,21 +883,12 @@ heading appears in the automatically generated table of contents that normally appears in the upper right-hand corner of the page. - \target chapter-command - \section1 \\chapter - - The \\chapter command is intended for use in - larger documents, and divides the document into chapters. - - See \l{part} {\\part} for an explanation of the various - section units, command argument, and rendering. - \target sectionOne-command \section1 \\section1 The \\section1 command starts a new section. - See \l{part} {\\part} for an explanation of the various + See \l{section-commands} {Section commands} for an explanation of the various section units, command argument, and rendering. \target sectionTwo-command @@ -922,7 +896,7 @@ The \\section2 command starts a new section. - See \l{part} {\\part} for an explanation of the various + See \l{section-commands} {Section commands} for an explanation of the various section units, command argument, and rendering. \target sectionThree-command @@ -930,7 +904,7 @@ The \\section3 command starts a new section. - See \l{part} {\\part} for an explanation of the various + See \l{section-commands} {Section commands} for an explanation of the various section units, command argument, and rendering. \target sectionFour-command @@ -938,7 +912,7 @@ The \\section4 command starts a new section. - See \l{part} {\\part} for an explanation of the various + See \l{section-commands} {Section commands} for an explanation of the various section units, command argument, and rendering. */ @@ -1874,8 +1848,7 @@ \l {title-command} {\\title} command. \li \c {\l {Introduction to QDoc}}- The text from one of the - \l{part-command} {\\part}, \l{chapter} {\\chapter}, or \l - {sectionOne-command} {\\section} commands. + \l{section-commands} {Section commands}. \li \c {\l fontmatching} - The argument of a \l {target-command} {\\target} command. diff --git a/src/qdoc/generator.cpp b/src/qdoc/generator.cpp index 940ea7216..2336e9af8 100644 --- a/src/qdoc/generator.cpp +++ b/src/qdoc/generator.cpp @@ -894,15 +894,8 @@ void Generator::generateBody(const Node *node, CodeMarker *marker) QSet<QString> definedParams; QVector<Parameter>::ConstIterator p = func->parameters().constBegin(); while (p != func->parameters().constEnd()) { - if ((*p).name().isEmpty() && (*p).dataType() != QLatin1String("...") - && (*p).dataType() != QLatin1String("void") - && func->name() != QLatin1String("operator++") - && func->name() != QLatin1String("operator--")) { - node->doc().location().warning(tr("Missing parameter name")); - } - else { + if (!(*p).name().isEmpty()) definedParams.insert((*p).name()); - } ++p; } @@ -1638,8 +1631,7 @@ void Generator::generateOverloadedSignal(const Node* node, CodeMarker* marker) for (int i = 0; i < func->parameters().size(); ++i) { if (i != 0) code += ", "; - const Parameter &p = func->parameters().at(i); - code += p.dataType() + p.rightType(); + code += func->parameters().at(i).dataType(); } code += QLatin1Char(')'); @@ -1654,7 +1646,7 @@ void Generator::generateOverloadedSignal(const Node* node, CodeMarker* marker) code += p.dataType(); if (code[code.size()-1].isLetterOrNumber()) code += QLatin1Char(' '); - code += p.name() + p.rightType(); + code += p.name(); } code += "){ /* ... */ });"; @@ -2154,7 +2146,7 @@ void Generator::initializeTextOutput() void Generator::supplementAlsoList(const Node *node, QList<Text> &alsoList) { - if (node->type() == Node::Function) { + if (node->isFunction() && !node->isMacro()) { const FunctionNode *func = static_cast<const FunctionNode *>(node); if (func->overloadNumber() == 0) { QString alternateName; diff --git a/src/qdoc/htmlgenerator.cpp b/src/qdoc/htmlgenerator.cpp index aa7943287..a32e32caa 100644 --- a/src/qdoc/htmlgenerator.cpp +++ b/src/qdoc/htmlgenerator.cpp @@ -3993,26 +3993,42 @@ void HtmlGenerator::generateDetailedMember(const Node *node, #endif generateExtractionMark(node, MemberMark); generateKeywordAnchors(node); - QString nodeRef = refForNode(node); - if (node->isEnumType() && (etn = static_cast<const EnumNode *>(node))->flagsType()) { -#ifdef GENERATE_MAC_REFS - generateMacRef(etn->flagsType(), marker); -#endif - out() << "<h3 class=\"flags\" id=\"" << nodeRef << "\">"; - out() << "<a name=\"" + nodeRef + "\"></a>"; - generateSynopsis(etn, relative, marker, CodeMarker::Detailed); - out() << "<br/>"; - generateSynopsis(etn->flagsType(), - relative, - marker, - CodeMarker::Detailed); - out() << "</h3>\n"; + QString nodeRef = 0; + if (node->isSharedCommentNode()) { + const SharedCommentNode *scn = reinterpret_cast<const SharedCommentNode*>(node); + const QVector<Node*>& collective = scn->collective(); + out() << "<div class=\"fngroup\">\n"; + foreach (const Node* n, collective) { + if (n->isFunction()) { + nodeRef = refForNode(n); + out() << "<h3 class=\"fn fngroupitem\" id=\"" << nodeRef << "\">"; + out() << "<a name=\"" + nodeRef + "\"></a>"; + generateSynopsis(n, relative, marker, CodeMarker::Detailed); + out() << "</h3>"; + } + } + out() << "</div>"; + out() << divNavTop << '\n'; } else { - out() << "<h3 class=\"fn\" id=\"" << nodeRef << "\">"; - out() << "<a name=\"" + nodeRef + "\"></a>"; - generateSynopsis(node, relative, marker, CodeMarker::Detailed); - out() << "</h3>" << divNavTop << '\n'; + nodeRef = refForNode(node); + if (node->isEnumType() && (etn = static_cast<const EnumNode *>(node))->flagsType()) { +#ifdef GENERATE_MAC_REFS + generateMacRef(etn->flagsType(), marker); +#endif + out() << "<h3 class=\"flags\" id=\"" << nodeRef << "\">"; + out() << "<a name=\"" + nodeRef + "\"></a>"; + generateSynopsis(etn, relative, marker, CodeMarker::Detailed); + out() << "<br/>"; + generateSynopsis(etn->flagsType(), relative, marker, CodeMarker::Detailed); + out() << "</h3>\n"; + } + else { + out() << "<h3 class=\"fn\" id=\"" << nodeRef << "\">"; + out() << "<a name=\"" + nodeRef + "\"></a>"; + generateSynopsis(node, relative, marker, CodeMarker::Detailed); + out() << "</h3>" << divNavTop << '\n'; + } } generateStatus(node, marker); diff --git a/src/qdoc/location.cpp b/src/qdoc/location.cpp index b60233386..51fb45b54 100644 --- a/src/qdoc/location.cpp +++ b/src/qdoc/location.cpp @@ -406,11 +406,20 @@ void Location::emitMessage(MessageType type, if (!details.isEmpty()) result += "\n[" + details + QLatin1Char(']'); result.replace("\n", "\n "); - if (type == Error) - result.prepend(tr(": error: ")); - else if (type == Warning) { - result.prepend(tr(": warning: ")); - ++warningCount; + if (isEmpty()) { + if (type == Error) + result.prepend(tr(": error: ")); + else if (type == Warning) { + result.prepend(tr(": warning: ")); + ++warningCount; + } + } else { + if (type == Error) + result.prepend(tr(": (qdoc) error: ")); + else if (type == Warning) { + result.prepend(tr(": (qdoc) warning: ")); + ++warningCount; + } } if (type != Report) result.prepend(toString()); diff --git a/src/qdoc/main.cpp b/src/qdoc/main.cpp index 519952280..4dd307541 100644 --- a/src/qdoc/main.cpp +++ b/src/qdoc/main.cpp @@ -26,6 +26,16 @@ ** ****************************************************************************/ +#ifdef HEINOUS_SYSINC_HACK // There must be no #include before this ! +#define setlocale locale_file_name_for_clang_qdoc() { \ + static char data[] = __FILE__; \ + return data; \ + } \ + extern char *setlocale +#include <locale.h> +#undef setlocale +#endif // HEINOUS_SYSINC_HACK + #include <qglobal.h> #include <qhashfunctions.h> #include <stdlib.h> @@ -46,6 +56,7 @@ #include "jscodemarker.h" #include "qmlcodemarker.h" #include "qmlcodeparser.h" +#include "clangcodeparser.h" #include <qdatetime.h> #include <qdebug.h> #include "qtranslator.h" @@ -54,7 +65,7 @@ #endif #include "qcommandlineoption.h" #include "qcommandlineparser.h" - +#include <qhashfunctions.h> #include <algorithm> QT_BEGIN_NAMESPACE @@ -73,6 +84,7 @@ static bool noLinkErrors = false; static bool autolinkErrors = false; static bool obsoleteLinks = false; static QStringList defines; +static QStringList includesPaths; static QStringList dependModules; static QStringList indexDirs; static QString currentDir; @@ -82,6 +94,7 @@ static QHash<QString,QString> defaults; typedef QPair<QString, QTranslator*> Translator; static QList<Translator> translators; #endif +static ClangCodeParser* clangParser_ = 0; /*! Read some XML indexes containing definitions from other @@ -245,12 +258,14 @@ static void processQdocconfFile(const QString &fileName) Location::initialize(config); config.load(fileName); QString project = config.getString(CONFIG_PROJECT); - //qDebug() << "Start project:" << project; + QString moduleHeader = config.getString(CONFIG_MODULEHEADER); /* Add the defines to the configuration variables. */ QStringList defs = defines + config.getStringList(CONFIG_DEFINES); config.setStringList(CONFIG_DEFINES,defs); + QStringList incs = includesPaths + config.getStringList(CONFIG_INCLUDEPATHS); + config.setStringList(CONFIG_INCLUDEPATHS, incs); Location::terminate(); currentDir = QFileInfo(fileName).path(); @@ -266,6 +281,7 @@ static void processQdocconfFile(const QString &fileName) phase += "prepare phase "; else if (Generator::generating()) phase += "generate phase "; + QString msg = "Running qdoc for " + config.getString(CONFIG_PROJECT) + phase; Location::logToStdErr(msg); @@ -357,6 +373,10 @@ static void processQdocconfFile(const QString &fileName) qdb->newPrimaryTree(project); else qdb->setPrimaryTree(project); + if (!moduleHeader.isEmpty()) + clangParser_->setModuleHeader(moduleHeader); + else + clangParser_->setModuleHeader(project); dependModules = config.getStringList(CONFIG_DEPENDS); dependModules.removeDuplicates(); @@ -433,7 +453,6 @@ static void processQdocconfFile(const QString &fileName) Parse each header file in the set using the appropriate parser and add it to the big tree. */ - QSet<CodeParser *> usedParsers; Generator::debug("Parsing header files"); int parsed = 0; @@ -444,16 +463,13 @@ static void processQdocconfFile(const QString &fileName) ++parsed; Generator::debug(QString("Parsing " + h.key())); codeParser->parseHeaderFile(config.location(), h.key()); - usedParsers.insert(codeParser); } ++h; } - foreach (CodeParser *codeParser, usedParsers) - codeParser->doneParsingHeaderFiles(); - - usedParsers.clear(); - qdb->resolveInheritance(); + clangParser_->precompileHeaders(); + // Moved into ClangCodeParser after building PCH + //qdb->resolveInheritance(); /* Parse each source text file in the set using the appropriate parser and @@ -468,19 +484,12 @@ static void processQdocconfFile(const QString &fileName) ++parsed; Generator::debug(QString("Parsing " + s.key())); codeParser->parseSourceFile(config.location(), s.key()); - usedParsers.insert(codeParser); } ++s; } Generator::debug(QString("Parsing done.")); /* - Currently these doneParsingSourceFiles() calls do nothing. - */ - foreach (CodeParser *codeParser, usedParsers) - codeParser->doneParsingSourceFiles(); - - /* Now the primary tree has been built from all the header and source files. Resolve all the class names, function names, targets, URLs, links, and other stuff that needs resolving. @@ -546,6 +555,7 @@ private: QCommandLineOption noLinkErrorsOption, autoLinkErrorsOption, debugOption; QCommandLineOption prepareOption, generateOption, logProgressOption; QCommandLineOption singleExecOption, writeQaPagesOption; + QCommandLineOption includePathOption, includePathSystemOption, frameworkOption; }; QDocCommandLineParser::QDocCommandLineParser() @@ -568,7 +578,10 @@ QDocCommandLineParser::QDocCommandLineParser() generateOption(QStringList() << QStringLiteral("generate")), logProgressOption(QStringList() << QStringLiteral("log-progress")), singleExecOption(QStringList() << QStringLiteral("single-exec")), - writeQaPagesOption(QStringList() << QStringLiteral("write-qa-pages")) + writeQaPagesOption(QStringList() << QStringLiteral("write-qa-pages")), + includePathOption("I", "Add dir to the include path for header files.", "path"), + includePathSystemOption("isystem", "Add dir to the system include path for header files.", "path"), + frameworkOption("F", "Add macOS framework to the include path for header files.", "framework") { setApplicationDescription(QCoreApplication::translate("qdoc", "Qt documentation generator")); addHelpOption(); @@ -640,7 +653,31 @@ QDocCommandLineParser::QDocCommandLineParser() writeQaPagesOption.setDescription(QCoreApplication::translate("qdoc", "Write QA pages.")); addOption(writeQaPagesOption); + + includePathOption.setFlags(QCommandLineOption::ShortOptionStyle); + addOption(includePathOption); + + addOption(includePathSystemOption); + + frameworkOption.setFlags(QCommandLineOption::ShortOptionStyle); + addOption(frameworkOption); +} + +#ifdef HEINOUS_SYSINC_HACK +/*! + Return the system include directory used when compiling this file. + */ +static QByteArray getSystemIncludePath() +{ + const char *raw = locale_file_name_for_clang_qdoc(); + const char *slash = strrchr(raw, '/'); + if (slash == NULL) + slash = strrchr(raw, '\\'); + if (slash == NULL) + return QByteArray(); + return QByteArray(raw, slash - raw); } +#endif // HEINOUS_SYSINC_HACK void QDocCommandLineParser::process(const QCoreApplication &app) { @@ -685,6 +722,21 @@ void QDocCommandLineParser::process(const QCoreApplication &app) if (isSet(logProgressOption)) Location::startLoggingProgress(); + QDir currentDir = QDir::current(); + const auto paths = values(includePathOption); + for (const auto &i : paths) + includesPaths << "-I" << currentDir.absoluteFilePath(i); + auto paths2 = values(includePathSystemOption); +#ifdef HEINOUS_SYSINC_HACK + if (paths2.isEmpty()) + paths2 << QString(getSystemIncludePath()); +#endif // HEINOUS_SYSINC_HACK + for (const auto &i : paths2) + includesPaths << "-isystem" << currentDir.absoluteFilePath(i); + const auto paths3 = values(frameworkOption); + for (const auto &i : paths3) + includesPaths << "-F" << currentDir.absoluteFilePath(i); + /* The default indent for code is 0. The default value for false is 0. @@ -722,7 +774,8 @@ int main(int argc, char **argv) Create code parsers for the languages to be parsed, and create a tree for C++. */ - CppCodeParser cppParser; + ClangCodeParser clangParser; + clangParser_ = &clangParser; QmlCodeParser qmlParser; PureDocParser docParser; diff --git a/src/qdoc/node.cpp b/src/qdoc/node.cpp index 9ec92555d..2c8e3eae8 100644 --- a/src/qdoc/node.cpp +++ b/src/qdoc/node.cpp @@ -35,6 +35,7 @@ #include <qdebug.h> #include "generator.h" #include "tokenizer.h" +#include "puredocparser.h" QT_BEGIN_NAMESPACE @@ -69,6 +70,7 @@ void Node::initialize() goals_.insert("qmlmethod", Node::QmlMethod); goals_.insert("qmlbasictype", Node::QmlBasicType); goals_.insert("enum", Node::Enum); + goals_.insert("typealias", Node::Typedef); goals_.insert("typedef", Node::Typedef); goals_.insert("namespace", Node::Namespace); } @@ -184,11 +186,12 @@ void Node::removeRelates() /*! Returns this node's name member. Appends "()" to the returned - name, if this node is a function node. + name if this node is a function node, but not if it is a macro + because macro names normally appear without parentheses. */ QString Node::plainName() const { - if (type() == Node::Function) + if (isFunction() && !isMacro()) return name_ + QLatin1String("()"); return name_; } @@ -455,6 +458,8 @@ QString Node::nodeTypeString(unsigned char t) return "QML signal handler"; case QmlMethod: return "QML method"; + case SharedComment: + return "shared comment"; default: break; } @@ -699,33 +704,6 @@ Node::ThreadSafeness Node::inheritedThreadSafeness() const return (ThreadSafeness) safeness_; } -#if 0 -/*! - Returns the sanitized file name without the path. - If the file is an html file, the html suffix - is removed. Why? - */ -QString Node::fileBase() const -{ - QString base = name(); - if (base.endsWith(".html")) - base.chop(5); - base.replace(QRegExp("[^A-Za-z0-9]+"), " "); - base = base.trimmed(); - base.replace(QLatin1Char(' '), QLatin1Char('-')); - return base.toLower(); -} -/*! - Returns this node's Universally Unique IDentifier as a - QString. Creates the UUID first, if it has not been created. - */ -QString Node::guid() const -{ - if (uuid_.isEmpty()) - uuid_ = idForNode(); - return uuid_; -} -#endif /*! If this node is a QML or JS type node, return a pointer to @@ -941,7 +919,7 @@ FunctionNode *Aggregate::findFunctionNode(const QString& name, const QString& pa bool isQPrivateSignal = false; // Not used in the search QVector<Parameter> testParams; if (!params.isEmpty()) { - CppCodeParser* cppParser = CppCodeParser::cppParser(); + CppCodeParser* cppParser = PureDocParser::pureDocParser(); cppParser->parseParameters(params, testParams, isQPrivateSignal); } NodeList funcs = secondaryFunctionMap_.value(name); @@ -1262,14 +1240,15 @@ bool Aggregate::isSameSignature(const FunctionNode *f1, const FunctionNode *f2) return false; if (f1->isConst() != f2->isConst()) return false; + if (f1->isRef() != f2->isRef()) + return false; + if (f1->isRefRef() != f2->isRefRef()) + return false; QVector<Parameter>::ConstIterator p1 = f1->parameters().constBegin(); QVector<Parameter>::ConstIterator p2 = f2->parameters().constBegin(); while (p2 != f2->parameters().constEnd()) { if ((*p1).hasType() && (*p2).hasType()) { - if ((*p1).rightType() != (*p2).rightType()) - return false; - QString t1 = p1->dataType(); QString t2 = p2->dataType(); @@ -1568,6 +1547,7 @@ LeafNode::LeafNode(NodeType type, Aggregate *parent, const QString& name) case QmlSignalHandler: case QmlMethod: case QmlBasicType: + case SharedComment: setPageType(ApiPage); break; default: @@ -1595,6 +1575,7 @@ LeafNode::LeafNode(Aggregate* parent, NodeType type, const QString& name) case QmlSignal: case QmlSignalHandler: case QmlMethod: + case SharedComment: setPageType(ApiPage); break; default: @@ -1690,6 +1671,8 @@ void ClassNode::fixBaseClasses() // Remove private and duplicate base classes. while (i < bases_.size()) { ClassNode* bc = bases_.at(i).node_; + if (!bc) + bc = QDocDatabase::qdocDB()->findClassNode(bases_.at(i).path_); if (bc && (bc->access() == Node::Private || found.contains(bc))) { RelatedClass rc = bases_.at(i); bases_.removeAt(i); @@ -1964,6 +1947,20 @@ void TypedefNode::setAssociatedEnum(const EnumNode *enume) } /*! + \class TypeAliasNode + */ + +/*! + Constructs a TypeAliasNode for the \a aliasedType with the + specified \a name and \a parent. + */ +TypeAliasNode::TypeAliasNode(Aggregate *parent, const QString& name, const QString& aliasedType) + : TypedefNode(parent, name), aliasedType_(aliasedType) +{ + // nothing. +} + +/*! \class Parameter \brief The class Parameter contains one parameter. @@ -1972,17 +1969,11 @@ void TypedefNode::setAssociatedEnum(const EnumNode *enume) */ /*! - Constructs this parameter from the left and right types - \a dataType and rightType, the parameter \a name, and the - \a defaultValue. In practice, \a rightType is not used, - and I don't know what is was meant for. + Constructs this parameter from the \a dataType, the \a name, + and the \a defaultValue. */ -Parameter::Parameter(const QString& dataType, - const QString& rightType, - const QString& name, - const QString& defaultValue) +Parameter::Parameter(const QString& dataType, const QString& name, const QString& defaultValue) : dataType_(dataType), - rightType_(rightType), name_(name), defaultValue_(defaultValue) { @@ -1994,7 +1985,6 @@ Parameter::Parameter(const QString& dataType, */ Parameter::Parameter(const Parameter& p) : dataType_(p.dataType_), - rightType_(p.rightType_), name_(p.name_), defaultValue_(p.defaultValue_) { @@ -2007,7 +1997,6 @@ Parameter::Parameter(const Parameter& p) Parameter& Parameter::operator=(const Parameter& p) { dataType_ = p.dataType_; - rightType_ = p.rightType_; name_ = p.name_; defaultValue_ = p.defaultValue_; return *this; @@ -2020,7 +2009,7 @@ Parameter& Parameter::operator=(const Parameter& p) */ QString Parameter::reconstruct(bool value) const { - QString p = dataType_ + rightType_; + QString p = dataType_; if (!p.endsWith(QChar('*')) && !p.endsWith(QChar('&')) && !p.endsWith(QChar(' '))) p += QLatin1Char(' '); p += name_; @@ -2054,7 +2043,11 @@ FunctionNode::FunctionNode(Aggregate *parent, const QString& name) isDefaulted_(false), isFinal_(false), isOverride_(false), - reimplementedFrom_(0) + isImplicit_(false), + isRef_(false), + isRefRef_(false), + reimplementedFrom_(0), + collective_(0) { setGenus(Node::CPP); } @@ -2081,7 +2074,11 @@ FunctionNode::FunctionNode(NodeType type, Aggregate *parent, const QString& name isDefaulted_(false), isFinal_(false), isOverride_(false), - reimplementedFrom_(0) + isImplicit_(false), + isRef_(false), + isRefRef_(false), + reimplementedFrom_(0), + collective_(0) { setGenus(Node::QML); if (type == QmlMethod || type == QmlSignal) { @@ -2235,7 +2232,31 @@ void FunctionNode::addParameter(const Parameter& parameter) } /*! + Split the parameters \a t and store them in this function's + Parameter vector. */ +void FunctionNode::setParameters(const QString &t) +{ + clearParams(); + if (!t.isEmpty()) { + QStringList commaSplit = t.split(','); + foreach (QString s, commaSplit) { + QStringList blankSplit = s.split(' '); + QString pName = blankSplit.last(); + blankSplit.removeLast(); + QString pType = blankSplit.join(' '); + int i = 0; + while (i < pName.length() && !pName.at(i).isLetter()) + i++; + if (i > 0) { + pType += QChar(' ') + pName.left(i); + pName = pName.mid(i); + } + addParameter(Parameter(pType, pName)); + } + } +} + void FunctionNode::borrowParameterNames(const FunctionNode *source) { QVector<Parameter>::Iterator t = parameters_.begin(); @@ -2249,16 +2270,6 @@ void FunctionNode::borrowParameterNames(const FunctionNode *source) } /*! - If this function is a reimplementation, \a from points - to the FunctionNode of the function being reimplemented. - */ -void FunctionNode::setReimplementedFrom(FunctionNode *f) -{ - reimplementedFrom_ = f; - f->reimplementedBy_.append(this); -} - -/*! Adds the "associated" property \a p to this function node. The function might be the setter or getter for a property, for example. @@ -2311,7 +2322,7 @@ QString FunctionNode::rawParameters(bool names, bool values) const { QString raw; foreach (const Parameter ¶meter, parameters()) { - raw += parameter.dataType() + parameter.rightType(); + raw += parameter.dataType(); if (names) raw += parameter.name(); if (values) @@ -2345,17 +2356,30 @@ QString FunctionNode::signature(bool values, bool noReturnType) const QString s; if (!noReturnType && !returnType().isEmpty()) s = returnType() + QLatin1Char(' '); - s += name() + QLatin1Char('('); - QStringList reconstructedParameters = reconstructParameters(values); - int p = reconstructedParameters.size(); - if (p > 0) { - for (int i=0; i<p; i++) { - s += reconstructedParameters[i]; - if (i < (p-1)) - s += ", "; + s += name(); + if (!isMacroWithoutParams()) { + s += QLatin1Char('('); + QStringList reconstructedParameters = reconstructParameters(values); + int p = reconstructedParameters.size(); + if (p > 0) { + for (int i=0; i<p; i++) { + s += reconstructedParameters[i]; + if (i < (p-1)) + s += ", "; + } } - } - s += QLatin1Char(')'); + s += QLatin1Char(')'); + if (isMacro()) + return s; + } + if (isConst()) + s += " const"; + if (isRef()) + s += " &"; + else if (isRefRef()) + s += " &&"; + if (isImplicit()) + s += " = default"; return s; } @@ -2382,6 +2406,36 @@ void FunctionNode::debug() const } /*! + Compares this FunctionNode to the FunctionNode pointed to + by \a fn. Returns true if they describe the same function. + */ +bool FunctionNode::compare(const FunctionNode *fn) const +{ + if (!fn) + return false; + if (type() != fn->type()) + return false; + if (parent() != fn->parent()) + return false; + if (returnType_ != fn->returnType()) + return false; + if (isConst() != fn->isConst()) + return false; + if (isAttached() != fn->isAttached()) + return false; + const QVector<Parameter>& p = fn->parameters(); + if (parameters().size() != p.size()) + return false; + if (!p.isEmpty()) { + for (int i = 0; i < p.size(); ++i) { + if (parameters()[i].dataType() != p[i].dataType()) + return false; + } + } + return true; +} + +/*! \class PropertyNode This class describes one instance of using the Q_PROPERTY macro. @@ -2850,236 +2904,6 @@ QString Node::cleanId(const QString &str) return clean; } -#if 0 -/*! - Creates a string that can be used as a UUID for the node, - depending on the type and subtype of the node. Uniquenss - is not guaranteed, but it is expected that strings created - here will be unique within an XML document. Hence, the - returned string can be used as the value of an \e id - attribute. - */ -QString Node::idForNode() const -{ - const FunctionNode* func; - const TypedefNode* tdn; - QString str; - - switch (type()) { - case Node::Namespace: - str = "namespace-" + fullDocumentName(); - break; - case Node::Class: - str = "class-" + fullDocumentName(); - break; - case Node::Enum: - str = "enum-" + name(); - break; - case Node::Typedef: - tdn = static_cast<const TypedefNode*>(this); - if (tdn->associatedEnum()) { - return tdn->associatedEnum()->idForNode(); - } - else { - str = "typedef-" + name(); - } - break; - case Node::Function: - func = static_cast<const FunctionNode*>(this); - if (func->associatedProperty()) { - return func->associatedProperty()->idForNode(); - } - else { - if (func->name().startsWith("operator")) { - str.clear(); - /* - The test below should probably apply to all - functions, but for now, overloaded operators - are the only ones that produce duplicate id - attributes in the DITA XML files. - */ - if (relatesTo_) - str = "nonmember-"; - QString op = func->name().mid(8); - if (!op.isEmpty()) { - int i = 0; - while (i<op.size() && op.at(i) == ' ') - ++i; - if (i>0 && i<op.size()) { - op = op.mid(i); - } - if (!op.isEmpty()) { - i = 0; - while (i < op.size()) { - const QChar c = op.at(i); - const uint u = c.unicode(); - if ((u >= 'a' && u <= 'z') || - (u >= 'A' && u <= 'Z') || - (u >= '0' && u <= '9')) - break; - ++i; - } - str += "operator-"; - if (i>0) { - QString tail = op.mid(i); - op = op.left(i); - if (operators_.contains(op)) { - str += operators_.value(op); - if (!tail.isEmpty()) - str += QLatin1Char('-') + tail; - } - else - qDebug() << "qdoc internal error: Operator missing from operators_ map:" << op; - } - else { - str += op; - } - } - } - } - else if (parent_) { - if (parent_->isClass()) - str = "class-member-" + func->name(); - else if (parent_->isNamespace()) - str = "namespace-member-" + func->name(); - else if (parent_->isQmlType()) - str = "qml-method-" + parent_->name().toLower() + "-" + func->name(); - else if (parent_->isJsType()) - str = "js-method-" + parent_->name().toLower() + "-" + func->name(); - else if (parent_->type() == Document) { - qDebug() << "qdoc internal error: Node subtype not handled:" - << parent_->docSubtype() << func->name(); - } - else - qDebug() << "qdoc internal error: Node type not handled:" - << parent_->type() << func->name(); - - } - if (func->overloadNumber() != 0) - str += QLatin1Char('-') + QString::number(func->overloadNumber()); - } - break; - case Node::QmlType: - if (genus() == QML) - str = "qml-class-" + name(); - else - str = "js-type-" + name(); - break; - case Node::QmlBasicType: - if (genus() == QML) - str = "qml-basic-type-" + name(); - else - str = "js-basic-type-" + name(); - break; - case Node::Document: - { - switch (docSubtype()) { - case Node::Page: - case Node::HeaderFile: - str = title(); - if (str.isEmpty()) { - str = name(); - if (str.endsWith(".html")) - str.remove(str.size()-5,5); - } - str.replace(QLatin1Char('/'), QLatin1Char('-')); - break; - case Node::File: - str = name(); - str.replace(QLatin1Char('/'), QLatin1Char('-')); - break; - case Node::Example: - str = name(); - str.replace(QLatin1Char('/'), QLatin1Char('-')); - break; - default: - qDebug() << "ERROR: A case was not handled in Node::idForNode():" - << "docSubtype():" << docSubtype() << "type():" << type(); - break; - } - } - break; - case Node::Group: - case Node::Module: - str = title(); - if (str.isEmpty()) { - str = name(); - if (str.endsWith(".html")) - str.remove(str.size()-5,5); - } - str.replace(QLatin1Char('/'), QLatin1Char('-')); - break; - case Node::QmlModule: - if (genus() == QML) - str = "qml-module-" + name(); - else - str = "js-module-" + name(); - break; - case Node::QmlProperty: - if (genus() == QML) - str = "qml-"; - else - str = "js-"; - if (isAttached()) - str += "attached-property-" + name(); - else - str += "property-" + name(); - break; - case Node::QmlPropertyGroup: - { - Node* n = const_cast<Node*>(this); - if (genus() == QML) - str = "qml-propertygroup-" + n->name(); - else - str = "js-propertygroup-" + n->name(); - } - break; - case Node::Property: - str = "property-" + name(); - break; - case Node::QmlSignal: - if (genus() == QML) - str = "qml-signal-" + name(); - else - str = "js-signal-" + name(); - break; - case Node::QmlSignalHandler: - if (genus() == QML) - str = "qml-signal-handler-" + name(); - else - str = "js-signal-handler-" + name(); - break; - case Node::QmlMethod: - func = static_cast<const FunctionNode*>(this); - if (genus() == QML) - str = "qml-method-"; - else - str = "js-method-"; - str += parent_->name().toLower() + "-" + func->name(); - if (func->overloadNumber() != 0) - str += QLatin1Char('-') + QString::number(func->overloadNumber()); - break; - case Node::Variable: - str = "var-" + name(); - break; - default: - qDebug() << "ERROR: A case was not handled in Node::idForNode():" - << "type():" << type() << "docSubtype():" << docSubtype(); - break; - } - if (str.isEmpty()) { - qDebug() << "ERROR: A link text was empty in Node::idForNode():" - << "type():" << type() << "docSubtype():" << docSubtype() - << "name():" << name() - << "title():" << title(); - } - else { - str = cleanId(str); - } - return str; -} -#endif - /*! Prints the inner node's list of children. For debugging only. diff --git a/src/qdoc/node.h b/src/qdoc/node.h index 15fda389b..c550eff96 100644 --- a/src/qdoc/node.h +++ b/src/qdoc/node.h @@ -52,6 +52,7 @@ class FunctionNode; class PropertyNode; class CollectionNode; class QmlPropertyNode; +class SharedCommentNode; typedef QList<Node*> NodeList; typedef QList<PropertyNode*> PropNodeList; @@ -87,6 +88,7 @@ public: QmlSignalHandler, QmlMethod, QmlBasicType, + SharedComment, LastType }; @@ -205,6 +207,7 @@ public: virtual bool isJsBasicType() const { return false; } virtual bool isEnumType() const { return false; } virtual bool isTypedef() const { return false; } + virtual bool isTypeAlias() const { return false; } virtual bool isExample() const { return false; } virtual bool isExampleFile() const { return false; } virtual bool isHeaderFile() const { return false; } @@ -216,6 +219,7 @@ public: virtual bool isQtQuickNode() const { return false; } virtual bool isAbstract() const { return false; } virtual bool isProperty() const { return false; } + virtual bool isVariable() const { return false; } virtual bool isQmlProperty() const { return false; } virtual bool isJsProperty() const { return false; } virtual bool isQmlPropertyGroup() const { return false; } @@ -232,6 +236,10 @@ public: virtual bool isReadOnly() const { return false; } virtual bool isDefault() const { return false; } virtual bool isExternalPage() const { return false; } + virtual bool isImplicit() const { return false; } + virtual bool isInCollective() const { return false; } + virtual bool isSharedCommentNode() const { return false; } + virtual bool isMacro() const { return false; } virtual void addMember(Node* ) { } virtual bool hasMembers() const { return false; } virtual bool hasNamespaces() const { return false; } @@ -257,6 +265,7 @@ public: virtual Tree* tree() const; virtual void findChildren(const QString& , NodeList& nodes) const { nodes.clear(); } virtual void setNoAutoList(bool ) { } + virtual void setCollectiveNode(SharedCommentNode* ) { } bool isIndexNode() const { return indexNodeFlag_; } NodeType type() const { return (NodeType) nodeType_; } virtual DocSubtype docSubtype() const { return NoSubtype; } @@ -273,6 +282,7 @@ public: virtual void setObsoleteLink(const QString& ) { }; virtual void setQtVariable(const QString& ) { } virtual QString qtVariable() const { return QString(); } + virtual bool hasTag(const QString& ) const { return false; } const QMap<LinkType, QPair<QString,QString> >& links() const { return linkMap_; } void setLink(LinkType linkType, const QString &link, const QString &desc); @@ -322,7 +332,6 @@ public: virtual void setOutputSubdirectory(const QString& t) { outSubDir_ = t; } QString fullDocumentName() const; static QString cleanId(const QString &str); - //QString idForNode() const; static FlagValue toFlagValue(bool b); static bool fromFlagValue(FlagValue fv, bool defaultValue); @@ -824,6 +833,19 @@ private: const EnumNode* associatedEnum_; }; +class TypeAliasNode : public TypedefNode +{ + public: + TypeAliasNode(Aggregate* parent, const QString& name, const QString& aliasedType); + virtual ~TypeAliasNode() { } + + virtual bool isTypeAlias() const { return true; } + QString aliasedType() { return aliasedType_; } + + private: + QString aliasedType_; +}; + inline void EnumNode::setFlagsType(TypedefNode* t) { flagsType_ = t; @@ -835,7 +857,6 @@ class Parameter public: Parameter() {} Parameter(const QString& dataType, - const QString& rightType = QString(), const QString& name = QString(), const QString& defaultValue = QString()); Parameter(const Parameter& p); @@ -844,21 +865,35 @@ public: void setName(const QString& name) { name_ = name; } - bool hasType() const { return dataType_.length() + rightType_.length() > 0; } + bool hasType() const { return dataType_.length() > 0; } const QString& dataType() const { return dataType_; } - const QString& rightType() const { return rightType_; } const QString& name() const { return name_; } const QString& defaultValue() const { return defaultValue_; } + void setDefaultValue(const QString& defaultValue) { defaultValue_ = defaultValue; } QString reconstruct(bool value = false) const; public: QString dataType_; - QString rightType_; // mws says remove this 04/08/2015 QString name_; QString defaultValue_; }; +class SharedCommentNode : public LeafNode +{ + public: + SharedCommentNode(Aggregate* parent, int count) + : LeafNode(Node::SharedComment, parent, QString()) { collective_.reserve(count); } + virtual ~SharedCommentNode() { collective_.clear(); } + + virtual bool isSharedCommentNode() const Q_DECL_OVERRIDE { return true; } + void append(Node* n) { collective_.append(n); } + const QVector<Node*>& collective() const { return collective_; } + + private: + QVector<Node*> collective_; +}; + //friend class QTypeInfo<Parameter>; //Q_DECLARE_TYPEINFO(Parameter, Q_MOVABLE_TYPE); @@ -901,8 +936,9 @@ public: void setReimplemented(bool b); void addParameter(const Parameter& parameter); inline void setParameters(const QVector<Parameter>& parameters); + void setParameters(const QString &t); void borrowParameterNames(const FunctionNode* source); - void setReimplementedFrom(FunctionNode* from); + void setReimplementedFrom(FunctionNode* from) { reimplementedFrom_ = from; } const QString& returnType() const { return returnType_; } QString metaness() const; @@ -915,7 +951,9 @@ public: bool isSomeCtor() const { return isCtor() || isCCtor() || isMCtor(); } bool isMacroWithParams() const { return (metaness_ == MacroWithParams); } bool isMacroWithoutParams() const { return (metaness_ == MacroWithoutParams); } - bool isMacro() const { return (isMacroWithParams() || isMacroWithoutParams()); } + bool isMacro() const Q_DECL_OVERRIDE { + return (isMacroWithParams() || isMacroWithoutParams()); + } bool isSignal() const { return (metaness_ == Signal); } bool isSlot() const { return (metaness_ == Slot); } bool isCtor() const { return (metaness_ == Ctor); } @@ -924,6 +962,9 @@ public: bool isMCtor() const { return (metaness_ == MCtor); } bool isCAssign() const { return (metaness_ == CAssign); } bool isMAssign() const { return (metaness_ == MAssign); } + bool isSpecialMemberFunction() const { + return (isDtor() || isCCtor() || isMCtor() || isCAssign() || isMAssign()); + } bool isNonvirtual() const { return (virtualness_ == NonVirtual); } bool isVirtual() const { return (virtualness_ == NormalVirtual); } bool isPureVirtual() const { return (virtualness_ == PureVirtual); } @@ -945,12 +986,12 @@ public: bool isJsMethod() const override { return (type() == Node::QmlMethod) && (genus() == Node::JS); } + QVector<Parameter> ¶meters() { return parameters_; } const QVector<Parameter>& parameters() const { return parameters_; } void clearParams() { parameters_.clear(); } QStringList parameterNames() const; QString rawParameters(bool names = false, bool values = false) const; const FunctionNode* reimplementedFrom() const { return reimplementedFrom_; } - const QList<FunctionNode*> &reimplementedBy() const { return reimplementedBy_; } const PropNodeList& associatedProperties() const { return associatedProperties_; } const QStringList& parentPath() const { return parentPath_; } bool hasAssociatedProperties() const { return !associatedProperties_.isEmpty(); } @@ -990,6 +1031,24 @@ public: void setOverride(bool b) { isOverride_ = b; } bool isOverride() const { return isOverride_; } + void setImplicit(bool b) { isImplicit_ = b; } + bool isImplicit() const Q_DECL_OVERRIDE { return isImplicit_; } + + void setRef(bool b) { isRef_ = b; } + bool isRef() const { return isRef_; } + + void setRefRef(bool b) { isRefRef_ = b; } + bool isRefRef() const { return isRefRef_; } + + bool isInCollective() const Q_DECL_OVERRIDE { return (collective_ != 0); } + const SharedCommentNode* collective() const { return collective_; } + void setCollectiveNode(SharedCommentNode* t) Q_DECL_OVERRIDE { collective_ = t; } + + virtual bool hasTag(const QString& t) const Q_DECL_OVERRIDE { return (tag_ == t); } + void setTag(const QString& t) { tag_ = t; } + const QString &tag() const { return tag_; } + bool compare(const FunctionNode *fn) const; + private: void addAssociatedProperty(PropertyNode* property); @@ -1010,11 +1069,15 @@ private: bool isDefaulted_ : 1; bool isFinal_ : 1; bool isOverride_ : 1; + bool isImplicit_ : 1; + bool isRef_ : 1; + bool isRefRef_ : 1; unsigned char overloadNumber_; QVector<Parameter> parameters_; const FunctionNode* reimplementedFrom_; PropNodeList associatedProperties_; - QList<FunctionNode*> reimplementedBy_; + SharedCommentNode* collective_; + QString tag_; }; class PropertyNode : public LeafNode @@ -1115,17 +1178,18 @@ public: VariableNode(Aggregate* parent, const QString &name); virtual ~VariableNode() { } - void setLeftType(const QString &leftType) { lrftType_ = leftType; } + void setLeftType(const QString &leftType) { leftType_ = leftType; } void setRightType(const QString &rightType) { rightType_ = rightType; } void setStatic(bool b) { static_ = b; } - const QString &leftType() const { return lrftType_; } + const QString &leftType() const { return leftType_; } const QString &rightType() const { return rightType_; } - QString dataType() const { return lrftType_ + rightType_; } + QString dataType() const { return leftType_ + rightType_; } bool isStatic() const { return static_; } + virtual bool isVariable() const { return true; } private: - QString lrftType_; + QString leftType_; QString rightType_; bool static_; }; diff --git a/src/qdoc/puredocparser.cpp b/src/qdoc/puredocparser.cpp index 16a6e831f..50c6a7bba 100644 --- a/src/qdoc/puredocparser.cpp +++ b/src/qdoc/puredocparser.cpp @@ -42,11 +42,14 @@ QT_BEGIN_NAMESPACE +PureDocParser *PureDocParser::pureParser_ = 0; + /*! Constructs the pure doc parser. */ PureDocParser::PureDocParser() { + pureParser_ = this; } /*! @@ -54,6 +57,7 @@ PureDocParser::PureDocParser() */ PureDocParser::~PureDocParser() { + pureParser_ = 0; } /*! @@ -180,7 +184,23 @@ bool PureDocParser::processQdocComments() ArgList::ConstIterator a = args.cbegin(); while (a != args.cend()) { Doc nodeDoc = doc; - Node* node = processTopicCommand(nodeDoc,topic,*a); + Node *node = 0; + if (topic == COMMAND_FN) { + node = parserForLanguage("Clang")->parseFnArg(doc.location(), a->first); + } else if (topic == COMMAND_MACRO) { + node = parserForLanguage("Clang")->parseMacroArg(doc.location(), a->first); + } else if (topic == COMMAND_QMLSIGNAL || + topic == COMMAND_QMLMETHOD || + topic == COMMAND_QMLATTACHEDSIGNAL || + topic == COMMAND_QMLATTACHEDMETHOD || + topic == COMMAND_JSSIGNAL || + topic == COMMAND_JSMETHOD || + topic == COMMAND_JSATTACHEDSIGNAL || + topic == COMMAND_JSATTACHEDMETHOD) { + node = parseOtherFuncArg(topic, doc.location(), a->first); + } else { + node = processTopicCommand(nodeDoc, topic, *a); + } if (node != 0) { nodes.append(node); docs.append(nodeDoc); diff --git a/src/qdoc/puredocparser.h b/src/qdoc/puredocparser.h index f03a75d97..fdd4f50c6 100644 --- a/src/qdoc/puredocparser.h +++ b/src/qdoc/puredocparser.h @@ -55,8 +55,11 @@ public: QStringList sourceFileNameFilter() override; void parseSourceFile(const Location& location, const QString& filePath) override; + static PureDocParser *pureDocParser() { return pureParser_; } + private: bool processQdocComments(); + static PureDocParser *pureParser_; }; QT_END_NAMESPACE diff --git a/src/qdoc/qdoc.pro b/src/qdoc/qdoc.pro index 9963fdf08..1dafeee37 100644 --- a/src/qdoc/qdoc.pro +++ b/src/qdoc/qdoc.pro @@ -1,3 +1,4 @@ + !force_bootstrap { requires(qtConfig(xmlstreamwriter)) } @@ -10,6 +11,11 @@ qtHaveModule(qmldevtools-private) { DEFINES += QT_NO_DECLARATIVE } +LIBS += $$CLANG_LIBS +INCLUDEPATH += $$CLANG_INCLUDEPATH +!disable_external_rpath: QMAKE_RPATHDIR += $$CLANG_LIBDIR +DEFINES += $$shell_quote(CLANG_RESOURCE_DIR=\"$${CLANG_LIBDIR}/clang/$${CLANG_VERSION}/include\") + DEFINES += \ QDOC2_COMPAT @@ -19,9 +25,13 @@ INCLUDEPATH += $$QT_SOURCE_TREE/src/tools/qdoc \ # Increase the stack size on MSVC to 4M to avoid a stack overflow win32-msvc*:{ QMAKE_LFLAGS += /STACK:4194304 +} else { + # (MSVC objects to defining a function in the locale.h dllimport context) + DEFINES += HEINOUS_SYSINC_HACK } HEADERS += atom.h \ + clangcodeparser.h \ codechunk.h \ codemarker.h \ codeparser.h \ @@ -49,6 +59,7 @@ HEADERS += atom.h \ webxmlgenerator.h SOURCES += atom.cpp \ + clangcodeparser.cpp \ codechunk.cpp \ codemarker.cpp \ codeparser.cpp \ diff --git a/src/qdoc/qdocdatabase.h b/src/qdoc/qdocdatabase.h index 7ff840b3b..9b3e7628b 100644 --- a/src/qdoc/qdocdatabase.h +++ b/src/qdoc/qdocdatabase.h @@ -348,6 +348,8 @@ class QDocDatabase const CollectionNode* getCollectionNode(const QString& name, Node::Genus genus) { return forest_.getCollectionNode(name, genus); } + Node *findFunctionNodeForTag(QString tag) { return primaryTree()->findFunctionNodeForTag(tag); } + Node* findMacroNode(const QString &t) { return primaryTree()->findMacroNode(t); } private: const Node* findNodeForTarget(QStringList& targetPath, diff --git a/src/qdoc/qdocindexfiles.cpp b/src/qdoc/qdocindexfiles.cpp index 492fc0897..c97ed2f46 100644 --- a/src/qdoc/qdocindexfiles.cpp +++ b/src/qdoc/qdocindexfiles.cpp @@ -494,6 +494,11 @@ void QDocIndexFiles::readIndexSection(QXmlStreamReader& reader, functionNode->setIsDefaulted(attributes.value(QLatin1String("default")) == QLatin1String("true")); functionNode->setFinal(attributes.value(QLatin1String("final")) == QLatin1String("true")); functionNode->setOverride(attributes.value(QLatin1String("override")) == QLatin1String("true")); + int refness = attributes.value(QLatin1String("refness")).toUInt(); + if (refness == 1) + functionNode->setRef(true); + else if (refness == 2) + functionNode->setRefRef(true); if (attributes.value(QLatin1String("overload")) == QLatin1String("true")) { functionNode->setOverloadFlag(true); functionNode->setOverloadNumber(attributes.value(QLatin1String("overload-number")).toUInt()); @@ -518,8 +523,7 @@ void QDocIndexFiles::readIndexSection(QXmlStreamReader& reader, if (reader.name() == QLatin1String("parameter")) { // Do not use the default value for the parameter; it is not // required, and has been known to cause problems. - Parameter parameter(childAttributes.value(QLatin1String("left")).toString(), - childAttributes.value(QLatin1String("right")).toString(), + Parameter parameter(childAttributes.value(QLatin1String("type")).toString(), childAttributes.value(QLatin1String("name")).toString(), QString()); // childAttributes.value(QLatin1String("default")) functionNode->addParameter(parameter); @@ -1020,6 +1024,8 @@ bool QDocIndexFiles::generateIndexSection(QXmlStreamWriter& writer, ClassNode* n = related.node_; if (n) baseStrings.insert(n->fullName()); + else if (!related.path_.isEmpty()) + baseStrings.insert(related.path_.join(QLatin1String("::"))); } if (!baseStrings.isEmpty()) { @@ -1195,6 +1201,10 @@ bool QDocIndexFiles::generateIndexSection(QXmlStreamWriter& writer, writer.writeAttribute("default", functionNode->isDefaulted() ? "true" : "false"); writer.writeAttribute("final", functionNode->isFinal() ? "true" : "false"); writer.writeAttribute("override", functionNode->isOverride() ? "true" : "false"); + if (functionNode->isRef()) + writer.writeAttribute("refness", QString::number(1)); + else if (functionNode->isRefRef()) + writer.writeAttribute("refness", QString::number(2)); if (functionNode->isOverload()) writer.writeAttribute("overload-number", QString::number(functionNode->overloadNumber())); if (functionNode->relates()) { @@ -1234,8 +1244,7 @@ bool QDocIndexFiles::generateIndexSection(QXmlStreamWriter& writer, for (int i = 0; i < functionNode->parameters().size(); ++i) { Parameter parameter = functionNode->parameters()[i]; writer.writeStartElement("parameter"); - writer.writeAttribute("left", parameter.dataType()); - writer.writeAttribute("right", parameter.rightType()); + writer.writeAttribute("type", parameter.dataType()); writer.writeAttribute("name", parameter.name()); writer.writeAttribute("default", parameter.defaultValue()); writer.writeEndElement(); // parameter diff --git a/src/qdoc/qmlcodeparser.cpp b/src/qdoc/qmlcodeparser.cpp index 31da874fe..31775bb1b 100644 --- a/src/qdoc/qmlcodeparser.cpp +++ b/src/qdoc/qmlcodeparser.cpp @@ -204,14 +204,6 @@ void QmlCodeParser::parseSourceFile(const Location& location, const QString& fil #endif } -/*! - Performs cleanup after qdoc is done parsing all the QML files. - Currently, no cleanup is required. - */ -void QmlCodeParser::doneParsingSourceFiles() -{ -} - static QSet<QString> topicCommands_; /*! Returns the set of strings representing the topic commands. diff --git a/src/qdoc/qmlcodeparser.h b/src/qdoc/qmlcodeparser.h index 0a63a019d..3788175f3 100644 --- a/src/qdoc/qmlcodeparser.h +++ b/src/qdoc/qmlcodeparser.h @@ -61,7 +61,6 @@ public: QString language() override; QStringList sourceFileNameFilter() override; void parseSourceFile(const Location& location, const QString& filePath) override; - void doneParsingSourceFiles() override; #ifndef QT_NO_DECLARATIVE /* Copied from src/declarative/qml/qdeclarativescriptparser.cpp */ diff --git a/src/qdoc/qmlvisitor.cpp b/src/qdoc/qmlvisitor.cpp index ac64348d6..9ebf422cf 100644 --- a/src/qdoc/qmlvisitor.cpp +++ b/src/qdoc/qmlvisitor.cpp @@ -382,7 +382,7 @@ bool QmlSignatureParser::matchParameter() readToken(); } } - func_->addParameter(Parameter(dataType.toString(), "", name, defaultValue.toString())); + func_->addParameter(Parameter(dataType.toString(), name, defaultValue.toString())); return true; } diff --git a/src/qdoc/tree.cpp b/src/qdoc/tree.cpp index 0354386ee..9669f3991 100644 --- a/src/qdoc/tree.cpp +++ b/src/qdoc/tree.cpp @@ -1523,4 +1523,53 @@ TargetList* Tree::getTargetList(const QString& module) return targetListMap_->value(module); } +/*! + Search this tree recursively from \a parent to find a function + node with the specified \a tag. If no function node is found + with the required \a tag, return 0. + */ +Node* Tree::findFunctionNodeForTag(const QString &tag, Aggregate* parent) +{ + if (!parent) + parent = root(); + const NodeList& children = parent->childNodes(); + for (Node *n : children) { + if (n && n->isFunction() && n->hasTag(tag)) + return n; + } + for (Node *n : children) { + if (n && n->isAggregate()) { + Aggregate* a = static_cast<Aggregate*>(n); + n = findFunctionNodeForTag(tag, a); + if (n) + return n; + } + } + return 0; +} + +/*! + There should only be one macro node for macro name \a t. + The macro node is not built until the \macro command is seen. + */ +Node *Tree::findMacroNode(const QString &t, const Aggregate *parent) +{ + if (!parent) + parent = root(); + const NodeList &children = parent->childNodes(); + for (Node *n : children) { + if (n && (n->isMacro() || n->isFunction()) && n->name() == t) + return n; + } + for (Node *n : children) { + if (n && n->isAggregate()) { + Aggregate *a = static_cast<Aggregate*>(n); + n = findMacroNode(t, a); + if (n) + return n; + } + } + return 0; +} + QT_END_NAMESPACE diff --git a/src/qdoc/tree.h b/src/qdoc/tree.h index 0cb07aff7..1aac6d664 100644 --- a/src/qdoc/tree.h +++ b/src/qdoc/tree.h @@ -216,6 +216,8 @@ class Tree bool broken); TargetList* getTargetList(const QString& module); QStringList getTargetListKeys() { return targetListMap_->keys(); } + Node* findFunctionNodeForTag(const QString &tag, Aggregate* parent = 0); + Node *findMacroNode(const QString &t, const Aggregate *parent = 0); public: const QString& camelCaseModuleName() const { return camelCaseModuleName_; } diff --git a/src/src.pro b/src/src.pro index 41064a5d5..ed370052c 100644 --- a/src/src.pro +++ b/src/src.pro @@ -13,13 +13,14 @@ qtHaveModule(widgets) { } SUBDIRS += linguist \ - qdoc \ qtattributionsscanner qtConfig(library) { !android|android_app: SUBDIRS += qtplugininfo } +config_clang: SUBDIRS += qdoc + if(!android|android_app):!uikit: SUBDIRS += qtpaths mac { |