diff options
author | Liang Qi <liang.qi@qt.io> | 2017-11-13 14:48:12 +0100 |
---|---|---|
committer | Liang Qi <liang.qi@qt.io> | 2017-11-13 14:48:12 +0100 |
commit | 2f6aaed6dc157121e95b5dd210aea8bbd1338722 (patch) | |
tree | 4899d6e15980d1bfecb462ddf637b0445877a203 /src/qdoc/clangcodeparser.cpp | |
parent | 07c2c0604bbfac064c8303f2a68ac54f8b7a78fc (diff) | |
parent | 995c91cd814d47e5ac3e20aca1d70d6e66f39cf2 (diff) | |
download | qttools-2f6aaed6dc157121e95b5dd210aea8bbd1338722.tar.gz |
Merge remote-tracking branch 'origin/wip/qdoc-clang' into dev
Conflicts:
src/qdoc/cppcodeparser.h
src/qdoc/location.cpp
src/qdoc/qmlcodeparser.h
Change-Id: I2e579ca5d83cd1c4b42acc9a07066d800cbc02cb
Diffstat (limited to 'src/qdoc/clangcodeparser.cpp')
-rw-r--r-- | src/qdoc/clangcodeparser.cpp | 1658 |
1 files changed, 1658 insertions, 0 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 |