From 4f1de1432c0a028ac68d5a800f3b670ce539d848 Mon Sep 17 00:00:00 2001 From: Martin Smith Date: Thu, 9 Oct 2014 12:44:07 +0200 Subject: [PATCH] qdoc: Generate QA pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit qdoc has a new command line option, -write-qa-pages. Using this flag on the command line will tell qdoc to generate a QA html page in each module's output directory. The QA page contains information that is useful for Quality Assurance checking of the module's docs. The QA file name begins with "aaa" so it will always be listed at the top of the output directory. The file name for the QA file for QtCore, for example, is aaa-qtcore-qa-page.html. Currently, the QA page only contains a report listing the intermodule link count for each module that is the target of links from the documented module. The link report can be used to optimize the search order qdoc uses when resolving inter-module links. By default, the search order is the same as the ordering of the modules in the depends list in the .qdocconf file. Using the report, the user can reorder that list according to the number of links found in each module. i.e. in descending order of link count. The modules are listed in descending order of link count. Additionally, an actual depends variable is printed. It can be cut and pasted into the module's qdocconf file. Change-Id: I442596aeb54dcdd5db4a0821096a5273c15627e6 Task-number: QTBUG-41850 Reviewed-by: Topi Reiniƶ --- src/tools/qdoc/config.cpp | 1 + src/tools/qdoc/config.h | 2 ++ src/tools/qdoc/generator.cpp | 7 ++-- src/tools/qdoc/generator.h | 8 +++-- src/tools/qdoc/htmlgenerator.cpp | 48 +++++++++++++++++++++++++++ src/tools/qdoc/htmlgenerator.h | 1 + src/tools/qdoc/location.cpp | 12 ++++++- src/tools/qdoc/location.h | 4 ++- src/tools/qdoc/main.cpp | 18 +++++++--- src/tools/qdoc/qdocdatabase.cpp | 57 ++++++++++++++++++++++++++++++-- src/tools/qdoc/qdocdatabase.h | 13 ++++++++ src/tools/qdoc/tree.cpp | 1 + src/tools/qdoc/tree.h | 4 +++ 13 files changed, 163 insertions(+), 13 deletions(-) diff --git a/src/tools/qdoc/config.cpp b/src/tools/qdoc/config.cpp index bd72fc106b..f73ac147b8 100644 --- a/src/tools/qdoc/config.cpp +++ b/src/tools/qdoc/config.cpp @@ -119,6 +119,7 @@ QString ConfigStrings::FILEEXTENSIONS = QStringLiteral("fileextensions"); QString ConfigStrings::IMAGEEXTENSIONS = QStringLiteral("imageextensions"); QString ConfigStrings::QMLONLY = QStringLiteral("qmlonly"); QString ConfigStrings::QMLTYPESPAGE = QStringLiteral("qmltypespage"); +QString ConfigStrings::WRITEQAPAGES = QStringLiteral("writeqapages"); /*! An entry in a stack, where each entry is a list diff --git a/src/tools/qdoc/config.h b/src/tools/qdoc/config.h index c4d4f27a9e..a836448719 100644 --- a/src/tools/qdoc/config.h +++ b/src/tools/qdoc/config.h @@ -231,6 +231,7 @@ struct ConfigStrings static QString IMAGEEXTENSIONS; static QString QMLONLY; static QString QMLTYPESPAGE; + static QString WRITEQAPAGES; }; #define CONFIG_ALIAS ConfigStrings::ALIAS @@ -305,6 +306,7 @@ struct ConfigStrings #define CONFIG_IMAGEEXTENSIONS ConfigStrings::IMAGEEXTENSIONS #define CONFIG_QMLONLY ConfigStrings::QMLONLY #define CONFIG_QMLTYPESPAGE ConfigStrings::QMLTYPESPAGE +#define CONFIG_WRITEQAPAGES ConfigStrings::WRITEQAPAGES QT_END_NAMESPACE diff --git a/src/tools/qdoc/generator.cpp b/src/tools/qdoc/generator.cpp index 6f53783aaf..db531e3936 100644 --- a/src/tools/qdoc/generator.cpp +++ b/src/tools/qdoc/generator.cpp @@ -64,7 +64,7 @@ QString Generator::outSubdir_; QStringList Generator::outFileNames_; QSet Generator::outputFormats; QHash Generator::outputPrefixes; -QString Generator::project; +QString Generator::project_; QStringList Generator::scriptDirs; QStringList Generator::scriptFiles; QString Generator::sinceTitles[] = @@ -94,6 +94,7 @@ bool Generator::autolinkErrors_ = false; bool Generator::redirectDocumentationToDevNull_ = false; Generator::QDocPass Generator::qdocPass_ = Generator::Neither; bool Generator::qdocSingleExec_ = false; +bool Generator::qdocWriteQaPages_ = false; bool Generator::useOutputSubdirs_ = true; void Generator::startDebugging(const QString& message) @@ -317,7 +318,7 @@ QString Generator::fileBase(const Node *node) const if (node->isExample() || node->isExampleFile()) { QString modPrefix(node->moduleName()); if (modPrefix.isEmpty()) { - modPrefix = project; + modPrefix = project_; } base.prepend(modPrefix.toLower() + QLatin1Char('-')); } @@ -1646,7 +1647,7 @@ void Generator::initialize(const Config &config) ++n; } - project = config.getString(CONFIG_PROJECT); + project_ = config.getString(CONFIG_PROJECT); QStringList prefixes = config.getStringList(CONFIG_OUTPUTPREFIXES); if (!prefixes.isEmpty()) { diff --git a/src/tools/qdoc/generator.h b/src/tools/qdoc/generator.h index 306a1596ae..b1faf02ae9 100644 --- a/src/tools/qdoc/generator.h +++ b/src/tools/qdoc/generator.h @@ -95,8 +95,10 @@ public: static bool preparing() { return (qdocPass_ == Prepare); } static bool generating() { return (qdocPass_ == Generate); } static bool singleExec() { return qdocSingleExec_; } + static bool writeQaPages() { return qdocWriteQaPages_; } static void setSingleExec() { qdocSingleExec_ = true; } - static QString defaultModuleName() { return project; } + static void setWriteQaPages() { qdocWriteQaPages_ = true; } + static QString defaultModuleName() { return project_; } static void resetUseOutputSubdirs() { useOutputSubdirs_ = false; } static bool useOutputSubdirs() { return useOutputSubdirs_; } @@ -105,6 +107,7 @@ protected: virtual void endSubPage(); virtual QString fileBase(const Node* node) const; virtual QString fileExtension() const = 0; + virtual void generateQAPage() { } virtual void generateAlsoList(const Node *node, CodeMarker *marker); virtual int generateAtom(const Atom *atom, const Node *relative, CodeMarker *marker); virtual void generateBody(const Node *node, CodeMarker *marker); @@ -200,7 +203,7 @@ private: static QStringList imageDirs; static QStringList imageFiles; static QMap imgFileExts; - static QString project; + static QString project_; static QString outDir_; static QString outSubdir_; static QStringList outFileNames_; @@ -216,6 +219,7 @@ private: static bool redirectDocumentationToDevNull_; static QDocPass qdocPass_; static bool qdocSingleExec_; + static bool qdocWriteQaPages_; static bool useOutputSubdirs_; void generateReimplementedFrom(const FunctionNode *func, CodeMarker *marker); diff --git a/src/tools/qdoc/htmlgenerator.cpp b/src/tools/qdoc/htmlgenerator.cpp index 91f5ec1fcb..cbd8c1ddef 100644 --- a/src/tools/qdoc/htmlgenerator.cpp +++ b/src/tools/qdoc/htmlgenerator.cpp @@ -280,6 +280,8 @@ void HtmlGenerator::generateDocs() qflagsHref_ = linkForNode(qflags,0); if (!preparing()) Generator::generateDocs(); + if (Generator::generating() && Generator::writeQaPages()) + generateQAPage(); if (!generating()) { QString fileBase = project.toLower().simplified().replace(QLatin1Char(' '), QLatin1Char('-')); @@ -300,6 +302,46 @@ void HtmlGenerator::generateDocs() } } +/*! + Output the module's Quality Assurance page. + */ +void HtmlGenerator::generateQAPage() +{ + NamespaceNode* node = qdb_->primaryTreeRoot(); + beginSubPage(node, "aaa-" + defaultModuleName().toLower() + "-qa-page.html"); + CodeMarker* marker = CodeMarker::markerForFileName(node->location().filePath()); + QString title = "Quality Assurance Page for " + defaultModuleName(); + QString t = "Quality assurance information for checking the " + defaultModuleName() + " documentation."; + generateHeader(title, node, marker); + generateTitle(title, Text() << t, LargeSubTitle, node, marker); + + QStringList strings; + QVector counts; + QString depends = qdb_->getLinkCounts(strings, counts); + if (!strings.isEmpty()) { + t = "Intermodule Link Counts"; + QString ref = registerRef(t); + out() << "" << divNavTop << '\n'; + out() << "

" << protectEnc(t) << "

\n"; + out() << "" + << "\n"; + for (int i = 0; i< strings.size(); ++i) { + out() << "\n"; + } + out() << "
Destination ModuleLink Count
" << strings.at(i) + << "" << counts.at(i) + << "
\n"; + t = "The Optimal \"depends\" Variable"; + out() << "

" << protectEnc(t) << "

\n"; + t = "Consider replacing the depends variable in " + defaultModuleName().toLower() + + ".qdocconf with this one, if the two are not identical:"; + out() << "

" << protectEnc(t) << "

\n"; + out() << "

" << protectEnc(depends) << "

\n"; + } + endSubPage(); +} + /*! Generate html from an instance of Atom. */ @@ -3676,6 +3718,9 @@ QString HtmlGenerator::getLink(const Atom *atom, const Node *relative, const Nod if (!(*node)) return QString(); + if (Generator::writeQaPages()) + qdb_->incrementLinkCount(*node); + QString url = (*node)->url(); if (!url.isEmpty()) { if (ref.isEmpty()) @@ -3720,6 +3765,9 @@ QString HtmlGenerator::getAutoLink(const Atom *atom, const Node *relative, const if (!(*node)) return QString(); + if (Generator::writeQaPages()) + qdb_->incrementLinkCount(*node); + QString url = (*node)->url(); if (!url.isEmpty()) { if (ref.isEmpty()) diff --git a/src/tools/qdoc/htmlgenerator.h b/src/tools/qdoc/htmlgenerator.h index 616a989361..825522a121 100644 --- a/src/tools/qdoc/htmlgenerator.h +++ b/src/tools/qdoc/htmlgenerator.h @@ -89,6 +89,7 @@ public: static QString sinceTitle(int i) { return sinceTitles[i]; } protected: + virtual void generateQAPage(); virtual int generateAtom(const Atom *atom, const Node *relative, CodeMarker *marker); diff --git a/src/tools/qdoc/location.cpp b/src/tools/qdoc/location.cpp index ca06eeb283..923901fc34 100644 --- a/src/tools/qdoc/location.cpp +++ b/src/tools/qdoc/location.cpp @@ -285,6 +285,15 @@ void Location::fatal(const QString& message, const QString& details) const exit(EXIT_FAILURE); } +/*! + Writes \a message and \a detals to stderr as a formatted + report message. + */ +void Location::report(const QString& message, const QString& details) const +{ + emitMessage(Report, message, details); +} + /*! Gets several parameters from the \a config, including tab size, program name, and a regular expression that @@ -371,7 +380,8 @@ void Location::emitMessage(MessageType type, result.prepend(tr(": error: ")); else if (type == Warning) result.prepend(tr(": warning: ")); - result.prepend(toString()); + if (type != Report) + result.prepend(toString()); fprintf(stderr, "%s\n", result.toLatin1().data()); fflush(stderr); } diff --git a/src/tools/qdoc/location.h b/src/tools/qdoc/location.h index eb4dbbae98..b6589fd6ab 100644 --- a/src/tools/qdoc/location.h +++ b/src/tools/qdoc/location.h @@ -81,6 +81,8 @@ public: const QString& details = QString()) const; void fatal(const QString& message, const QString& details = QString()) const; + void report(const QString& message, + const QString& details = QString()) const; static const Location null; @@ -94,7 +96,7 @@ public: static QString canonicalRelativePath(const QString &path); private: - enum MessageType { Warning, Error }; + enum MessageType { Warning, Error, Report }; struct StackEntry { diff --git a/src/tools/qdoc/main.cpp b/src/tools/qdoc/main.cpp index 79fd174a08..186fd3195b 100644 --- a/src/tools/qdoc/main.cpp +++ b/src/tools/qdoc/main.cpp @@ -71,6 +71,7 @@ bool creationTimeBefore(const QFileInfo &fi1, const QFileInfo &fi2) static bool highlighting = false; static bool showInternal = false; static bool singleExec = false; +static bool writeQaPages = false; static bool redirectDocumentationToDevNull = false; static bool noLinkErrors = false; static bool autolinkErrors = false; @@ -221,6 +222,7 @@ static void processQdocconfFile(const QString &fileName) config.setStringList(CONFIG_SYNTAXHIGHLIGHTING, QStringList(highlighting ? "true" : "false")); config.setStringList(CONFIG_SHOWINTERNAL, QStringList(showInternal ? "true" : "false")); config.setStringList(CONFIG_SINGLEEXEC, QStringList(singleExec ? "true" : "false")); + config.setStringList(CONFIG_WRITEQAPAGES, QStringList(writeQaPages ? "true" : "false")); config.setStringList(CONFIG_REDIRECTDOCUMENTATIONTODEVNULL, QStringList(redirectDocumentationToDevNull ? "true" : "false")); config.setStringList(CONFIG_NOLINKERRORS, QStringList(noLinkErrors ? "true" : "false")); config.setStringList(CONFIG_AUTOLINKERRORS, QStringList(autolinkErrors ? "true" : "false")); @@ -459,7 +461,6 @@ static void processQdocconfFile(const QString &fileName) codeParser->doneParsingHeaderFiles(); usedParsers.clear(); - //qDebug() << "CALL: resolveInheritance()"; qdb->resolveInheritance(); /* @@ -490,7 +491,6 @@ static void processQdocconfFile(const QString &fileName) targets, URLs, links, and other stuff that needs resolving. */ Generator::debug("Resolving stuff prior to generating docs"); - //qDebug() << "CALL: resolveIssues()"; qdb->resolveIssues(); } else { @@ -533,7 +533,6 @@ static void processQdocconfFile(const QString &fileName) one. */ Generator::debug("Generating docs"); - //qDebug() << "CALL: generateDocs()"; QSet::ConstIterator of = outputFormats.constBegin(); while (of != outputFormats.constEnd()) { Generator* generator = Generator::generatorForFormat(*of); @@ -543,8 +542,12 @@ static void processQdocconfFile(const QString &fileName) generator->generateDocs(); ++of; } +#if 0 + if (Generator::generating() && Generator::writeQaPages()) + qdb->printLinkCounts(project); +#endif + qdb->clearLinkCounts(); - //Generator::writeOutFileNames(); Generator::debug("Terminating qdoc classes"); if (Generator::debugging()) Generator::stopDebugging(project); @@ -681,6 +684,10 @@ int main(int argc, char **argv) singleExecOption.setDescription(QCoreApplication::translate("qdoc", "Run qdoc once over all the qdoc conf files.")); parser.addOption(singleExecOption); + QCommandLineOption writeQaPagesOption(QStringList() << QStringLiteral("write-qa-pages")); + writeQaPagesOption.setDescription(QCoreApplication::translate("qdoc", "Write QA pages.")); + parser.addOption(writeQaPagesOption); + parser.process(app); defines += parser.values(defineOption); @@ -688,6 +695,7 @@ int main(int argc, char **argv) highlighting = parser.isSet(highlightingOption); showInternal = parser.isSet(showInternalOption); singleExec = parser.isSet(singleExecOption); + writeQaPages = parser.isSet(writeQaPagesOption); redirectDocumentationToDevNull = parser.isSet(redirectDocumentationToDevNullOption); Config::generateExamples = !parser.isSet(noExamplesOption); foreach (const QString &indexDir, parser.values(indexDirOption)) { @@ -713,6 +721,8 @@ int main(int argc, char **argv) Generator::setQDocPass(Generator::Generate); if (parser.isSet(singleExecOption)) Generator::setSingleExec(); + if (parser.isSet(writeQaPagesOption)) + Generator::setWriteQaPages(); if (parser.isSet(logProgressOption)) Location::startLoggingProgress(); diff --git a/src/tools/qdoc/qdocdatabase.cpp b/src/tools/qdoc/qdocdatabase.cpp index a22244d174..4b4c88a176 100644 --- a/src/tools/qdoc/qdocdatabase.cpp +++ b/src/tools/qdoc/qdocdatabase.cpp @@ -274,7 +274,6 @@ const QVector& QDocForest::indexSearchOrder() */ NamespaceNode* QDocForest::newIndexTree(const QString& module) { - //qDebug() << " New index tree:" << module; primaryTree_ = new Tree(module, qdb_); forest_.insert(module, primaryTree_); return primaryTree_->root(); @@ -286,7 +285,6 @@ NamespaceNode* QDocForest::newIndexTree(const QString& module) */ void QDocForest::newPrimaryTree(const QString& module) { - //qDebug() << " New primary tree:" << module; primaryTree_ = new Tree(module, qdb_); } @@ -351,6 +349,61 @@ void QDocForest::mergeCollectionMaps(Node::Type nt, CNMultiMap& cnmm) } } +/*! + Print the list of module names ordered according + to how many successful searches each tree had. + */ +void QDocForest::printLinkCounts(const QString& project) +{ + Location::null.report(QString("%1: Link Counts").arg(project)); + QMultiMap m; + foreach (Tree* t, searchOrder()) { + if (t->linkCount() < 0) + m.insert(t->linkCount(), t->moduleName()); + } + QString depends = "depends +="; + QString module = project.toLower(); + QMultiMap::iterator i = m.begin(); + while (i != m.end()) { + QString line = " " + i.value(); + if (i.value() != module) + depends += " " + i.value(); + int pad = 30 - line.length(); + for (int k=0; k& counts) +{ + QMultiMap m; + foreach (Tree* t, searchOrder()) { + if (t->linkCount() < 0) + m.insert(t->linkCount(), t->moduleName()); + } + QString depends = "depends +="; + QString module = Generator::defaultModuleName().toLower(); + QMultiMap::iterator i = m.begin(); + while (i != m.end()) { + if (i.value() != module) { + counts.append(-(i.key())); + strings.append(i.value()); + depends += " " + i.value(); + } + ++i; + } + return depends; +} + /*! \class QDocDatabase This class provides exclusive access to the qdoc database, which consists of a forrest of trees and a lot of maps and diff --git a/src/tools/qdoc/qdocdatabase.h b/src/tools/qdoc/qdocdatabase.h index f6deeeece8..02d39e62e7 100644 --- a/src/tools/qdoc/qdocdatabase.h +++ b/src/tools/qdoc/qdocdatabase.h @@ -194,6 +194,13 @@ class QDocForest } void clearSearchOrder() { searchOrder_.clear(); } + void clearLinkCounts() + { + foreach (Tree* t, searchOrder()) + t->clearLinkCount(); + } + void printLinkCounts(const QString& project); + QString getLinkCounts(QStringList& strings, QVector& counts); private: void newPrimaryTree(const QString& module); @@ -379,6 +386,12 @@ class QDocDatabase void mergeCollections(Node::Type nt, CNMap& cnm, const Node* relative); void mergeCollections(CollectionNode* cn); void clearSearchOrder() { forest_.clearSearchOrder(); } + void incrementLinkCount(const Node* t) { t->tree()->incrementLinkCount(); } + void clearLinkCounts() { forest_.clearLinkCounts(); } + void printLinkCounts(const QString& t) { forest_.printLinkCounts(t); } + QString getLinkCounts(QStringList& strings, QVector& counts) { + return forest_.getLinkCounts(strings, counts); + } private: friend class QDocIndexFiles; diff --git a/src/tools/qdoc/tree.cpp b/src/tools/qdoc/tree.cpp index d4169f6242..f3a6ed78da 100644 --- a/src/tools/qdoc/tree.cpp +++ b/src/tools/qdoc/tree.cpp @@ -68,6 +68,7 @@ QT_BEGIN_NAMESPACE Tree::Tree(const QString& module, QDocDatabase* qdb) : treeHasBeenAnalyzed_(false), docsHaveBeenGenerated_(false), + linkCount_(0), module_(module), qdb_(qdb), root_(0, QString()) diff --git a/src/tools/qdoc/tree.h b/src/tools/qdoc/tree.h index 5f11a81405..75f751f976 100644 --- a/src/tools/qdoc/tree.h +++ b/src/tools/qdoc/tree.h @@ -197,10 +197,14 @@ class Tree public: const QString& moduleName() const { return module_; } const QString& indexFileName() const { return indexFileName_; } + void incrementLinkCount() { --linkCount_; } + void clearLinkCount() { linkCount_ = 0; } + int linkCount() const { return linkCount_; } private: bool treeHasBeenAnalyzed_; bool docsHaveBeenGenerated_; + int linkCount_; QString module_; QString indexFileName_; QDocDatabase* qdb_;