qdoc: Generate QA pages

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ö <topi.reinio@digia.com>
bb10
Martin Smith 2014-10-09 12:44:07 +02:00
parent 77165553af
commit 4f1de1432c
13 changed files with 163 additions and 13 deletions

View File

@ -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

View File

@ -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

View File

@ -64,7 +64,7 @@ QString Generator::outSubdir_;
QStringList Generator::outFileNames_;
QSet<QString> Generator::outputFormats;
QHash<QString, QString> 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()) {

View File

@ -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<QString, QStringList> 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);

View File

@ -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<int> counts;
QString depends = qdb_->getLinkCounts(strings, counts);
if (!strings.isEmpty()) {
t = "Intermodule Link Counts";
QString ref = registerRef(t);
out() << "<a name=\"" << ref << "\"></a>" << divNavTop << '\n';
out() << "<h2 id=\"" << ref << "\">" << protectEnc(t) << "</h2>\n";
out() << "<table class=\"valuelist\"><tr valign=\"top\" "
<< "class=\"even\"><th class=\"tblConst\">Destination Module</th>"
<< "<th class=\"tblval\">Link Count</th></tr>\n";
for (int i = 0; i< strings.size(); ++i) {
out() << "<tr><td class=\"topAlign\"><tt>" << strings.at(i)
<< "</tt></td><td class=\"topAlign\"><tt>" << counts.at(i)
<< "</tt></td></tr>\n";
}
out() << "</table>\n";
t = "The Optimal \"depends\" Variable";
out() << "<h2>" << protectEnc(t) << "</h2>\n";
t = "Consider replacing the depends variable in " + defaultModuleName().toLower() +
".qdocconf with this one, if the two are not identical:";
out() << "<p>" << protectEnc(t) << "</p>\n";
out() << "<p>" << protectEnc(depends) << "</p>\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())

View File

@ -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);

View File

@ -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);
}

View File

@ -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
{

View File

@ -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<QString>::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();

View File

@ -274,7 +274,6 @@ const QVector<Tree*>& 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<int, QString> m;
foreach (Tree* t, searchOrder()) {
if (t->linkCount() < 0)
m.insert(t->linkCount(), t->moduleName());
}
QString depends = "depends +=";
QString module = project.toLower();
QMultiMap<int, QString>::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<pad; ++k)
line += " ";
line += "%1";
Location::null.report(line.arg(-(i.key())));
++i;
}
Location::null.report("Optimal depends variable:");
Location::null.report(depends);
}
/*!
Print the list of module names ordered according
to how many successful searches each tree had.
*/
QString QDocForest::getLinkCounts(QStringList& strings, QVector<int>& counts)
{
QMultiMap<int, QString> m;
foreach (Tree* t, searchOrder()) {
if (t->linkCount() < 0)
m.insert(t->linkCount(), t->moduleName());
}
QString depends = "depends +=";
QString module = Generator::defaultModuleName().toLower();
QMultiMap<int, QString>::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

View File

@ -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<int>& 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<int>& counts) {
return forest_.getLinkCounts(strings, counts);
}
private:
friend class QDocIndexFiles;

View File

@ -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())

View File

@ -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_;