diff options
Diffstat (limited to 'src/qdoc/clangcodeparser.cpp')
-rw-r--r-- | src/qdoc/clangcodeparser.cpp | 1034 |
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 |