diff options
-rw-r--r-- | mkspecs/features/qt_find_clang.prf | 100 | ||||
-rw-r--r-- | qttools.pro | 1 | ||||
-rw-r--r-- | src/qdoc/clangcodeparser.cpp | 1034 | ||||
-rw-r--r-- | src/qdoc/clangcodeparser.h | 66 | ||||
-rw-r--r-- | src/qdoc/codeparser.cpp | 5 | ||||
-rw-r--r-- | src/qdoc/codeparser.h | 2 | ||||
-rw-r--r-- | src/qdoc/config.cpp | 1 | ||||
-rw-r--r-- | src/qdoc/config.h | 2 | ||||
-rw-r--r-- | src/qdoc/cppcodeparser.cpp | 883 | ||||
-rw-r--r-- | src/qdoc/cppcodeparser.h | 40 | ||||
-rw-r--r-- | src/qdoc/main.cpp | 44 | ||||
-rw-r--r-- | src/qdoc/node.cpp | 3 | ||||
-rw-r--r-- | src/qdoc/node.h | 1 | ||||
-rw-r--r-- | src/qdoc/puredocparser.cpp | 4 | ||||
-rw-r--r-- | src/qdoc/puredocparser.h | 3 | ||||
-rw-r--r-- | src/qdoc/qdoc.pro | 8 | ||||
-rw-r--r-- | src/qdoc/qmlcodeparser.cpp | 8 | ||||
-rw-r--r-- | src/qdoc/qmlcodeparser.h | 1 | ||||
-rw-r--r-- | src/src.pro | 3 |
19 files changed, 1277 insertions, 932 deletions
diff --git a/mkspecs/features/qt_find_clang.prf b/mkspecs/features/qt_find_clang.prf new file mode 100644 index 000000000..14a2e80bf --- /dev/null +++ b/mkspecs/features/qt_find_clang.prf @@ -0,0 +1,100 @@ +config_clang_done: return() + +defineReplace(extractVersion) { return($$replace(1, ^(\\d+\\.\\d+\\.\\d+)(svn)?$, \\1)) } +defineReplace(extractMajorVersion) { return($$replace(1, ^(\\d+)\\.\\d+\\.\\d+(svn)?$, \\1)) } +defineReplace(extractMinorVersion) { return($$replace(1, ^\\d+\\.(\\d+)\\.\\d+(svn)?$, \\1)) } +defineReplace(extractPatchVersion) { return($$replace(1, ^\\d+\\.\\d+\\.(\\d+)(svn)?$, \\1)) } + +defineTest(versionIsAtLeast) { + actual_major_version = $$extractMajorVersion($$1) + actual_minor_version = $$extractMinorVersion($$1) + actual_patch_version = $$extractPatchVersion($$1) + required_min_major_version = $$extractMajorVersion($$2) + required_min_minor_version = $$extractMinorVersion($$2) + required_min_patch_version = $$extractPatchVersion($$2) + + isEqual(actual_major_version, $$required_min_major_version) { + isEqual(actual_minor_version, $$required_min_minor_version) { + isEqual(actual_patch_version, $$required_min_patch_version): return(true) + greaterThan(actual_patch_version, $$required_min_patch_version): return(true) + } + greaterThan(actual_minor_version, $$required_min_minor_version): return(true) + } + greaterThan(actual_major_version, $$required_min_major_version): return(true) + + return(false) +} + +defineReplace(findLLVMVersionFromLibDir) { + libdir = $$1 + version_dirs = $$files($$libdir/clang/*) + for (version_dir, version_dirs) { + fileName = $$basename(version_dir) + version = $$find(fileName, ^(\\d+\\.\\d+\\.\\d+)$) + !isEmpty(version) { + isEmpty(candidateVersion): candidateVersion = $$version + else: versionIsAtLeast($$version, $$candidateVersion): candidateVersion = $$version + } + } + return($$candidateVersion) +} + +for(_, $$list(_)) { # just a way to break easily + isEmpty(CLANG_INSTALL_DIR): CLANG_INSTALL_DIR = $$(CLANG_INSTALL_DIR) + CLANG_INSTALL_DIR = $$clean_path($$CLANG_INSTALL_DIR) + clangInstallDir = $$CLANG_INSTALL_DIR + isEmpty(CLANG_INSTALL_DIR) { + win32 { + log("Set the CLANG_INSTALL_DIR environment variable to configure clang location.$$escape_expand(\\n)") + break() + } + clangInstallDir = /usr + } + + # note: llvm_config only exits on unix + llvm_config = $$clangInstallDir/bin/llvm-config + exists($$llvm_config) { + CLANG_LIBDIR = $$system("$$llvm_config --libdir 2>/dev/null") + CLANG_INCLUDEPATH = $$system("$$llvm_config --includedir 2>/dev/null") + output = $$system("$$llvm_config --version 2>/dev/null") + CLANG_VERSION = $$extractVersion($$output) + } else { + CLANG_LIBDIR = $$clangInstallDir/lib + CLANG_INCLUDEPATH = $$clangInstallDir/include + CLANG_VERSION = $$findLLVMVersionFromLibDir($$CLANG_LIBDIR) + } + isEmpty(CLANG_VERSION) { + !isEmpty(CLANG_INSTALL_DIR): \ + error("Cannot determine clang version at $${CLANG_INSTALL_DIR}.") + log("Set the CLANG_INSTALL_DIR environment variable to configure clang location.$$escape_expand(\\n)") + break() + } + + LIBCLANG_MAIN_HEADER = $$CLANG_INCLUDEPATH/clang-c/Index.h + !exists($$LIBCLANG_MAIN_HEADER) { + !isEmpty(CLANG_INSTALL_DIR): \ + error("Cannot find libclang's main header file, candidate: $${LIBCLANG_MAIN_HEADER}.") + log("Set the CLANG_INSTALL_DIR environment variable to configure clang location.$$escape_expand(\\n)") + break() + } + + !contains(QMAKE_DEFAULT_LIBDIRS, $$CLANG_LIBDIR): CLANG_LIBS = -L$${CLANG_LIBDIR} + win32: \ + CLANG_LIBS += -llibclang -ladvapi32 -lshell32 + else: \ + CLANG_LIBS += -lclang + + !versionIsAtLeast($$CLANG_VERSION, "3.6.2") { + log("LLVM/Clang version >= 3.6.2 required, version provided: $$CLANG_VERSION.$$escape_expand(\\n)") + log("Clang was found in $$CLANG_INSTALL_DIR. Set the CLANG_INSTALL_DIR environment variable to override.$$escape_expand(\\n)") + break() + } + + cache(CLANG_LIBS) + cache(CLANG_INCLUDEPATH) + cache(CLANG_LIBDIR) + cache(CLANG_VERSION) + cache(CONFIG, add, $$list(config_clang)) +} + +cache(CONFIG, add, $$list(config_clang_done)) diff --git a/qttools.pro b/qttools.pro index 58c33f27c..b87dcea59 100644 --- a/qttools.pro +++ b/qttools.pro @@ -1 +1,2 @@ +load(qt_find_clang) load(qt_parts) 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 diff --git a/src/qdoc/clangcodeparser.h b/src/qdoc/clangcodeparser.h new file mode 100644 index 000000000..e0359e400 --- /dev/null +++ b/src/qdoc/clangcodeparser.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef CLANGCODEPARSER_H +#define CLANGCODEPARSER_H + +#include <QTemporaryDir> + +#include "cppcodeparser.h" + +QT_BEGIN_NAMESPACE + +class ClangCodeParser : public CppCodeParser +{ + Q_DECLARE_TR_FUNCTIONS(QDoc::ClangCodeParser) + +public: + ~ClangCodeParser(); + + virtual void initializeParser(const Config& config) Q_DECL_OVERRIDE; + virtual void terminateParser() Q_DECL_OVERRIDE; + virtual QString language() Q_DECL_OVERRIDE; + virtual QStringList headerFileNameFilter() Q_DECL_OVERRIDE; + virtual QStringList sourceFileNameFilter() Q_DECL_OVERRIDE; + virtual void parseHeaderFile(const Location& location, const QString& filePath) Q_DECL_OVERRIDE; + virtual void parseSourceFile(const Location& location, const QString& filePath) Q_DECL_OVERRIDE; +private: + QSet<QString> allHeaders_; + QVector<QByteArray> includePaths_; + QScopedPointer<QTemporaryDir> pchFileDir_; + QByteArray pchName_; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/qdoc/codeparser.cpp b/src/qdoc/codeparser.cpp index 2b8e28f91..83d2ddc4d 100644 --- a/src/qdoc/codeparser.cpp +++ b/src/qdoc/codeparser.cpp @@ -112,11 +112,6 @@ void CodeParser::parseHeaderFile(const Location& location, const QString& filePa parseSourceFile(location, filePath); } -void CodeParser::doneParsingHeaderFiles() -{ - doneParsingSourceFiles(); -} - /*! All the code parsers in the static list are initialized here, after the qdoc configuration variables have been set. diff --git a/src/qdoc/codeparser.h b/src/qdoc/codeparser.h index cd24987c6..0ff7014a7 100644 --- a/src/qdoc/codeparser.h +++ b/src/qdoc/codeparser.h @@ -53,8 +53,6 @@ public: virtual QStringList sourceFileNameFilter() = 0; virtual void parseHeaderFile(const Location& location, const QString& filePath); virtual void parseSourceFile(const Location& location, const QString& filePath) = 0; - virtual void doneParsingHeaderFiles(); - virtual void doneParsingSourceFiles() = 0; bool isParsingH() const; bool isParsingCpp() const; diff --git a/src/qdoc/config.cpp b/src/qdoc/config.cpp index 46d7c5bf1..76c615046 100644 --- a/src/qdoc/config.cpp +++ b/src/qdoc/config.cpp @@ -75,6 +75,7 @@ QString ConfigStrings::IGNOREDIRECTIVES = QStringLiteral("ignoredirectives"); QString ConfigStrings::IGNORETOKENS = QStringLiteral("ignoretokens"); QString ConfigStrings::IMAGEDIRS = QStringLiteral("imagedirs"); QString ConfigStrings::IMAGES = QStringLiteral("images"); +QString ConfigStrings::INCLUDEPATHS = QStringLiteral("includepaths"); QString ConfigStrings::INDEXES = QStringLiteral("indexes"); QString ConfigStrings::LANDINGPAGE = QStringLiteral("landingpage"); QString ConfigStrings::LANGUAGE = QStringLiteral("language"); diff --git a/src/qdoc/config.h b/src/qdoc/config.h index 298cd3f63..588e0e0a9 100644 --- a/src/qdoc/config.h +++ b/src/qdoc/config.h @@ -188,6 +188,7 @@ struct ConfigStrings static QString IGNORETOKENS; static QString IMAGEDIRS; static QString IMAGES; + static QString INCLUDEPATHS; static QString INDEXES; static QString LANDINGPAGE; static QString LANGUAGE; @@ -268,6 +269,7 @@ struct ConfigStrings #define CONFIG_IGNORETOKENS ConfigStrings::IGNORETOKENS #define CONFIG_IMAGEDIRS ConfigStrings::IMAGEDIRS #define CONFIG_IMAGES ConfigStrings::IMAGES +#define CONFIG_INCLUDEPATHS ConfigStrings::INCLUDEPATHS #define CONFIG_INDEXES ConfigStrings::INDEXES #define CONFIG_LANDINGPAGE ConfigStrings::LANDINGPAGE #define CONFIG_LANGUAGE ConfigStrings::LANGUAGE diff --git a/src/qdoc/cppcodeparser.cpp b/src/qdoc/cppcodeparser.cpp index 7298d1ec2..7ec6ed805 100644 --- a/src/qdoc/cppcodeparser.cpp +++ b/src/qdoc/cppcodeparser.cpp @@ -51,7 +51,6 @@ QStringList CppCodeParser::exampleFiles; QStringList CppCodeParser::exampleDirs; QSet<QString> CppCodeParser::excludeDirs; QSet<QString> CppCodeParser::excludeFiles; -CppCodeParser* CppCodeParser::cppParser_ = 0; /*! The constructor initializes some regular expressions @@ -61,7 +60,6 @@ CppCodeParser::CppCodeParser() : varComment("/\\*\\s*([a-zA-Z_0-9]+)\\s*\\*/"), sep("(?:<[^>]+>)?::") { reset(); - cppParser_ = this; } /*! @@ -140,7 +138,7 @@ QString CppCodeParser::language() */ QStringList CppCodeParser::headerFileNameFilter() { - return QStringList() << "*.ch" << "*.h" << "*.h++" << "*.hh" << "*.hpp" << "*.hxx"; + return QStringList(); } /*! @@ -149,121 +147,9 @@ QStringList CppCodeParser::headerFileNameFilter() */ QStringList CppCodeParser::sourceFileNameFilter() { - return QStringList() << "*.c++" << "*.cc" << "*.cpp" << "*.cxx" << "*.mm"; + return QStringList(); } -/*! - Parse the C++ header file identified by \a filePath and add - the parsed contents to the database. The \a location is used - for reporting errors. - */ -void CppCodeParser::parseHeaderFile(const Location& location, const QString& filePath) -{ - QFile in(filePath); - currentFile_ = filePath; - if (!in.open(QIODevice::ReadOnly)) { - location.error(tr("Cannot open C++ header file '%1'").arg(filePath)); - currentFile_.clear(); - return; - } - - reset(); - Location fileLocation(filePath); - Tokenizer fileTokenizer(fileLocation, in); - tokenizer = &fileTokenizer; - readToken(); - parsingHeaderFile_ = true; - matchDeclList(qdb_->primaryTreeRoot()); - parsingHeaderFile_ = false; - if (!fileTokenizer.version().isEmpty()) - qdb_->setVersion(fileTokenizer.version()); - in.close(); - - if (fileLocation.fileName() == "qiterator.h") - parseQiteratorDotH(location, filePath); - currentFile_.clear(); -} - -/*! - Get ready to parse the C++ cpp file identified by \a filePath - and add its parsed contents to the database. \a location is - used for reporting errors. - - Call matchDocsAndStuff() to do all the parsing and tree building. - */ -void CppCodeParser::parseSourceFile(const Location& location, const QString& filePath) -{ - QFile in(filePath); - currentFile_ = filePath; - if (!in.open(QIODevice::ReadOnly)) { - location.error(tr("Cannot open C++ source file '%1' (%2)").arg(filePath).arg(strerror(errno))); - currentFile_.clear(); - return; - } - - reset(); - Location fileLocation(filePath); - Tokenizer fileTokenizer(fileLocation, in); - tokenizer = &fileTokenizer; - readToken(); - - /* - The set of open namespaces is cleared before parsing - each source file. The word "source" here means cpp file. - */ - qdb_->clearOpenNamespaces(); - - matchDocsAndStuff(); - in.close(); - currentFile_.clear(); -} - -/*! - This is called after all the C++ header files have been - parsed. The most important thing it does is resolve C++ - class inheritance links in the tree. It also initializes - a bunch of other collections. - */ -void CppCodeParser::doneParsingHeaderFiles() -{ - QMapIterator<QString, QString> i(sequentialIteratorClasses); - while (i.hasNext()) { - i.next(); - instantiateIteratorMacro(i.key(), i.value(), sequentialIteratorDefinition); - } - i = mutableSequentialIteratorClasses; - while (i.hasNext()) { - i.next(); - instantiateIteratorMacro(i.key(), i.value(), mutableSequentialIteratorDefinition); - } - i = associativeIteratorClasses; - while (i.hasNext()) { - i.next(); - instantiateIteratorMacro(i.key(), i.value(), associativeIteratorDefinition); - } - i = mutableAssociativeIteratorClasses; - while (i.hasNext()) { - i.next(); - instantiateIteratorMacro(i.key(), i.value(), mutableAssociativeIteratorDefinition); - } - sequentialIteratorDefinition.clear(); - mutableSequentialIteratorDefinition.clear(); - associativeIteratorDefinition.clear(); - mutableAssociativeIteratorDefinition.clear(); - sequentialIteratorClasses.clear(); - mutableSequentialIteratorClasses.clear(); - associativeIteratorClasses.clear(); - mutableAssociativeIteratorClasses.clear(); -} - -/*! - This is called after all the source files (i.e., not the - header files) have been parsed. Currently nothing to do. - */ -void CppCodeParser::doneParsingSourceFiles() -{ - // contents moved to QdocDatabase::resolveIssues() -} static QSet<QString> topicCommands_; /*! @@ -563,7 +449,7 @@ Node* CppCodeParser::processTopicCommand(const Doc& doc, QString module; QString name; QString type; - if (splitQmlMethodArg(arg.first, type, module, name)) { + if (splitQmlMethodArg(arg.first, type, module, name, doc.location())) { Aggregate* aggregate = qdb_->findQmlType(module, name); if (!aggregate) aggregate = qdb_->findQmlBasicType(module, name); @@ -623,7 +509,8 @@ Node* CppCodeParser::processTopicCommand(const Doc& doc, bool CppCodeParser::splitQmlPropertyGroupArg(const QString& arg, QString& module, QString& qmlTypeName, - QString& name) + QString& name, + const Location& location) { QStringList colonSplit = arg.split("::"); if (colonSplit.size() == 3) { @@ -633,7 +520,7 @@ bool CppCodeParser::splitQmlPropertyGroupArg(const QString& arg, return true; } QString msg = "Unrecognizable QML module/component qualifier for " + arg; - location().warning(tr(msg.toLatin1().data())); + location.warning(tr(msg.toLatin1().data())); return false; } @@ -660,7 +547,8 @@ bool CppCodeParser::splitQmlPropertyArg(const QString& arg, QString& type, QString& module, QString& qmlTypeName, - QString& name) + QString& name, + const Location &location) { QStringList blankSplit = arg.split(QLatin1Char(' ')); if (blankSplit.size() > 1) { @@ -679,11 +567,11 @@ bool CppCodeParser::splitQmlPropertyArg(const QString& arg, return true; } QString msg = "Unrecognizable QML module/component qualifier for " + arg; - location().warning(tr(msg.toLatin1().data())); + location.warning(tr(msg.toLatin1().data())); } else { QString msg = "Missing property type for " + arg; - location().warning(tr(msg.toLatin1().data())); + location.warning(tr(msg.toLatin1().data())); } return false; } @@ -705,7 +593,8 @@ bool CppCodeParser::splitQmlPropertyArg(const QString& arg, bool CppCodeParser::splitQmlMethodArg(const QString& arg, QString& type, QString& module, - QString& qmlTypeName) + QString& qmlTypeName, + const Location& location) { QString name; int leftParen = arg.indexOf(QChar('(')); @@ -734,7 +623,7 @@ bool CppCodeParser::splitQmlMethodArg(const QString& arg, return true; } QString msg = "Unrecognizable QML module/component qualifier for " + arg; - location().warning(tr(msg.toLatin1().data())); + location.warning(tr(msg.toLatin1().data())); return false; } @@ -775,7 +664,7 @@ void CppCodeParser::processQmlProperties(const Doc& doc, else qmlPropertyGroupTopic.topic = COMMAND_QMLPROPERTYGROUP; arg = qmlPropertyGroupTopic.args; - if (splitQmlPropertyArg(arg, type, module, qmlTypeName, property)) { + if (splitQmlPropertyArg(arg, type, module, qmlTypeName, property, doc.location())) { int i = property.indexOf('.'); if (i != -1) { property = property.left(i); @@ -795,7 +684,7 @@ void CppCodeParser::processQmlProperties(const Doc& doc, if (!qmlPropertyGroupTopic.isEmpty()) { arg = qmlPropertyGroupTopic.args; - if (splitQmlPropertyGroupArg(arg, module, qmlTypeName, property)) { + if (splitQmlPropertyGroupArg(arg, module, qmlTypeName, property, doc.location())) { qmlType = qdb_->findQmlType(module, qmlTypeName); if (qmlType) { qpgn = new QmlPropertyGroupNode(qmlType, property); @@ -817,7 +706,7 @@ void CppCodeParser::processQmlProperties(const Doc& doc, (topic == COMMAND_JSPROPERTY) || (topic == COMMAND_JSATTACHEDPROPERTY)) { bool attached = ((topic == COMMAND_QMLATTACHEDPROPERTY) || (topic == COMMAND_JSATTACHEDPROPERTY)); - if (splitQmlPropertyArg(arg, type, module, qmlTypeName, property)) { + if (splitQmlPropertyArg(arg, type, module, qmlTypeName, property, doc.location())) { Aggregate* aggregate = qdb_->findQmlType(module, qmlTypeName); if (!aggregate) aggregate = qdb_->findQmlBasicType(module, qmlTypeName); @@ -1179,15 +1068,6 @@ bool CppCodeParser::matchTemplateAngles(CodeChunk *dataType) return matches; } -/* - This function is no longer used. - */ -bool CppCodeParser::matchTemplateHeader() -{ - readToken(); - return matchTemplateAngles(); -} - bool CppCodeParser::matchDataType(CodeChunk *dataType, QString *var, bool qProp) { /* @@ -1718,145 +1598,6 @@ bool CppCodeParser::matchFunctionDecl(Aggregate *parent, return true; } -bool CppCodeParser::matchBaseSpecifier(ClassNode *classe, bool isClass) -{ - Node::Access access; - - switch (tok) { - case Tok_public: - access = Node::Public; - readToken(); - break; - case Tok_protected: - access = Node::Protected; - readToken(); - break; - case Tok_private: - access = Node::Private; - readToken(); - break; - default: - access = isClass ? Node::Private : Node::Public; - } - - if (tok == Tok_virtual) - readToken(); - - CodeChunk baseClass; - if (!matchDataType(&baseClass)) - return false; - - classe->addUnresolvedBaseClass(access, baseClass.toPath(), baseClass.toString()); - return true; -} - -bool CppCodeParser::matchBaseList(ClassNode *classe, bool isClass) -{ - for (;;) { - if (!matchBaseSpecifier(classe, isClass)) - return false; - if (tok == Tok_LeftBrace) - return true; - if (!match(Tok_Comma)) - return false; - } -} - -/*! - Parse a C++ class, union, or struct declaration. - - This function only handles one level of class nesting, but that is - sufficient for Qt because there are no cases of class nesting more - than one level deep. - */ -bool CppCodeParser::matchClassDecl(Aggregate *parent, - const QString &templateStuff) -{ - bool isClass = (tok == Tok_class); - readToken(); - - bool compat = matchCompat(); - - if (tok != Tok_Ident) - return false; - while (tok == Tok_Ident) - readToken(); - if (tok == Tok_Gulbrandsen) { - Node* n = parent->findChildNode(previousLexeme(),Node::Class); - if (n) { - parent = static_cast<Aggregate*>(n); - if (parent) { - readToken(); - if (tok != Tok_Ident) - return false; - readToken(); - } - } - } - - const QString className = previousLexeme(); - match(Tok_final); // ignore C++11 final class-virt-specifier - if (tok != Tok_Colon && tok != Tok_LeftBrace) - return false; - - /* - So far, so good. We have 'class Foo {' or 'class Foo :'. - This is enough to recognize a class definition. - */ - ClassNode *classe = new ClassNode(parent, className); - classe->setAccess(access); - classe->setLocation(declLoc()); - if (compat) - classe->setStatus(Node::Compat); - if (!physicalModuleName.isEmpty()) - classe->setPhysicalModuleName(physicalModuleName); - classe->setTemplateStuff(templateStuff); - - if (match(Tok_Colon) && !matchBaseList(classe, isClass)) - return false; - if (!match(Tok_LeftBrace)) - return false; - - Node::Access outerAccess = access; - access = isClass ? Node::Private : Node::Public; - FunctionNode::Metaness outerMetaness = metaness_; - metaness_ = FunctionNode::Plain; - - bool matches = (matchDeclList(classe) && match(Tok_RightBrace) && - match(Tok_Semicolon)); - access = outerAccess; - metaness_ = outerMetaness; - return matches; -} - -bool CppCodeParser::matchNamespaceDecl(Aggregate *parent) -{ - readToken(); // skip 'namespace' - if (tok != Tok_Ident) - return false; - while (tok == Tok_Ident) - readToken(); - if (tok != Tok_LeftBrace) - return false; - - /* - So far, so good. We have 'namespace Foo {'. - */ - QString namespaceName = previousLexeme(); - NamespaceNode* ns = 0; - if (parent) - ns = static_cast<NamespaceNode*>(parent->findChildNode(namespaceName, Node::Namespace)); - if (!ns) { - ns = new NamespaceNode(parent, namespaceName); - ns->setAccess(access); - ns->setLocation(declLoc()); - } - - readToken(); // skip '{' - bool matched = matchDeclList(ns); - return matched && match(Tok_RightBrace); -} - /*! Match a C++ \c using clause. Return \c true if the match is successful. Otherwise false. @@ -1930,555 +1671,6 @@ bool CppCodeParser::matchUsingDecl(Aggregate* parent) return true; } -bool CppCodeParser::matchEnumItem(Aggregate *parent, EnumNode *enume) -{ - if (!match(Tok_Ident)) - return false; - - QString name = previousLexeme(); - CodeChunk val; - int parenLevel = 0; - - if (match(Tok_Equal)) { - while (tok != Tok_RightBrace && tok != Tok_Eoi) { - if (tok == Tok_LeftParen) - parenLevel++; - else if (tok == Tok_RightParen) - parenLevel--; - else if (tok == Tok_Comma) { - if (parenLevel <= 0) - break; - } - val.append(lexeme()); - readToken(); - } - } - - if (enume) { - QString strVal = val.toString(); - if (strVal.isEmpty()) { - if (enume->items().isEmpty()) { - strVal = "0"; - } - else { - QString last = enume->items().last().value(); - bool ok; - int n = last.toInt(&ok); - if (ok) { - if (last.startsWith(QLatin1Char('0')) && last.size() > 1) { - if (last.startsWith("0x") || last.startsWith("0X")) - strVal = last.left(2) + QString::number(n + 1, 16); - else - strVal = QLatin1Char('0') + QString::number(n + 1, 8); - } - else - strVal = QString::number(n + 1); - } - } - } - - enume->addItem(EnumItem(name, strVal)); - } - else { - VariableNode *var = new VariableNode(parent, name); - var->setAccess(access); - var->setLocation(location()); - var->setLeftType("const int"); - var->setStatic(true); - } - return true; -} - -bool CppCodeParser::matchEnumDecl(Aggregate *parent) -{ - QString name; - - if (!match(Tok_enum)) - return false; - if (tok == Tok_struct || tok == Tok_class) - readToken(); // ignore C++11 struct or class attribute - if (match(Tok_Ident)) - name = previousLexeme(); - if (match(Tok_Colon)) { // ignore C++11 enum-base - CodeChunk dataType; - if (!matchDataType(&dataType)) - return false; - } - if (tok != Tok_LeftBrace) - return false; - - EnumNode *enume = 0; - - if (!name.isEmpty()) { - enume = new EnumNode(parent, name); - enume->setAccess(access); - enume->setLocation(declLoc()); - } - - readToken(); - - if (!matchEnumItem(parent, enume)) - return false; - - while (match(Tok_Comma)) { - if (!matchEnumItem(parent, enume)) - return false; - } - return match(Tok_RightBrace) && match(Tok_Semicolon); -} - -bool CppCodeParser::matchTypedefDecl(Aggregate *parent) -{ - CodeChunk dataType; - QString name; - - if (!match(Tok_typedef)) - return false; - if (!matchDataType(&dataType, &name)) - return false; - if (!match(Tok_Semicolon)) - return false; - - if (parent && !parent->findChildNode(name, Node::Typedef)) { - TypedefNode* td = new TypedefNode(parent, name); - td->setAccess(access); - td->setLocation(declLoc()); - } - return true; -} - -bool CppCodeParser::matchProperty(Aggregate *parent) -{ - int expected_tok = Tok_LeftParen; - if (match(Tok_Q_PRIVATE_PROPERTY)) { - expected_tok = Tok_Comma; - if (!skipTo(Tok_Comma)) - return false; - } - else if (!match(Tok_Q_PROPERTY) && - !match(Tok_Q_OVERRIDE) && - !match(Tok_QDOC_PROPERTY)) { - return false; - } - - if (!match(expected_tok)) - return false; - - QString name; - CodeChunk dataType; - if (!matchDataType(&dataType, &name, true)) - return false; - - PropertyNode *property = new PropertyNode(parent, name); - property->setAccess(Node::Public); - property->setLocation(declLoc()); - property->setDataType(dataType.toString()); - - while (tok != Tok_RightParen && tok != Tok_Eoi) { - if (!match(Tok_Ident) && !match(Tok_default) && !match(Tok_final) && !match(Tok_override)) - return false; - QString key = previousLexeme(); - QString value; - - // Keywords with no associated values - if (key == "CONSTANT") { - property->setConstant(); - continue; - } - else if (key == "FINAL") { - property->setFinal(); - continue; - } - - if (match(Tok_Ident) || match(Tok_Number)) { - value = previousLexeme(); - } - else if (match(Tok_LeftParen)) { - int depth = 1; - while (tok != Tok_Eoi) { - if (tok == Tok_LeftParen) { - readToken(); - ++depth; - } else if (tok == Tok_RightParen) { - readToken(); - if (--depth == 0) - break; - } else { - readToken(); - } - } - value = "?"; - } - - if (key == "READ") - qdb_->addPropertyFunction(property, value, PropertyNode::Getter); - else if (key == "WRITE") { - qdb_->addPropertyFunction(property, value, PropertyNode::Setter); - property->setWritable(true); - } - else if (key == "STORED") - property->setStored(value.toLower() == "true"); - else if (key == "DESIGNABLE") { - QString v = value.toLower(); - if (v == "true") - property->setDesignable(true); - else if (v == "false") - property->setDesignable(false); - else { - property->setDesignable(false); - property->setRuntimeDesFunc(value); - } - } - else if (key == "RESET") - qdb_->addPropertyFunction(property, value, PropertyNode::Resetter); - else if (key == "NOTIFY") { - qdb_->addPropertyFunction(property, value, PropertyNode::Notifier); - } else if (key == "REVISION") { - int revision; - bool ok; - revision = value.toInt(&ok); - if (ok) - property->setRevision(revision); - else - location().warning(tr("Invalid revision number: %1").arg(value)); - } else if (key == "SCRIPTABLE") { - QString v = value.toLower(); - if (v == "true") - property->setScriptable(true); - else if (v == "false") - property->setScriptable(false); - else { - property->setScriptable(false); - property->setRuntimeScrFunc(value); - } - } - } - match(Tok_RightParen); - return true; -} - -/*! - Parse a C++ declaration. - */ -bool CppCodeParser::matchDeclList(Aggregate *parent) -{ - ExtraFuncData extra; - QString templateStuff; - int braceDepth0 = tokenizer->braceDepth(); - if (tok == Tok_RightBrace) // prevents failure on empty body - braceDepth0++; - - while (tokenizer->braceDepth() >= braceDepth0 && tok != Tok_Eoi) { - switch (tok) { - case Tok_Colon: - readToken(); - break; - case Tok_class: - case Tok_struct: - case Tok_union: - setDeclLoc(); - matchClassDecl(parent, templateStuff); - break; - case Tok_namespace: - setDeclLoc(); - matchNamespaceDecl(parent); - break; - case Tok_using: - setDeclLoc(); - matchUsingDecl(parent); - break; - case Tok_template: - { - CodeChunk dataType; - readToken(); - matchTemplateAngles(&dataType); - templateStuff = dataType.toString(); - } - continue; - case Tok_enum: - setDeclLoc(); - matchEnumDecl(parent); - break; - case Tok_typedef: - setDeclLoc(); - matchTypedefDecl(parent); - break; - case Tok_private: - readToken(); - access = Node::Private; - metaness_ = FunctionNode::Plain; - break; - case Tok_protected: - readToken(); - access = Node::Protected; - metaness_ = FunctionNode::Plain; - break; - case Tok_public: - readToken(); - access = Node::Public; - metaness_ = FunctionNode::Plain; - break; - case Tok_signals: - case Tok_Q_SIGNALS: - readToken(); - access = Node::Public; - metaness_ = FunctionNode::Signal; - break; - case Tok_slots: - case Tok_Q_SLOTS: - readToken(); - metaness_ = FunctionNode::Slot; - break; - case Tok_Q_OBJECT: - readToken(); - break; - case Tok_Q_OVERRIDE: - case Tok_Q_PROPERTY: - case Tok_Q_PRIVATE_PROPERTY: - case Tok_QDOC_PROPERTY: - setDeclLoc(); - if (!matchProperty(parent)) { - location().warning(tr("Failed to parse token %1 in property declaration").arg(lexeme())); - skipTo(Tok_RightParen); - match(Tok_RightParen); - } - break; - case Tok_Q_DECLARE_SEQUENTIAL_ITERATOR: - readToken(); - if (match(Tok_LeftParen) && match(Tok_Ident)) - sequentialIteratorClasses.insert(previousLexeme(), location().fileName()); - match(Tok_RightParen); - break; - case Tok_Q_DECLARE_MUTABLE_SEQUENTIAL_ITERATOR: - readToken(); - if (match(Tok_LeftParen) && match(Tok_Ident)) - mutableSequentialIteratorClasses.insert(previousLexeme(), location().fileName()); - match(Tok_RightParen); - break; - case Tok_Q_DECLARE_ASSOCIATIVE_ITERATOR: - readToken(); - if (match(Tok_LeftParen) && match(Tok_Ident)) - associativeIteratorClasses.insert(previousLexeme(), location().fileName()); - match(Tok_RightParen); - break; - case Tok_Q_DECLARE_MUTABLE_ASSOCIATIVE_ITERATOR: - readToken(); - if (match(Tok_LeftParen) && match(Tok_Ident)) - mutableAssociativeIteratorClasses.insert(previousLexeme(), location().fileName()); - match(Tok_RightParen); - break; - case Tok_Q_DECLARE_FLAGS: - readToken(); - setDeclLoc(); - if (match(Tok_LeftParen) && match(Tok_Ident)) { - QString flagsType = previousLexeme(); - if (match(Tok_Comma) && match(Tok_Ident)) { - QString name = previousLexeme(); - TypedefNode *flagsNode = new TypedefNode(parent, flagsType); - flagsNode->setAccess(access); - flagsNode->setLocation(declLoc()); - EnumNode* en = static_cast<EnumNode*>(parent->findChildNode(name, Node::Enum)); - if (en) - en->setFlagsType(flagsNode); - } - } - match(Tok_RightParen); - break; - case Tok_QT_MODULE: - readToken(); - setDeclLoc(); - if (match(Tok_LeftParen) && match(Tok_Ident)) - physicalModuleName = previousLexeme(); - if (!physicalModuleName.startsWith("Qt")) - physicalModuleName.prepend("Qt"); - match(Tok_RightParen); - break; - default: - if (parsingHeaderFile_) - setDeclLoc(); - if (!matchFunctionDecl(parent, 0, 0, templateStuff, extra)) { - while (tok != Tok_Eoi && - (tokenizer->braceDepth() > braceDepth0 || - (!match(Tok_Semicolon) && - tok != Tok_public && tok != Tok_protected && - tok != Tok_private))) { - readToken(); - } - } - } - templateStuff.clear(); - } - return true; -} - -/*! - This is called by parseSourceFile() to do the actual parsing - and tree building. - */ -bool CppCodeParser::matchDocsAndStuff() -{ - ExtraFuncData extra; - const QSet<QString>& topicCommandsAllowed = topicCommands(); - const QSet<QString>& otherMetacommandsAllowed = otherMetaCommands(); - const QSet<QString>& metacommandsAllowed = topicCommandsAllowed + otherMetacommandsAllowed; - - while (tok != Tok_Eoi) { - if (tok == Tok_Doc) { - /* - lexeme() returns an entire qdoc comment. - */ - QString comment = lexeme(); - Location start_loc(location()); - readToken(); - - Doc::trimCStyleComment(start_loc,comment); - Location end_loc(location()); - - /* - Doc parses the comment. - */ - Doc doc(start_loc,end_loc,comment,metacommandsAllowed, topicCommandsAllowed); - QString topic; - bool isQmlPropertyTopic = false; - bool isJsPropertyTopic = false; - - const TopicList& topics = doc.topicsUsed(); - if (!topics.isEmpty()) { - topic = topics[0].topic; - if ((topic == COMMAND_QMLPROPERTY) || - (topic == COMMAND_QMLPROPERTYGROUP) || - (topic == COMMAND_QMLATTACHEDPROPERTY)) { - isQmlPropertyTopic = true; - } - else if ((topic == COMMAND_JSPROPERTY) || - (topic == COMMAND_JSPROPERTYGROUP) || - (topic == COMMAND_JSATTACHEDPROPERTY)) { - isJsPropertyTopic = true; - } - } - NodeList nodes; - DocList docs; - - if (topic.isEmpty()) { - QStringList parentPath; - FunctionNode *clone; - FunctionNode *func = 0; - - if (matchFunctionDecl(0, &parentPath, &clone, QString(), extra)) { - func = qdb_->findFunctionNode(parentPath, clone); - /* - If the node was not found, then search for it in the - open C++ namespaces. We don't expect this search to - be necessary often. Nor do we expect it to succeed - very often. - */ - if (func == 0) - func = qdb_->findNodeInOpenNamespace(parentPath, clone); - - if (func) { - func->borrowParameterNames(clone); - nodes.append(func); - docs.append(doc); - } - delete clone; - } - else { - doc.location().warning(tr("Cannot tie this documentation to anything"), - tr("I found a /*! ... */ comment, but there was no " - "topic command (e.g., '\\%1', '\\%2') in the " - "comment and no function definition following " - "the comment.") - .arg(COMMAND_FN).arg(COMMAND_PAGE)); - } - } - else if (isQmlPropertyTopic || isJsPropertyTopic) { - Doc nodeDoc = doc; - processQmlProperties(nodeDoc, nodes, docs, isJsPropertyTopic); - } - else { - ArgList args; - const QSet<QString>& topicCommandsUsed = topicCommandsAllowed & doc.metaCommandsUsed(); - if (topicCommandsUsed.count() > 0) { - topic = *topicCommandsUsed.constBegin(); - args = doc.metaCommandArgs(topic); - } - if (topicCommandsUsed.count() > 1) { - QString topics; - QSet<QString>::ConstIterator t = topicCommandsUsed.constBegin(); - while (t != topicCommandsUsed.constEnd()) { - topics += " \\" + *t + QLatin1Char(','); - ++t; - } - topics[topics.lastIndexOf(',')] = '.'; - int i = topics.lastIndexOf(','); - topics[i] = ' '; - topics.insert(i+1,"and"); - doc.location().warning(tr("Multiple topic commands found in comment: %1").arg(topics)); - } - ArgList::ConstIterator a = args.constBegin(); - while (a != args.constEnd()) { - Doc nodeDoc = doc; - Node *node = processTopicCommand(nodeDoc,topic,*a); - if (node != 0) { - nodes.append(node); - docs.append(nodeDoc); - } - ++a; - } - } - - NodeList::Iterator n = nodes.begin(); - QList<Doc>::Iterator d = docs.begin(); - while (n != nodes.end()) { - processOtherMetaCommands(*d, *n); - (*n)->setDoc(*d); - checkModuleInclusion(*n); - if ((*n)->isAggregate() && ((Aggregate *)*n)->includes().isEmpty()) { - Aggregate *m = static_cast<Aggregate *>(*n); - while (m->parent() && m->physicalModuleName().isEmpty()) { - m = m->parent(); - } - if (m == *n) - ((Aggregate *)*n)->addInclude((*n)->name()); - else - ((Aggregate *)*n)->setIncludes(m->includes()); - } - ++d; - ++n; - } - } - else if (tok == Tok_using) { - matchUsingDecl(0); - } - else { - QStringList parentPath; - FunctionNode *clone; - FunctionNode *fnode = 0; - - if (matchFunctionDecl(0, &parentPath, &clone, QString(), extra)) { - /* - The location of the definition is more interesting - than that of the declaration. People equipped with - a sophisticated text editor can respond to warnings - concerning undocumented functions very quickly. - - Signals are implemented in uninteresting files - generated by moc. - */ - fnode = qdb_->findFunctionNode(parentPath, clone); - if (fnode != 0 && !fnode->isSignal()) - fnode->setLocation(clone->location()); - delete clone; - } - else { - if (tok != Tok_Doc) - readToken(); - } - } - } - return true; -} - /*! This function uses a Tokenizer to parse the function \a signature in an attempt to match it to the signature of a child node of \a root. @@ -2494,7 +1686,8 @@ bool CppCodeParser::makeFunctionNode(const QString& signature, int outerTok = tok; QByteArray latin1 = signature.toLatin1(); - Tokenizer stringTokenizer(location(), latin1); + Location tmpLoc(signature); // FIXME give a proper location with a filename. + Tokenizer stringTokenizer(tokenizer ? location() : tmpLoc, latin1); stringTokenizer.setParsingFnOrMacro(true); tokenizer = &stringTokenizer; readToken(); @@ -2565,46 +1758,6 @@ FunctionNode* CppCodeParser::makeFunctionNode(const Doc& doc, return fn; } -void CppCodeParser::parseQiteratorDotH(const Location &location, const QString &filePath) -{ - QFile file(filePath); - if (!file.open(QFile::ReadOnly)) - return; - - QString text = file.readAll(); - text.remove("\r"); - text.remove("\\\n"); - QStringList lines = text.split(QLatin1Char('\n')); - lines = lines.filter("Q_DECLARE"); - lines.replaceInStrings(QRegExp("#define Q[A-Z_]*\\(C\\)"), QString()); - - if (lines.size() == 4) { - sequentialIteratorDefinition = lines[0]; - mutableSequentialIteratorDefinition = lines[1]; - associativeIteratorDefinition = lines[2]; - mutableAssociativeIteratorDefinition = lines[3]; - } - else { - location.warning(tr("The qiterator.h hack failed")); - } -} - -void CppCodeParser::instantiateIteratorMacro(const QString &container, - const QString &includeFile, - const QString ¯oDef) -{ - QString resultingCode = macroDef; - resultingCode.replace(QRegExp("\\bC\\b"), container); - resultingCode.remove(QRegExp("\\s*##\\s*")); - - Location loc(includeFile); // hack to get the include file for free - QByteArray latin1 = resultingCode.toLatin1(); - Tokenizer stringTokenizer(loc, latin1); - tokenizer = &stringTokenizer; - readToken(); - matchDeclList(QDocDatabase::qdocDB()->primaryTreeRoot()); -} - void CppCodeParser::createExampleFileNodes(DocumentNode *dn) { QString examplePath = dn->name(); diff --git a/src/qdoc/cppcodeparser.h b/src/qdoc/cppcodeparser.h index c49da77f2..79e5f47b6 100644 --- a/src/qdoc/cppcodeparser.h +++ b/src/qdoc/cppcodeparser.h @@ -59,17 +59,12 @@ class CppCodeParser : public CodeParser public: CppCodeParser(); ~CppCodeParser(); - static CppCodeParser* cppParser() { return cppParser_; } virtual void initializeParser(const Config& config) Q_DECL_OVERRIDE; virtual void terminateParser() Q_DECL_OVERRIDE; virtual QString language() Q_DECL_OVERRIDE; virtual QStringList headerFileNameFilter() Q_DECL_OVERRIDE; virtual QStringList sourceFileNameFilter() Q_DECL_OVERRIDE; - virtual void parseHeaderFile(const Location& location, const QString& filePath) Q_DECL_OVERRIDE; - virtual void parseSourceFile(const Location& location, const QString& filePath) Q_DECL_OVERRIDE; - virtual void doneParsingHeaderFiles() Q_DECL_OVERRIDE; - virtual void doneParsingSourceFiles() Q_DECL_OVERRIDE; bool parseParameters(const QString& parameters, QVector<Parameter>& pvect, bool& isQPrivateSignal); const Location& declLoc() const { return declLoc_; } void setDeclLoc() { declLoc_ = location(); } @@ -84,16 +79,19 @@ protected: bool splitQmlPropertyGroupArg(const QString& arg, QString& module, QString& element, - QString& name); + QString& name, + const Location& location); bool splitQmlPropertyArg(const QString& arg, QString& type, QString& module, QString& element, - QString& name); + QString& name, + const Location& location); bool splitQmlMethodArg(const QString& arg, QString& type, QString& module, - QString& element); + QString& element, + const Location& location); virtual void processOtherMetaCommand(const Doc& doc, const QString& command, const ArgLocPair& argLocPair, @@ -113,7 +111,6 @@ protected: bool matchCompat(); bool matchModuleQualifier(QString& name); bool matchTemplateAngles(CodeChunk *type = 0); - bool matchTemplateHeader(); bool matchDataType(CodeChunk *type, QString *var = 0, bool qProp = false); bool matchParameter(QVector<Parameter>& pvect, bool& isQPrivateSignal); bool matchFunctionDecl(Aggregate *parent, @@ -121,18 +118,7 @@ protected: FunctionNode **funcPtr, const QString &templateStuff, ExtraFuncData& extra); - bool matchBaseSpecifier(ClassNode *classe, bool isClass); - bool matchBaseList(ClassNode *classe, bool isClass); - bool matchClassDecl(Aggregate *parent, - const QString &templateStuff = QString()); - bool matchNamespaceDecl(Aggregate *parent); bool matchUsingDecl(Aggregate* parent); - bool matchEnumItem(Aggregate *parent, EnumNode *enume); - bool matchEnumDecl(Aggregate *parent); - bool matchTypedefDecl(Aggregate *parent); - bool matchProperty(Aggregate *parent); - bool matchDeclList(Aggregate *parent); - bool matchDocsAndStuff(); bool makeFunctionNode(const QString &synopsis, QStringList *parentPathPtr, FunctionNode **funcPtr, @@ -143,13 +129,8 @@ protected: Node::NodeType type, bool attached, QString qdoctag); - void parseQiteratorDotH(const Location &location, const QString &filePath); - void instantiateIteratorMacro(const QString &container, - const QString &includeFile, - const QString ¯oDef); void createExampleFileNodes(DocumentNode *dn); int matchFunctionModifier(); - protected: QMap<QString, Node::NodeType> nodeTypeMap; Tokenizer *tokenizer; @@ -163,20 +144,11 @@ protected: Location declLoc_; private: - QString sequentialIteratorDefinition; - QString mutableSequentialIteratorDefinition; - QString associativeIteratorDefinition; - QString mutableAssociativeIteratorDefinition; - QMap<QString, QString> sequentialIteratorClasses; - QMap<QString, QString> mutableSequentialIteratorClasses; - QMap<QString, QString> associativeIteratorClasses; - QMap<QString, QString> mutableAssociativeIteratorClasses; static QStringList exampleFiles; static QStringList exampleDirs; static QSet<QString> excludeDirs; static QSet<QString> excludeFiles; - static CppCodeParser* cppParser_; QString exampleNameFilter; QString exampleImageFilter; }; diff --git a/src/qdoc/main.cpp b/src/qdoc/main.cpp index e5fe2412f..8cc5a704a 100644 --- a/src/qdoc/main.cpp +++ b/src/qdoc/main.cpp @@ -45,6 +45,7 @@ #include "jscodemarker.h" #include "qmlcodemarker.h" #include "qmlcodeparser.h" +#include "clangcodeparser.h" #include <qdatetime.h> #include <qdebug.h> #include "qtranslator.h" @@ -72,6 +73,7 @@ static bool noLinkErrors = false; static bool autolinkErrors = false; static bool obsoleteLinks = false; static QStringList defines; +static QStringList includesPaths; static QStringList dependModules; static QStringList indexDirs; static QString currentDir; @@ -243,6 +245,8 @@ static void processQdocconfFile(const QString &fileName) */ QStringList defs = defines + config.getStringList(CONFIG_DEFINES); config.setStringList(CONFIG_DEFINES,defs); + QStringList incs = includesPaths + config.getStringList(CONFIG_INCLUDEPATHS); + config.setStringList(CONFIG_INCLUDEPATHS, incs); Location::terminate(); currentDir = QFileInfo(fileName).path(); @@ -425,7 +429,6 @@ static void processQdocconfFile(const QString &fileName) Parse each header file in the set using the appropriate parser and add it to the big tree. */ - QSet<CodeParser *> usedParsers; Generator::debug("Parsing header files"); int parsed = 0; @@ -436,15 +439,10 @@ static void processQdocconfFile(const QString &fileName) ++parsed; Generator::debug(QString("Parsing " + h.key())); codeParser->parseHeaderFile(config.location(), h.key()); - usedParsers.insert(codeParser); } ++h; } - foreach (CodeParser *codeParser, usedParsers) - codeParser->doneParsingHeaderFiles(); - - usedParsers.clear(); qdb->resolveInheritance(); /* @@ -460,19 +458,12 @@ static void processQdocconfFile(const QString &fileName) ++parsed; Generator::debug(QString("Parsing " + s.key())); codeParser->parseSourceFile(config.location(), s.key()); - usedParsers.insert(codeParser); } ++s; } Generator::debug(QString("Parsing done.")); /* - Currently these doneParsingSourceFiles() calls do nothing. - */ - foreach (CodeParser *codeParser, usedParsers) - codeParser->doneParsingSourceFiles(); - - /* Now the primary tree has been built from all the header and source files. Resolve all the class names, function names, targets, URLs, links, and other stuff that needs resolving. @@ -537,6 +528,7 @@ private: QCommandLineOption noLinkErrorsOption, autoLinkErrorsOption, debugOption; QCommandLineOption prepareOption, generateOption, logProgressOption; QCommandLineOption singleExecOption, writeQaPagesOption; + QCommandLineOption includePathOption, includePathSystemOption, frameworkOption; }; QDocCommandLineParser::QDocCommandLineParser() @@ -559,7 +551,10 @@ QDocCommandLineParser::QDocCommandLineParser() generateOption(QStringList() << QStringLiteral("generate")), logProgressOption(QStringList() << QStringLiteral("log-progress")), singleExecOption(QStringList() << QStringLiteral("single-exec")), - writeQaPagesOption(QStringList() << QStringLiteral("write-qa-pages")) + writeQaPagesOption(QStringList() << QStringLiteral("write-qa-pages")), + includePathOption("I", "Add dir to the include path for header files.", "path"), + includePathSystemOption("isystem", "Add dir to the system include path for header files.", "path"), + frameworkOption("F", "Add macOS framework to the include path for header files.", "framework") { setApplicationDescription(QCoreApplication::translate("qdoc", "Qt documentation generator")); addHelpOption(); @@ -631,6 +626,14 @@ QDocCommandLineParser::QDocCommandLineParser() writeQaPagesOption.setDescription(QCoreApplication::translate("qdoc", "Write QA pages.")); addOption(writeQaPagesOption); + + includePathOption.setFlags(QCommandLineOption::ShortOptionStyle); + addOption(includePathOption); + + addOption(includePathSystemOption); + + frameworkOption.setFlags(QCommandLineOption::ShortOptionStyle); + addOption(frameworkOption); } void QDocCommandLineParser::process(const QCoreApplication &app) @@ -676,6 +679,17 @@ void QDocCommandLineParser::process(const QCoreApplication &app) if (isSet(logProgressOption)) Location::startLoggingProgress(); + QDir currentDir = QDir::current(); + const auto paths = values(includePathOption); + for (const auto &i : paths) + includesPaths << "-I" << currentDir.absoluteFilePath(i); + const auto paths2 = values(includePathSystemOption); + for (const auto &i : paths2) + includesPaths << "-isystem" << currentDir.absoluteFilePath(i); + const auto paths3 = values(frameworkOption); + for (const auto &i : paths3) + includesPaths << "-F" << currentDir.absoluteFilePath(i); + /* The default indent for code is 0. The default value for false is 0. @@ -713,7 +727,7 @@ int main(int argc, char **argv) Create code parsers for the languages to be parsed, and create a tree for C++. */ - CppCodeParser cppParser; + ClangCodeParser clangParser; QmlCodeParser qmlParser; PureDocParser docParser; diff --git a/src/qdoc/node.cpp b/src/qdoc/node.cpp index 9919317f2..f1af55ca6 100644 --- a/src/qdoc/node.cpp +++ b/src/qdoc/node.cpp @@ -35,6 +35,7 @@ #include <qdebug.h> #include "generator.h" #include "tokenizer.h" +#include "puredocparser.h" QT_BEGIN_NAMESPACE @@ -930,7 +931,7 @@ FunctionNode *Aggregate::findFunctionNode(const QString& name, const QString& pa bool isQPrivateSignal = false; // Not used in the search QVector<Parameter> testParams; if (!params.isEmpty()) { - CppCodeParser* cppParser = CppCodeParser::cppParser(); + CppCodeParser* cppParser = PureDocParser::pureDocParser(); cppParser->parseParameters(params, testParams, isQPrivateSignal); } NodeList funcs = secondaryFunctionMap_.value(name); diff --git a/src/qdoc/node.h b/src/qdoc/node.h index 33cdd3c1a..f5507293c 100644 --- a/src/qdoc/node.h +++ b/src/qdoc/node.h @@ -849,6 +849,7 @@ public: const QString& rightType() const { return rightType_; } const QString& name() const { return name_; } const QString& defaultValue() const { return defaultValue_; } + void setDefaultValue(const QString& defaultValue) { defaultValue_ = defaultValue; } QString reconstruct(bool value = false) const; diff --git a/src/qdoc/puredocparser.cpp b/src/qdoc/puredocparser.cpp index 16a6e831f..636c54d26 100644 --- a/src/qdoc/puredocparser.cpp +++ b/src/qdoc/puredocparser.cpp @@ -42,11 +42,14 @@ QT_BEGIN_NAMESPACE +PureDocParser *PureDocParser::pureParser_ = 0; + /*! Constructs the pure doc parser. */ PureDocParser::PureDocParser() { + pureParser_ = this; } /*! @@ -54,6 +57,7 @@ PureDocParser::PureDocParser() */ PureDocParser::~PureDocParser() { + pureParser_ = 0; } /*! diff --git a/src/qdoc/puredocparser.h b/src/qdoc/puredocparser.h index ec8d6e389..59a8a850a 100644 --- a/src/qdoc/puredocparser.h +++ b/src/qdoc/puredocparser.h @@ -55,8 +55,11 @@ public: virtual QStringList sourceFileNameFilter() Q_DECL_OVERRIDE; virtual void parseSourceFile(const Location& location, const QString& filePath) Q_DECL_OVERRIDE; + static PureDocParser *pureDocParser() { return pureParser_; } + private: bool processQdocComments(); + static PureDocParser *pureParser_; }; QT_END_NAMESPACE diff --git a/src/qdoc/qdoc.pro b/src/qdoc/qdoc.pro index a49ddc555..36a14b9d8 100644 --- a/src/qdoc/qdoc.pro +++ b/src/qdoc/qdoc.pro @@ -1,3 +1,4 @@ + !force_bootstrap { requires(qtConfig(xmlstreamwriter)) } @@ -10,6 +11,11 @@ qtHaveModule(qmldevtools-private) { DEFINES += QT_NO_DECLARATIVE } +LIBS += $$CLANG_LIBS +INCLUDEPATH += $$CLANG_INCLUDEPATH +!disable_external_rpath: QMAKE_RPATHDIR += $$CLANG_LIBDIR +DEFINES += $$shell_quote(CLANG_RESOURCE_DIR=\"$${CLANG_LIBDIR}/clang/$${CLANG_VERSION}/include\") + DEFINES += \ QDOC2_COMPAT @@ -22,6 +28,7 @@ win32-msvc*:{ } HEADERS += atom.h \ + clangcodeparser.h \ codechunk.h \ codemarker.h \ codeparser.h \ @@ -47,6 +54,7 @@ HEADERS += atom.h \ tokenizer.h \ tree.h SOURCES += atom.cpp \ + clangcodeparser.cpp \ codechunk.cpp \ codemarker.cpp \ codeparser.cpp \ diff --git a/src/qdoc/qmlcodeparser.cpp b/src/qdoc/qmlcodeparser.cpp index 31da874fe..31775bb1b 100644 --- a/src/qdoc/qmlcodeparser.cpp +++ b/src/qdoc/qmlcodeparser.cpp @@ -204,14 +204,6 @@ void QmlCodeParser::parseSourceFile(const Location& location, const QString& fil #endif } -/*! - Performs cleanup after qdoc is done parsing all the QML files. - Currently, no cleanup is required. - */ -void QmlCodeParser::doneParsingSourceFiles() -{ -} - static QSet<QString> topicCommands_; /*! Returns the set of strings representing the topic commands. diff --git a/src/qdoc/qmlcodeparser.h b/src/qdoc/qmlcodeparser.h index 1b317a1c1..231f163b7 100644 --- a/src/qdoc/qmlcodeparser.h +++ b/src/qdoc/qmlcodeparser.h @@ -61,7 +61,6 @@ public: virtual QString language() Q_DECL_OVERRIDE; virtual QStringList sourceFileNameFilter() Q_DECL_OVERRIDE; virtual void parseSourceFile(const Location& location, const QString& filePath) Q_DECL_OVERRIDE; - virtual void doneParsingSourceFiles() Q_DECL_OVERRIDE; #ifndef QT_NO_DECLARATIVE /* Copied from src/declarative/qml/qdeclarativescriptparser.cpp */ diff --git a/src/src.pro b/src/src.pro index 41064a5d5..ed370052c 100644 --- a/src/src.pro +++ b/src/src.pro @@ -13,13 +13,14 @@ qtHaveModule(widgets) { } SUBDIRS += linguist \ - qdoc \ qtattributionsscanner qtConfig(library) { !android|android_app: SUBDIRS += qtplugininfo } +config_clang: SUBDIRS += qdoc + if(!android|android_app):!uikit: SUBDIRS += qtpaths mac { |