summaryrefslogtreecommitdiff
path: root/src/qdoc/clangcodeparser.cpp
diff options
context:
space:
mode:
authorOlivier Goffart <ogoffart@woboq.com>2016-03-03 13:12:47 +0100
committerMartin Smith <martin.smith@qt.io>2017-08-10 07:31:47 +0000
commitb032aee53c8fb6807485186f276ca25b86ac1251 (patch)
treea23ca6f0a1e2d13b2629299a49234035b1fb3863 /src/qdoc/clangcodeparser.cpp
parent3c74e2594595f06b3e07f441b254ee7f57b2c770 (diff)
downloadqttools-b032aee53c8fb6807485186f276ca25b86ac1251.tar.gz
Use clang as a parser in qdoc
The file qt_find_clang.prf is inspired by qtcreator's clang_installation.pri. The code from the while loop in ClangVisitor::parseProperty contains code moved from CppCodeParser::matchProperty. The code in the for loop of ClangCodeParser::parseSourceFile (from the "Doc parses the comment"), is mostly moved from CppCodeParser::matchDocsAndStuff. In CppCodeParser, most of the code is removed since clang is used for parsing. We just need to leave enough to parse the declaration in the comments which still use the old parser (\fn, ...) Known issues: - When the parameter name is a comment, it is lost. (e.g. QObject::eventFilter(QObject * /* watched */, QEvent * /* event */) - I can't compute default parameters when they are expanded from a macro. (e.g. QObject::tr) - Instances of #ifndef Q_QDOC need to be reviewed Change-Id: I92d4ca4fc52810d9d3de433147a9953eea3a1802 Reviewed-by: Topi Reiniƶ <topi.reinio@qt.io>
Diffstat (limited to 'src/qdoc/clangcodeparser.cpp')
-rw-r--r--src/qdoc/clangcodeparser.cpp1034
1 files changed, 1034 insertions, 0 deletions
diff --git a/src/qdoc/clangcodeparser.cpp b/src/qdoc/clangcodeparser.cpp
new file mode 100644
index 000000000..ba270a613
--- /dev/null
+++ b/src/qdoc/clangcodeparser.cpp
@@ -0,0 +1,1034 @@
+/****************************************************************************
+**
+** 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 <qregularexpression.h>
+#include <qtemporarydir.h>
+#include "generator.h"
+
+#include <clang-c/Index.h>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ Call clang_visitChildren on the given cursor with the lambda as a callback
+ T can be any functor that is callable with a CXCursor parametter and returns a CXChildVisitResult
+ (in other word compatible with function<CXChildVisitResult(CXCursor)>
+ */
+template <typename T> bool visitChildrenLambda(CXCursor cursor, T &&lambda)
+{
+ auto 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);
+ if (t1 != t2) {
+ QString parentScope = parent->name() + QLatin1String("::");
+ if (t1.remove(parentScope) != t2.remove(parentScope)) {
+ 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;
+ }
+}
+
+class ClangVisitor {
+public:
+ ClangVisitor(QDocDatabase *qdb, const QSet<QString> &allHeaders)
+ : qdb_(qdb), parent_(qdb->primaryTreeRoot()), allHeaders_(allHeaders)
+ {}
+
+ 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;
+ }
+
+ Node *nodeForCommentAtLocation(CXSourceLocation loc, CXSourceLocation nextCommentLoc);
+private:
+ /*! \class 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);
+ void parseProperty(const QString &spelling, const Location &loc);
+ void readParameterNamesAndAttributes(FunctionNode* fn, 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;
+}
+
+CXChildVisitResult ClangVisitor::visitHeader(CXCursor cursor, CXSourceLocation loc)
+{
+ auto kind = clang_getCursorKind(cursor);
+ switch (kind) {
+ case CXCursor_StructDecl:
+ case CXCursor_UnionDecl:
+ 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));
+
+ if (parent_ && parent_->findChildNode(className, Node::Class)) {
+ return CXChildVisit_Continue;
+ }
+
+ ClassNode *classe = new ClassNode(parent_, 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);
+ if (!baseNode || !baseNode->isClass())
+ return CXChildVisit_Continue;
+ auto classe = static_cast<ClassNode*>(parent_);
+ 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);
+ 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);
+ // 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) {
+ auto typeName = fromCXString(clang_getTypeSpelling(clang_getArgType(funcType, i)));
+ pvect.append(Parameter(adjustTypeName(typeName)));
+ }
+ 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;
+ }
+ case CXCursor_EnumDecl: {
+ if (findNodeForCursor(qdb_, cursor)) // Was already parsed, propably in another translation unit
+ return CXChildVisit_Continue;
+ auto en = new EnumNode(parent_, fromCXString(clang_getCursorSpelling(cursor)));
+ 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)
+{
+ const QLatin1String metaKeyword("READ|WRITE|CONSTANT|FINAL|REVISION|MEMBER|RESET|SCRIPTABLE|STORED|WRITE|DESIGNABLE|EDITABLE|NOTIFY|USER");
+ static QRegularExpression typeNameRx(QLatin1String("^[^(]*\\((?<type>.*?)\\s*(?<name>[a-zA-Z0-9_]+)\\s+(")
+ + metaKeyword + QLatin1String(")\\s"));
+ auto match = typeNameRx.match(spelling);
+ if (!match.hasMatch()) {
+ qWarning() << "ERROR PARSING " << spelling;
+ return;
+ }
+ auto type = match.captured(QStringLiteral("type"));
+ auto name = match.captured(QStringLiteral("name"));
+ auto *property = new PropertyNode(parent_, name);
+ property->setAccess(Node::Public);
+ property->setLocation(loc);
+ property->setDataType(type);
+
+ static QRegularExpression nextKeyword(QLatin1String("\\s(?<key>") + metaKeyword
+ + QLatin1String(")\\s+(?<value>.*?)(\\s*\\)$|\\s+(") + metaKeyword + QLatin1String("))"));
+ int pos = match.capturedEnd(QStringLiteral("name"));
+ while ((match = nextKeyword.match(spelling, pos)).hasMatch()) {
+ pos = match.capturedEnd(QStringLiteral("value"));
+ auto key = match.capturedRef(QStringLiteral("key"));
+ auto value = match.captured(QStringLiteral("value"));
+ // Keywords with no associated values
+ if (key == "CONSTANT") {
+ property->setConstant();
+ } else if (key == "FINAL") {
+ property->setFinal();
+ } else 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.
+}
+
+/*!
+
+ */
+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();
+}
+
+/*!
+ */
+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());
+}
+
+
+/*!
+ 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)
+{
+ 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
+ };
+ std::vector<const char *> args(std::begin(defaultArgs), std::end(defaultArgs));
+
+ auto moreArgs = includePaths_;
+ if (moreArgs.isEmpty()) {
+ // Try to guess the include paths if none were given.
+ auto forest = qdb_->searchOrder();
+ QByteArray installDocDir = Config::installDir.toUtf8();
+ QByteArray version = qdb_->version().toUtf8();
+ moreArgs += "-I" + installDocDir + "/../include";
+ moreArgs += "-I" + filePath.toUtf8() + "/../";
+ moreArgs += "-I" + filePath.toUtf8() + "/../../";
+ for (const auto &s : forest) {
+ QByteArray module = s->camelCaseModuleName().toUtf8();
+ moreArgs += "-I" + installDocDir + "/../include/" + module;
+ moreArgs += "-I" + installDocDir + "/../include/" + module + "/" + version;
+ moreArgs += "-I" + installDocDir + "/../include/" + module + "/" + version + "/" + module;
+ }
+ }
+
+ for (const auto &p : qAsConst(moreArgs))
+ args.push_back(p.constData());
+
+ auto flags = CXTranslationUnit_Incomplete | CXTranslationUnit_SkipFunctionBodies;
+ CXIndex index = clang_createIndex(1, 1);
+
+ if (!pchFileDir_) {
+ pchFileDir_.reset(new QTemporaryDir(QDir::tempPath() + QLatin1String("/qdoc_pch")));
+ if (pchFileDir_->isValid()) {
+ const QByteArray module = qdb_->primaryTreeRoot()->tree()->camelCaseModuleName().toUtf8();
+ QByteArray header;
+ // 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()) {
+ QByteArray installDocDir = Config::installDir.toUtf8();
+ const QByteArray candidate = installDocDir + "/../include/" + module + "/" + module;
+ if (QFile::exists(QString::fromUtf8(candidate)))
+ header = candidate;
+ }
+ if (header.isEmpty()) {
+ qWarning() << "Could not find the module header in the include path for module"
+ << module << " (include paths: "<< includePaths_ << ")";
+ } else {
+ args.push_back("-xc++");
+ CXTranslationUnit tu;
+ CXErrorCode err = clang_parseTranslationUnit2(
+ index, header.constData(), args.data(), 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() << "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() << "Could not create PCH file for " << header << " error code:" << err;
+ }
+ args.pop_back(); // remove the "-xc++";
+ }
+ }
+ }
+ if (!pchName_.isEmpty() && !filePath.endsWith(".mm")) {
+ args.push_back("-include-pch");
+ args.push_back(pchName_.constData());
+ }
+
+ CXTranslationUnit tu;
+ CXErrorCode err = clang_parseTranslationUnit2(index, filePath.toLocal8Bit(), args.data(),
+ args.size(), nullptr, 0, flags, &tu);
+ if (err || !tu) {
+ qWarning() << "Could not parse " << 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 {
+ 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();
+ 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;
+ }
+ }
+
+ clang_disposeTokens(tu, tokens, numTokens);
+ clang_disposeTranslationUnit(tu);
+ clang_disposeIndex(index);
+}
+
+QT_END_NAMESPACE