diff options
-rw-r--r-- | src/qdoc/clangcodeparser.cpp | 231 | ||||
-rw-r--r-- | src/qdoc/clangcodeparser.h | 1 | ||||
-rw-r--r-- | src/qdoc/main.cpp | 2 | ||||
-rw-r--r-- | src/qdoc/node.h | 6 | ||||
-rw-r--r-- | src/qdoc/qdocdatabase.h | 1 | ||||
-rw-r--r-- | src/qdoc/tree.cpp | 25 | ||||
-rw-r--r-- | src/qdoc/tree.h | 1 |
7 files changed, 256 insertions, 11 deletions
diff --git a/src/qdoc/clangcodeparser.cpp b/src/qdoc/clangcodeparser.cpp index 4b455414f..983b0b9ba 100644 --- a/src/qdoc/clangcodeparser.cpp +++ b/src/qdoc/clangcodeparser.cpp @@ -151,7 +151,6 @@ QString functionName(CXCursor cursor) return name; } - /*! Find the node from the QDocDatabase \a qdb that corrseponds to the declaration represented by the cursor \a cur, if it exists. @@ -239,11 +238,85 @@ static Node *findNodeForCursor(QDocDatabase* qdb, CXCursor cur) { } } +/*! + Find the function node from the QDocDatabase \a qdb that + corrseponds to the declaration represented by the cursor + \a cur, if it exists. + */ +static Node *findFunctionNodeForCursor(QDocDatabase* qdb, CXCursor cur) { + auto kind = clang_getCursorKind(cur); + if (clang_isInvalid(kind)) + return nullptr; + if (kind == CXCursor_TranslationUnit) + return qdb->primaryTreeRoot(); + + Node *p = findNodeForCursor(qdb, clang_getCursorSemanticParent(cur)); + if (!p || !p->isAggregate()) + return nullptr; + auto parent = static_cast<Aggregate *>(p); + + switch (kind) { + case CXCursor_FunctionDecl: + case CXCursor_FunctionTemplate: + case CXCursor_CXXMethod: + case CXCursor_Constructor: + case CXCursor_Destructor: + case CXCursor_ConversionFunction: { + NodeList candidates; + parent->findChildren(functionName(cur), candidates); + if (candidates.isEmpty()) + return nullptr; + CXType funcType = clang_getCursorType(cur); + auto numArg = clang_getNumArgTypes(funcType); + bool isVariadic = clang_isFunctionTypeVariadic(funcType); + QVarLengthArray<QString, 20> args; + for (Node *candidate : qAsConst(candidates)) { + if (!candidate->isFunction()) + continue; + auto fn = static_cast<FunctionNode*>(candidate); + const auto &funcParams = fn->parameters(); + const int actualArg = numArg - fn->isPrivateSignal(); + if (funcParams.count() != (actualArg + isVariadic)) + continue; + if (fn->isConst() != bool(clang_CXXMethod_isConst(cur))) + continue; + if (isVariadic && funcParams.last().dataType() != QLatin1String("...")) + continue; + bool different = false; + for (int i = 0; i < actualArg; i++) { + if (args.size() <= i) + args.append(fromCXString(clang_getTypeSpelling(clang_getArgType(funcType, i)))); + QString t1 = funcParams.at(i).dataType(); + QString t2 = args.at(i); + auto p2 = parent; + while (p2 && t1 != t2) { + QString parentScope = p2->name() + QLatin1String("::"); + t1 = t1.remove(parentScope); + t2 = t2.remove(parentScope); + p2 = p2->parent(); + } + if (t1 != t2) { + different = true; + break; + } + } + if (!different) + return fn; + } + break; + } + default: + break; + } + return nullptr; +} + class ClangVisitor { public: ClangVisitor(QDocDatabase *qdb, const QSet<QString> &allHeaders) - : qdb_(qdb), parent_(qdb->primaryTreeRoot()), allHeaders_(allHeaders) - {} + : qdb_(qdb), parent_(qdb->primaryTreeRoot()), allHeaders_(allHeaders) { } + + QDocDatabase* qdocDB() { return qdb_; } CXChildVisitResult visitChildren(CXCursor cursor) { @@ -272,11 +345,26 @@ public: return ret ? CXChildVisit_Break : CXChildVisit_Continue; } + /* + Not sure about all the possibilities, when the cursor + location is not in the main file. + */ + CXChildVisitResult visitFnArg(CXCursor cursor, Node** fnNode, bool &ignoreSignature) + { + auto ret = visitChildrenLambda(cursor, [&](CXCursor cur) { + auto loc = clang_getCursorLocation(cur); + if (clang_Location_isFromMainFile(loc)) + return visitFnSignature(cur, loc, fnNode, ignoreSignature); + return CXChildVisit_Continue; + }); + return ret ? CXChildVisit_Break : CXChildVisit_Continue; + } + Node *nodeForCommentAtLocation(CXSourceLocation loc, CXSourceLocation nextCommentLoc); private: - /*! \class SimpleLoc - Represents a simple location in the main source file, - which can be used as a key in a QMap + /*! + SimpleLoc represents a simple location in the main source file, + which can be used as a key in a QMap. */ struct SimpleLoc { @@ -338,6 +426,7 @@ private: CXChildVisitResult visitSource(CXCursor cursor, CXSourceLocation loc); CXChildVisitResult visitHeader(CXCursor cursor, CXSourceLocation loc); + CXChildVisitResult visitFnSignature(CXCursor cursor, CXSourceLocation loc, Node** fnNode, bool &ignoreSignature); void parseProperty(const QString &spelling, const Location &loc); void readParameterNamesAndAttributes(FunctionNode* fn, CXCursor cursor); Aggregate *getSemanticParent(CXCursor cursor); @@ -377,6 +466,25 @@ Aggregate *ClangVisitor::getSemanticParent(CXCursor cursor) return parent_; } +CXChildVisitResult ClangVisitor::visitFnSignature(CXCursor cursor, CXSourceLocation , Node** fnNode, bool &ignoreSignature) +{ + switch (clang_getCursorKind(cursor)) { + case CXCursor_FunctionDecl: + case CXCursor_FunctionTemplate: + case CXCursor_CXXMethod: + case CXCursor_Constructor: + case CXCursor_Destructor: + case CXCursor_ConversionFunction: { + ignoreSignature = ignoredSymbol(functionName(cursor)); + *fnNode = ignoreSignature ? 0 : findFunctionNodeForCursor(qdb_, cursor); + break; + } + default: + break; + } + return CXChildVisit_Continue; +} + CXChildVisitResult ClangVisitor::visitHeader(CXCursor cursor, CXSourceLocation loc) { auto kind = clang_getCursorKind(cursor); @@ -475,6 +583,19 @@ CXChildVisitResult ClangVisitor::visitHeader(CXCursor cursor, CXSourceLocation l CXType funcType = clang_getCursorType(cursor); FunctionNode* fn = new FunctionNode(Node::Function, parent_, name, false); + + CXSourceRange range = clang_Cursor_getCommentRange(cursor); + if (!clang_Range_isNull(range)) { + QString comment = getSpelling(range); + if (comment.startsWith("//!")) { + int tag = comment.indexOf(QChar('[')); + if (tag > 0) { + int end = comment.indexOf(QChar(']'), tag); + if (end > 0) + fn->setTag(comment.mid(tag, 1 + end - tag)); + } + } + } fn->setAccess(fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor))); fn->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor))); if (kind == CXCursor_Constructor @@ -1044,11 +1165,11 @@ void ClangCodeParser::buildPCH() qWarning() << "(qdoc) Could not save PCH file for " << module << error; pchName_.clear(); } - // Visit the header now, as token from pre-compiled header won't be visited later CXCursor cur = clang_getTranslationUnitCursor(tu); ClangVisitor visitor(qdb_, allHeaders_); visitor.visitChildren(cur); + clang_disposeTranslationUnit(tu); } else { pchFileDir_->remove(); @@ -1105,7 +1226,7 @@ void ClangCodeParser::parseSourceFile(const Location& /*location*/, const QStrin CXErrorCode err = clang_parseTranslationUnit2(index_, filePath.toLocal8Bit(), args_.data(), args_.size(), nullptr, 0, flags_, &tu); if (err || !tu) { - qWarning() << "(qdoc) Could not parse " << filePath << " error code:" << err; + qWarning() << "(qdoc) Could not parse source file" << filePath << " error code:" << err; clang_disposeIndex(index_); return; } @@ -1216,7 +1337,7 @@ void ClangCodeParser::parseSourceFile(const Location& /*location*/, const QStrin while (a != args.constEnd()) { Doc nodeDoc = doc; if ((count > 1) && (topic == COMMAND_FN)) { - node = processFnCommand(*a, doc); + node = parseFnArg(doc.location(), a->first); if (node != 0) { if (scn == 0) { scn = new SharedCommentNode(node->parent(), count); @@ -1228,7 +1349,10 @@ void ClangCodeParser::parseSourceFile(const Location& /*location*/, const QStrin } } else { - node = processTopicCommand(nodeDoc, topic, *a); + if (topic == "fn") + node = parseFnArg(doc.location(), a->first); + else + node = processTopicCommand(nodeDoc, topic, *a); if (node != 0) { nodes.append(node); docs.append(nodeDoc); @@ -1264,4 +1388,91 @@ void ClangCodeParser::parseSourceFile(const Location& /*location*/, const QStrin clang_disposeIndex(index_); } +/*! + Use clang to parse the function signature from an fn command. + \a fnArg is the string to parse. It is always a function decl. + \a location is used for reporting errors. + */ +Node* ClangCodeParser::parseFnArg(const Location& location, const QString& fnArg) +{ + Node* fnNode = 0; + if (Generator::preparing() && !Generator::singleExec()) + return 0; + /* + If the \fn command begins with a tag, then don't try to + parse the \fn command with clang. Use the tag to search + for the correct function node. It is an error if it can + not be found. Return 0 in that case. + */ + if (fnArg.startsWith('[')) { + int end = fnArg.indexOf(QChar(']', 0)); + if (end > 1) { + QString tag = fnArg.left(end + 1); + fnNode = qdb_->findFunctionNodeForTag(tag); + if (!fnNode) + location.error(ClangCodeParser::tr("tag \\fn %1 not used in any include file in current module").arg(tag)); + } + return fnNode; + } + CXTranslationUnit_Flags flags = (CXTranslationUnit_Flags) (CXTranslationUnit_Incomplete | + CXTranslationUnit_SkipFunctionBodies | + CXTranslationUnit_KeepGoing); + CXIndex index = clang_createIndex(1, 0); + + std::vector<const char *> args(std::begin(defaultArgs_), std::end(defaultArgs_)); + // Add the defines from the qdocconf file. + for (const auto &p : qAsConst(defines_)) + args.push_back(p.constData()); + if (!pchName_.isEmpty()) { + args.push_back("-w"); + args.push_back("-include-pch"); + args.push_back(pchName_.constData()); + } + CXTranslationUnit tu; + QByteArray fn = fnArg.toUtf8(); + if (!fn.endsWith(";")) + fn += "{ }"; + const char *dummyFileName = "/fn_dummyfile.cpp"; + CXUnsavedFile unsavedFile { dummyFileName, fn.constData(), + static_cast<unsigned long>(fn.size()) }; + CXErrorCode err = clang_parseTranslationUnit2(index, dummyFileName, + args.data(), + args.size(), + &unsavedFile, + 1, + flags, + &tu); + + if (err || !tu) { + location.error(ClangCodeParser::tr("clang could not parse \\fn %1").arg(fnArg)); + clang_disposeTranslationUnit(tu); + clang_disposeIndex(index); + return 0; + } else { + /* + Always visit the tu if one is constructed, because + it might be possible to find the correct node, even + if clang detected diagnostics. Only bother to report + the diagnostics if they stop us finding the node. + */ + CXCursor cur = clang_getTranslationUnitCursor(tu); + ClangVisitor visitor(qdb_, allHeaders_); + bool ignoreSignature = false; + visitor.visitFnArg(cur, &fnNode, ignoreSignature); + if (fnNode == 0) { + unsigned diagnosticCount = clang_getNumDiagnostics(tu); + if (diagnosticCount > 0) { + location.warning(ClangCodeParser::tr("clang found diagnostics parsing \\fn %1").arg(fnArg)); + for (unsigned i = 0; i < diagnosticCount; ++i) { + CXDiagnostic diagnostic = clang_getDiagnostic(tu, i); + location.report(tr(" %1").arg(fromCXString(clang_formatDiagnostic(diagnostic, 0)))); + } + } + } + } + clang_disposeTranslationUnit(tu); + clang_disposeIndex(index); + return fnNode; +} + QT_END_NAMESPACE diff --git a/src/qdoc/clangcodeparser.h b/src/qdoc/clangcodeparser.h index e89228055..7c3f750ce 100644 --- a/src/qdoc/clangcodeparser.h +++ b/src/qdoc/clangcodeparser.h @@ -55,6 +55,7 @@ public: virtual void parseHeaderFile(const Location& location, const QString& filePath) Q_DECL_OVERRIDE; virtual void parseSourceFile(const Location& location, const QString& filePath) Q_DECL_OVERRIDE; virtual void precompileHeaders() Q_DECL_OVERRIDE; + Node* parseFnArg(const Location& location, const QString& fnArg); private: void getDefaultArgs(); diff --git a/src/qdoc/main.cpp b/src/qdoc/main.cpp index 8bdf0039d..3534abc0e 100644 --- a/src/qdoc/main.cpp +++ b/src/qdoc/main.cpp @@ -250,7 +250,6 @@ static void processQdocconfFile(const QString &fileName) config.load(fileName); QString project = config.getString(CONFIG_PROJECT); QString moduleHeader = config.getString(CONFIG_MODULEHEADER); - //qDebug() << "Start project:" << project; /* Add the defines to the configuration variables. */ @@ -273,6 +272,7 @@ static void processQdocconfFile(const QString &fileName) phase += "prepare phase "; else if (Generator::generating()) phase += "generate phase "; + QString msg = "Running qdoc for " + config.getString(CONFIG_PROJECT) + phase; Location::logToStdErr(msg); diff --git a/src/qdoc/node.h b/src/qdoc/node.h index 89223260b..46752a5fc 100644 --- a/src/qdoc/node.h +++ b/src/qdoc/node.h @@ -281,6 +281,7 @@ public: virtual void setObsoleteLink(const QString& ) { }; virtual void setQtVariable(const QString& ) { } virtual QString qtVariable() const { return QString(); } + virtual bool hasTag(const QString& ) const { return false; } const QMap<LinkType, QPair<QString,QString> >& links() const { return linkMap_; } void setLink(LinkType linkType, const QString &link, const QString &desc); @@ -1038,6 +1039,10 @@ public: const SharedCommentNode* collective() const { return collective_; } void setCollectiveNode(SharedCommentNode* t) Q_DECL_OVERRIDE { collective_ = t; } + virtual bool hasTag(const QString& t) const Q_DECL_OVERRIDE { return (tag_ == t); } + void setTag(const QString& t) { tag_ = t; } + const QString &tag() const { return tag_; } + private: void addAssociatedProperty(PropertyNode* property); @@ -1066,6 +1071,7 @@ private: const FunctionNode* reimplementedFrom_; PropNodeList associatedProperties_; SharedCommentNode* collective_; + QString tag_; }; class PropertyNode : public LeafNode diff --git a/src/qdoc/qdocdatabase.h b/src/qdoc/qdocdatabase.h index 0af094878..53a18a870 100644 --- a/src/qdoc/qdocdatabase.h +++ b/src/qdoc/qdocdatabase.h @@ -349,6 +349,7 @@ class QDocDatabase const CollectionNode* getCollectionNode(const QString& name, Node::Genus genus) { return forest_.getCollectionNode(name, genus); } + Node *findFunctionNodeForTag(QString tag) { return primaryTree()->findFunctionNodeForTag(tag); } private: const Node* findNodeForTarget(QStringList& targetPath, diff --git a/src/qdoc/tree.cpp b/src/qdoc/tree.cpp index adc9f75b2..6364c2560 100644 --- a/src/qdoc/tree.cpp +++ b/src/qdoc/tree.cpp @@ -1523,4 +1523,29 @@ TargetList* Tree::getTargetList(const QString& module) return targetListMap_->value(module); } +/*! + Search this tree recursively from \a parent to find a function + node with the specified \a tag. If no function node is found + with the required \a tag, return 0. + */ +Node* Tree::findFunctionNodeForTag(const QString &tag, Aggregate* parent) +{ + if (!parent) + parent = root(); + const NodeList& children = parent->childNodes(); + for (Node *n : children) { + if (n && n->isFunction() && n->hasTag(tag)) + return n; + } + for (Node *n : children) { + if (n && n->isAggregate()) { + Aggregate* a = static_cast<Aggregate*>(n); + n = findFunctionNodeForTag(tag, a); + if (n) + return n; + } + } + return 0; +} + QT_END_NAMESPACE diff --git a/src/qdoc/tree.h b/src/qdoc/tree.h index 0cb07aff7..711fac598 100644 --- a/src/qdoc/tree.h +++ b/src/qdoc/tree.h @@ -216,6 +216,7 @@ class Tree bool broken); TargetList* getTargetList(const QString& module); QStringList getTargetListKeys() { return targetListMap_->keys(); } + Node* findFunctionNodeForTag(const QString &tag, Aggregate* parent = 0); public: const QString& camelCaseModuleName() const { return camelCaseModuleName_; } |