diff options
Diffstat (limited to 'src/qdoc/generator.cpp')
-rw-r--r-- | src/qdoc/generator.cpp | 2231 |
1 files changed, 0 insertions, 2231 deletions
diff --git a/src/qdoc/generator.cpp b/src/qdoc/generator.cpp deleted file mode 100644 index 8e9a8698c..000000000 --- a/src/qdoc/generator.cpp +++ /dev/null @@ -1,2231 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2019 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** 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 https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -/* - generator.cpp -*/ -#include "generator.h" - -#include "access.h" -#include "aggregate.h" -#include "classnode.h" -#include "codemarker.h" -#include "collectionnode.h" -#include "config.h" -#include "doc.h" -#include "editdistance.h" -#include "enumnode.h" -#include "examplenode.h" -#include "functionnode.h" -#include "loggingcategory.h" -#include "node.h" -#include "openedlist.h" -#include "propertynode.h" -#include "qdocdatabase.h" -#include "qmltypenode.h" -#include "quoter.h" -#include "separator.h" -#include "sharedcommentnode.h" -#include "tokenizer.h" -#include "typedefnode.h" - -#include <QtCore/qdebug.h> -#include <QtCore/qdir.h> -#include <QtCore/qregularexpression.h> - -#ifndef QT_BOOTSTRAPPED -# include "QtCore/qurl.h" -#endif - -QT_BEGIN_NAMESPACE - -Generator *Generator::currentGenerator_; -QStringList Generator::exampleDirs; -QStringList Generator::exampleImgExts; -QMap<QString, QMap<QString, QString>> Generator::fmtLeftMaps; -QMap<QString, QMap<QString, QString>> Generator::fmtRightMaps; -QList<Generator *> Generator::generators; -QStringList Generator::imageDirs; -QStringList Generator::imageFiles; -QMap<QString, QStringList> Generator::imgFileExts; -QString Generator::outDir_; -QString Generator::outSubdir_; -QStringList Generator::outFileNames_; -QSet<QString> Generator::outputFormats; -QHash<QString, QString> Generator::outputPrefixes; -QHash<QString, QString> Generator::outputSuffixes; -QString Generator::project_; -bool Generator::noLinkErrors_ = false; -bool Generator::autolinkErrors_ = false; -bool Generator::redirectDocumentationToDevNull_ = false; -bool Generator::useOutputSubdirs_ = true; -QmlTypeNode *Generator::qmlTypeContext_ = nullptr; - -static QRegularExpression tag("</?@[^>]*>"); -static QLatin1String amp("&"); -static QLatin1String gt(">"); -static QLatin1String lt("<"); -static QLatin1String quot("""); - -/*! - Constructs the generator base class. Prepends the newly - constructed generator to the list of output generators. - Sets a pointer to the QDoc database singleton, which is - available to the generator subclasses. - */ -Generator::Generator() -{ - m_qdb = QDocDatabase::qdocDB(); - generators.prepend(this); -} - -/*! - Destroys the generator after removing it from the list of - output generators. - */ -Generator::~Generator() -{ - generators.removeAll(this); -} - -void Generator::appendFullName(Text &text, const Node *apparentNode, const Node *relative, - const Node *actualNode) -{ - if (actualNode == nullptr) - actualNode = apparentNode; - text << Atom(Atom::LinkNode, CodeMarker::stringForNode(actualNode)) - << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) - << Atom(Atom::String, apparentNode->plainFullName(relative)) - << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK); -} - -void Generator::appendFullName(Text &text, const Node *apparentNode, const QString &fullName, - const Node *actualNode) -{ - if (actualNode == nullptr) - actualNode = apparentNode; - text << Atom(Atom::LinkNode, CodeMarker::stringForNode(actualNode)) - << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << Atom(Atom::String, fullName) - << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK); -} - -void Generator::appendFullNames(Text &text, const NodeList &nodes, const Node *relative) -{ - int index = 0; - for (const auto &node : nodes) { - appendFullName(text, node, relative); - text << comma(index++, nodes.count()); - } -} - -/*! - Append the signature for the function named in \a node to - \a text, so that is is a link to the documentation for that - function. - */ -void Generator::appendSignature(Text &text, const Node *node) -{ - text << Atom(Atom::LinkNode, CodeMarker::stringForNode(node)) - << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) - << Atom(Atom::String, node->signature(false, true)) - << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK); -} - -/*! - Generate a bullet list of function signatures. The function - nodes are in \a nodes. It uses the \a relative node and the - \a marker for the generation. - */ -void Generator::signatureList(const NodeList &nodes, const Node *relative, CodeMarker *marker) -{ - Text text; - int count = 0; - text << Atom(Atom::ListLeft, QString("bullet")); - for (const auto &node : nodes) { - text << Atom(Atom::ListItemNumber, QString::number(++count)); - text << Atom(Atom::ListItemLeft, QString("bullet")); - appendSignature(text, node); - text << Atom(Atom::ListItemRight, QString("bullet")); - } - text << Atom(Atom::ListRight, QString("bullet")); - generateText(text, relative, marker); -} - -int Generator::appendSortedNames(Text &text, const ClassNode *cn, const QList<RelatedClass> &rc) -{ - QMap<QString, Text> classMap; - for (const auto &relatedClass : rc) { - ClassNode *rcn = relatedClass.m_node; - if (rcn && rcn->isInAPI()) { - Text className; - appendFullName(className, rcn, cn); - classMap[className.toString().toLower()] = className; - } - } - - int index = 0; - const QStringList classNames = classMap.keys(); - for (const auto &className : classNames) { - text << classMap[className]; - text << comma(index++, classNames.count()); - } - return index; -} - -int Generator::appendSortedQmlNames(Text &text, const Node *base, const NodeList &subs) -{ - QMap<QString, Text> classMap; - - for (const auto sub : subs) { - Text text; - if (!base->isQtQuickNode() || !sub->isQtQuickNode() - || (base->logicalModuleName() == sub->logicalModuleName())) { - appendFullName(text, sub, base); - classMap[text.toString().toLower()] = text; - } - } - - int index = 0; - const QStringList names = classMap.keys(); - for (const auto &name : names) { - text << classMap[name]; - text << comma(index++, names.count()); - } - return index; -} - -/*! - Creates the file named \a fileName in the output directory - and returns a QFile pointing to this file. In particular, - this method deals with errors when opening the file: - the returned QFile is always valid and can be written to. - - \sa beginFilePage() - */ -QFile *Generator::openSubPageFile(const Node *node, const QString &fileName) -{ - QString path = outputDir() + QLatin1Char('/'); - if (Generator::useOutputSubdirs() && !node->outputSubdirectory().isEmpty() - && !outputDir().endsWith(node->outputSubdirectory())) { - path += node->outputSubdirectory() + QLatin1Char('/'); - } - path += fileName; - - auto outPath = redirectDocumentationToDevNull_ ? QStringLiteral("/dev/null") : path; - auto outFile = new QFile(outPath); - if (!redirectDocumentationToDevNull_ && outFile->exists()) { - node->location().error(QStringLiteral("Output file already exists; overwriting %1") - .arg(outFile->fileName())); - } - if (!outFile->open(QFile::WriteOnly)) { - node->location().fatal( - QStringLiteral("Cannot open output file '%1'").arg(outFile->fileName())); - } - qCDebug(lcQdoc, "Writing: %s", qPrintable(path)); - outFileNames_ << fileName; - return outFile; -} - -/*! - Creates the file named \a fileName in the output directory. - Attaches a QTextStream to the created file, which is written - to all over the place using out(). This function does not - store the \a fileName in the \a node as the output file name. - - \sa beginSubPage() - */ -void Generator::beginFilePage(const Node *node, const QString &fileName) -{ - QFile *outFile = openSubPageFile(node, fileName); - QTextStream *out = new QTextStream(outFile); - outStreamStack.push(out); -} - -/*! - Creates the file named \a fileName in the output directory. - Attaches a QTextStream to the created file, which is written - to all over the place using out(). This function calls another - function, \c beginFilePage(), which is really just most of what - this function used to contain. We needed a different version - that doesn't store the \a fileName in the \a node as the output - file name. - - \sa beginFilePage() -*/ -void Generator::beginSubPage(const Node *node, const QString &fileName) -{ - beginFilePage(node, fileName); - const_cast<Node *>(node)->setOutputFileName(fileName); -} - -/*! - Flush the text stream associated with the subpage, and - then pop it off the text stream stack and delete it. - This terminates output of the subpage. - */ -void Generator::endSubPage() -{ - outStreamStack.top()->flush(); - delete outStreamStack.top()->device(); - delete outStreamStack.pop(); -} - -/* - the code below is effectively equivalent to: - input.replace(QRegularExpression("[^A-Za-z0-9]+"), " "); - input = input.trimmed(); - input.replace(QLatin1Char(' '), QLatin1Char('-')); - input = input.toLower(); - as this function accounted for ~8% of total running time - we optimize a bit... -*/ -static void transmogrify(QString &input, QString &output) -{ - // +5 prevents realloc in fileName() below - output.reserve(input.size() + 5); - bool begun = false; - for (int i = 0; i != input.size(); ++i) { - QChar c = input.at(i); - uint u = c.unicode(); - if (u >= 'A' && u <= 'Z') - u += 'a' - 'A'; - if ((u >= 'a' && u <= 'z') || (u >= '0' && u <= '9')) { - output += QLatin1Char(u); - begun = true; - } else if (begun) { - output += QLatin1Char('-'); - begun = false; - } - } - while (output.endsWith(QLatin1Char('-'))) - output.chop(1); -} - -QString Generator::fileBase(const Node *node) -{ - if (!node->isPageNode() && !node->isCollectionNode()) - node = node->parent(); - - if (node->hasFileNameBase()) - return node->fileNameBase(); - - QString base; - if (node->isCollectionNode()) { - base = node->name() + outputSuffix(node); - if (base.endsWith(".html")) - base.truncate(base.length() - 5); - - if (node->isQmlModule()) - base.append("-qmlmodule"); - else if (node->isJsModule()) - base.append("-jsmodule"); - else if (node->isModule()) - base.append("-module"); - // Why not add "-group" for group pages? - } else if (node->isTextPageNode()) { - base = node->name(); - if (base.endsWith(".html")) - base.truncate(base.length() - 5); - - if (node->isExample()) { - QString modPrefix(node->physicalModuleName()); - if (modPrefix.isEmpty()) { - modPrefix = project_; - } - base.prepend(modPrefix.toLower() + QLatin1Char('-')); - } - if (node->isExample()) { - base.append(QLatin1String("-example")); - } - } else if (node->isQmlType() || node->isQmlBasicType() || node->isJsType() - || node->isJsBasicType()) { - base = node->name(); - /* - To avoid file name conflicts in the html directory, - we prepend a prefix (by default, "qml-") and an optional suffix - to the file name. The suffix, if one exists, is appended to the - module name. - */ - if (!node->logicalModuleName().isEmpty() - && (!node->logicalModule()->isInternal() || Config::instance().showInternal())) - base.prepend(node->logicalModuleName() + outputSuffix(node) + QLatin1Char('-')); - - base.prepend(outputPrefix(node)); - } else if (node->isProxyNode()) { - base = node->name(); - base.append("-proxy"); - } else { - const Node *p = node; - forever { - const Node *pp = p->parent(); - base.prepend(p->name()); - if (pp == nullptr || pp->name().isEmpty() || pp->isTextPageNode()) - break; - base.prepend(QLatin1Char('-')); - p = pp; - } - if (node->isNamespace() && !node->name().isEmpty()) { - const NamespaceNode *ns = static_cast<const NamespaceNode *>(node); - if (!ns->isDocumentedHere()) { - base.append(QLatin1String("-sub-")); - base.append(ns->tree()->camelCaseModuleName()); - } - } - } - - QString res; - transmogrify(base, res); - Node *n = const_cast<Node *>(node); - n->setFileNameBase(res); - return res; -} - -/*! - Constructs an href link from an example file name, which - is a path to the example file. If \a fileExtension is - empty (default value), retrieve the file extension from - the generator. - */ -QString Generator::linkForExampleFile(const QString &path, const Node *parent, - const QString &fileExt) -{ - QString link = path; - QString modPrefix(parent->physicalModuleName()); - if (modPrefix.isEmpty()) - modPrefix = project_; - link.prepend(modPrefix.toLower() + QLatin1Char('-')); - - QString res; - transmogrify(link, res); - res.append(QLatin1Char('.')); - res.append(fileExt); - if (fileExt.isEmpty()) - res.append(fileExtension()); - return res; -} - -/*! - Helper function to construct a title for a file or image page - included in an example. -*/ -QString Generator::exampleFileTitle(const ExampleNode *relative, const QString &fileName) -{ - QString suffix; - if (relative->files().contains(fileName)) - suffix = QLatin1String(" Example File"); - else if (relative->images().contains(fileName)) - suffix = QLatin1String(" Image File"); - else - return suffix; - - return fileName.mid(fileName.lastIndexOf(QLatin1Char('/')) + 1) + suffix; -} - -/*! - If the \a node has a URL, return the URL as the file name. - Otherwise, construct the file name from the fileBase() and - either the provided \a extension or fileExtension(), and - return the constructed name. - */ -QString Generator::fileName(const Node *node, const QString &extension) const -{ - if (!node->url().isEmpty()) - return node->url(); - - QString name = fileBase(node) + QLatin1Char('.'); - return extension.isNull() ? name + fileExtension() : name + extension; -} - -QString Generator::cleanRef(const QString &ref) -{ - QString clean; - - if (ref.isEmpty()) - return clean; - - clean.reserve(ref.size() + 20); - const QChar c = ref[0]; - const uint u = c.unicode(); - - if ((u >= 'a' && u <= 'z') || (u >= 'A' && u <= 'Z') || (u >= '0' && u <= '9')) { - clean += c; - } else if (u == '~') { - clean += "dtor."; - } else if (u == '_') { - clean += "underscore."; - } else { - clean += QLatin1Char('A'); - } - - for (int i = 1; i < ref.length(); i++) { - const QChar c = ref[i]; - const uint u = c.unicode(); - if ((u >= 'a' && u <= 'z') || (u >= 'A' && u <= 'Z') || (u >= '0' && u <= '9') || u == '-' - || u == '_' || u == ':' || u == '.') { - clean += c; - } else if (c.isSpace()) { - clean += QLatin1Char('-'); - } else if (u == '!') { - clean += "-not"; - } else if (u == '&') { - clean += "-and"; - } else if (u == '<') { - clean += "-lt"; - } else if (u == '=') { - clean += "-eq"; - } else if (u == '>') { - clean += "-gt"; - } else if (u == '#') { - clean += QLatin1Char('#'); - } else { - clean += QLatin1Char('-'); - clean += QString::number(static_cast<int>(u), 16); - } - } - return clean; -} - -QMap<QString, QString> &Generator::formattingLeftMap() -{ - return fmtLeftMaps[format()]; -} - -QMap<QString, QString> &Generator::formattingRightMap() -{ - return fmtRightMaps[format()]; -} - -/*! - Returns the full document location. - */ -QString Generator::fullDocumentLocation(const Node *node, bool useSubdir) -{ - if (node == nullptr) - return QString(); - if (!node->url().isEmpty()) - return node->url(); - - QString parentName; - QString anchorRef; - QString fdl; - - /* - If the useSubdir parameter is set, then the output is - being sent to subdirectories of the output directory. - Prepend the subdirectory name + '/' to the result. - */ - if (useSubdir) { - fdl = node->outputSubdirectory(); - if (!fdl.isEmpty()) - fdl.append(QLatin1Char('/')); - } - if (node->isNamespace()) { - /* - The root namespace has no name - check for this before creating - an attribute containing the location of any documentation. - */ - if (!fileBase(node).isEmpty()) - parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension(); - else - return QString(); - } else if (node->isQmlType() || node->isQmlBasicType() || node->isJsType() - || node->isJsBasicType()) { - QString fb = fileBase(node); - if (fb.startsWith(outputPrefix(node))) - return fb + QLatin1Char('.') + currentGenerator()->fileExtension(); - else { - QString mq; - if (!node->logicalModuleName().isEmpty()) { - mq = node->logicalModuleName().replace(QChar('.'), QChar('-')); - mq = mq.toLower() + QLatin1Char('-'); - } - return fdl + outputPrefix(node) + mq + fileBase(node) + QLatin1Char('.') - + currentGenerator()->fileExtension(); - } - } else if (node->isTextPageNode() || node->isCollectionNode()) { - parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension(); - } else if (fileBase(node).isEmpty()) - return QString(); - - Node *parentNode = nullptr; - - if ((parentNode = node->parent())) { - // use the parent's name unless the parent is the root namespace - if (!node->parent()->isNamespace() || !node->parent()->name().isEmpty()) - parentName = fullDocumentLocation(node->parent()); - } - - switch (node->nodeType()) { - case Node::Class: - case Node::Struct: - case Node::Union: - case Node::Namespace: - case Node::Proxy: - parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension(); - break; - case Node::Function: { - const FunctionNode *fn = static_cast<const FunctionNode *>(node); - switch (fn->metaness()) { - case FunctionNode::JsSignal: - case FunctionNode::QmlSignal: - anchorRef = QLatin1Char('#') + node->name() + "-signal"; - break; - case FunctionNode::JsSignalHandler: - case FunctionNode::QmlSignalHandler: - anchorRef = QLatin1Char('#') + node->name() + "-signal-handler"; - break; - case FunctionNode::JsMethod: - case FunctionNode::QmlMethod: - anchorRef = QLatin1Char('#') + node->name() + "-method"; - break; - default: - if (fn->isDtor()) - anchorRef = "#dtor." + fn->name().mid(1); - else if (fn->hasOneAssociatedProperty() && fn->doc().isEmpty()) - return fullDocumentLocation(fn->firstAssociatedProperty()); - else if (fn->overloadNumber() > 0) - anchorRef = QLatin1Char('#') + cleanRef(fn->name()) + QLatin1Char('-') - + QString::number(fn->overloadNumber()); - else - anchorRef = QLatin1Char('#') + cleanRef(fn->name()); - break; - } - break; - } - /* - Use node->name() instead of fileBase(node) as - the latter returns the name in lower-case. For - HTML anchors, we need to preserve the case. - */ - case Node::Enum: - anchorRef = QLatin1Char('#') + node->name() + "-enum"; - break; - case Node::TypeAlias: - anchorRef = QLatin1Char('#') + node->name() + "-alias"; - break; - case Node::Typedef: { - const TypedefNode *tdef = static_cast<const TypedefNode *>(node); - if (tdef->associatedEnum()) { - return fullDocumentLocation(tdef->associatedEnum()); - } - anchorRef = QLatin1Char('#') + node->name() + "-typedef"; - break; - } - case Node::Property: - anchorRef = QLatin1Char('#') + node->name() + "-prop"; - break; - case Node::JsProperty: - case Node::QmlProperty: - if (node->isAttached()) - anchorRef = QLatin1Char('#') + node->name() + "-attached-prop"; - else - anchorRef = QLatin1Char('#') + node->name() + "-prop"; - break; - case Node::Variable: - anchorRef = QLatin1Char('#') + node->name() + "-var"; - break; - case Node::JsType: - case Node::QmlType: - case Node::Page: - case Node::Group: - case Node::HeaderFile: - case Node::Module: - case Node::JsModule: - case Node::QmlModule: { - parentName = fileBase(node); - parentName.replace(QLatin1Char('/'), QLatin1Char('-')) - .replace(QLatin1Char('.'), QLatin1Char('-')); - parentName += QLatin1Char('.') + currentGenerator()->fileExtension(); - } break; - default: - break; - } - - if (!node->isClassNode() && !node->isNamespace()) { - if (node->isObsolete()) - parentName.replace(QLatin1Char('.') + currentGenerator()->fileExtension(), - "-obsolete." + currentGenerator()->fileExtension()); - } - - return fdl + parentName.toLower() + anchorRef; -} - -void Generator::generateAlsoList(const Node *node, CodeMarker *marker) -{ - QList<Text> alsoList = node->doc().alsoList(); - supplementAlsoList(node, alsoList); - - if (!alsoList.isEmpty()) { - Text text; - text << Atom::ParaLeft << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD) << "See also " - << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD); - - for (int i = 0; i < alsoList.size(); ++i) - text << alsoList.at(i) << separator(i, alsoList.size()); - - text << Atom::ParaRight; - generateText(text, node, marker); - } -} - -const Atom *Generator::generateAtomList(const Atom *atom, const Node *relative, CodeMarker *marker, - bool generate, int &numAtoms) -{ - while (atom != nullptr) { - if (atom->type() == Atom::FormatIf) { - int numAtoms0 = numAtoms; - bool rightFormat = canHandleFormat(atom->string()); - atom = generateAtomList(atom->next(), relative, marker, generate && rightFormat, - numAtoms); - if (atom == nullptr) - return nullptr; - - if (atom->type() == Atom::FormatElse) { - ++numAtoms; - atom = generateAtomList(atom->next(), relative, marker, generate && !rightFormat, - numAtoms); - if (atom == nullptr) - return nullptr; - } - - if (atom->type() == Atom::FormatEndif) { - if (generate && numAtoms0 == numAtoms) { - relative->location().warning(QStringLiteral("Output format %1 not handled %2") - .arg(format()) - .arg(outFileName())); - Atom unhandledFormatAtom(Atom::UnhandledFormat, format()); - generateAtomList(&unhandledFormatAtom, relative, marker, generate, numAtoms); - } - atom = atom->next(); - } - } else if (atom->type() == Atom::FormatElse || atom->type() == Atom::FormatEndif) { - return atom; - } else { - int n = 1; - if (generate) { - n += generateAtom(atom, relative, marker); - numAtoms += n; - } - while (n-- > 0) - atom = atom->next(); - } - } - return nullptr; -} - -/*! - Generate the body of the documentation from the qdoc comment - found with the entity represented by the \a node. - */ -void Generator::generateBody(const Node *node, CodeMarker *marker) -{ - const FunctionNode *fn = node->isFunction() ? static_cast<const FunctionNode *>(node) : nullptr; - if (!node->hasDoc() && !node->hasSharedDoc()) { - /* - Test for special function, like a destructor or copy constructor, - that has no documentation. - */ - if (fn) { - if (fn->isDtor()) { - Text text; - text << "Destroys the instance of "; - text << fn->parent()->name() << "."; - if (fn->isVirtual()) - text << " The destructor is virtual."; - out() << "<p>"; - generateText(text, node, marker); - out() << "</p>"; - } else if (fn->isCtor()) { - Text text; - text << "Default constructs an instance of "; - text << fn->parent()->name() << "."; - out() << "<p>"; - generateText(text, node, marker); - out() << "</p>"; - } else if (fn->isCCtor()) { - Text text; - text << "Copy constructor."; - out() << "<p>"; - generateText(text, node, marker); - out() << "</p>"; - } else if (fn->isMCtor()) { - Text text; - text << "Move-copy constructor."; - out() << "<p>"; - generateText(text, node, marker); - out() << "</p>"; - } else if (fn->isCAssign()) { - Text text; - text << "Copy-assignment operator."; - out() << "<p>"; - generateText(text, node, marker); - out() << "</p>"; - } else if (fn->isMAssign()) { - Text text; - text << "Move-assignment operator."; - out() << "<p>"; - generateText(text, node, marker); - out() << "</p>"; - } else if (!node->isWrapper() && !node->isMarkedReimp()) { - if (!fn->isIgnored()) // undocumented functions added by Q_OBJECT - node->location().warning(QStringLiteral("No documentation for '%1'") - .arg(node->plainSignature())); - } - } else if (!node->isWrapper() && !node->isMarkedReimp()) { - // Don't require documentation of things defined in Q_GADGET - if (node->name() != QLatin1String("QtGadgetHelper")) - node->location().warning( - QStringLiteral("No documentation for '%1'").arg(node->plainSignature())); - } - } else if (!node->isSharingComment()) { - // Reimplements clause and type alias info precede body text - if (fn && !fn->overridesThis().isEmpty()) - generateReimplementsClause(fn, marker); - else if (node->isTypeAlias()) - generateAddendum(node, TypeAlias, marker, false); - else if (node->isProperty()) { - if (static_cast<const PropertyNode *>(node)->propertyType() != PropertyNode::Standard) - generateAddendum(node, BindableProperty, marker); - } - - if (!generateText(node->doc().body(), node, marker)) { - if (node->isMarkedReimp()) - return; - } - - if (fn) { - if (fn->isQmlSignal()) - generateAddendum(node, QmlSignalHandler, marker); - if (fn->isPrivateSignal()) - generateAddendum(node, PrivateSignal, marker); - if (fn->isInvokable()) - generateAddendum(node, Invokable, marker); - if (fn->hasAssociatedProperties()) - generateAddendum(node, AssociatedProperties, marker); - } - - // Generate warnings - if (node->isEnumType()) { - const EnumNode *enume = static_cast<const EnumNode *>(node); - - QSet<QString> definedItems; - const QList<EnumItem> &items = enume->items(); - for (const auto &item : items) - definedItems.insert(item.name()); - - const auto &documentedItemList = enume->doc().enumItemNames(); - QSet<QString> documentedItems(documentedItemList.cbegin(), documentedItemList.cend()); - const QSet<QString> allItems = definedItems + documentedItems; - if (allItems.count() > definedItems.count() - || allItems.count() > documentedItems.count()) { - for (const auto &it : allItems) { - if (!definedItems.contains(it)) { - QString details; - QString best = nearestName(it, definedItems); - if (!best.isEmpty() && !documentedItems.contains(best)) - details = QStringLiteral("Maybe you meant '%1'?").arg(best); - - node->doc().location().warning( - QStringLiteral("No such enum item '%1' in %2") - .arg(it) - .arg(node->plainFullName()), - details); - } else if (!documentedItems.contains(it)) { - node->doc().location().warning( - QStringLiteral("Undocumented enum item '%1' in %2") - .arg(it) - .arg(node->plainFullName())); - } - } - } - } else if (fn) { - const QSet<QString> declaredNames = fn->parameters().getNames(); - const QSet<QString> documentedNames = fn->doc().parameterNames(); - if (declaredNames != documentedNames) { - for (const auto &name : declaredNames) { - if (!documentedNames.contains(name)) { - if (fn->isActive() || fn->isPreliminary()) { - if (!fn->isMarkedReimp() && !fn->isOverload()) { - fn->doc().location().warning( - QStringLiteral("Undocumented parameter '%1' in %2") - .arg(name) - .arg(node->plainFullName())); - } - } - } - } - for (const auto &name : documentedNames) { - if (!declaredNames.contains(name)) { - QString best = nearestName(name, declaredNames); - QString details; - if (!best.isEmpty()) - details = QStringLiteral("Maybe you meant '%1'?").arg(best); - fn->doc().location().warning(QStringLiteral("No such parameter '%1' in %2") - .arg(name) - .arg(fn->plainFullName()), - details); - } - } - } - /* - This return value check should be implemented - for all functions with a return type. - mws 13/12/2018 - */ - if (!fn->isObsolete() && fn->returnsBool() && !fn->isMarkedReimp() - && !fn->isOverload()) { - if (!fn->doc().body().contains("return")) - node->doc().location().warning( - QStringLiteral("Undocumented return value " - "(hint: use 'return' or 'returns' in the text")); - } - } - } - generateRequiredLinks(node, marker); -} - -/*! - Generates either a link to the project folder for example \a node, or a list - of links files/images if 'url.examples config' variable is not defined. - - Does nothing for non-example nodes. -*/ -void Generator::generateRequiredLinks(const Node *node, CodeMarker *marker) -{ - if (!node->isExample()) - return; - - const ExampleNode *en = static_cast<const ExampleNode *>(node); - QString exampleUrl = Config::instance().getString(CONFIG_URL + Config::dot + CONFIG_EXAMPLES); - - if (exampleUrl.isEmpty()) { - if (!en->noAutoList()) { - generateFileList(en, marker, false); // files - generateFileList(en, marker, true); // images - } - } else { - generateLinkToExample(en, marker, exampleUrl); - } -} - -/*! - Generates an external link to the project folder for example \a node. - The path to the example replaces a placeholder '\1' character if - one is found in the \a baseUrl string. If no such placeholder is found, - the path is appended to \a baseUrl, after a '/' character if \a baseUrl did - not already end in one. -*/ -void Generator::generateLinkToExample(const ExampleNode *en, CodeMarker *marker, - const QString &baseUrl) -{ - QString exampleUrl(baseUrl); - QString link; -#ifndef QT_BOOTSTRAPPED - link = QUrl(exampleUrl).host(); -#endif - if (!link.isEmpty()) - link.prepend(" @ "); - link.prepend("Example project"); - - const QLatin1Char separator('/'); - const QLatin1Char placeholder('\1'); - if (!exampleUrl.contains(placeholder)) { - if (!exampleUrl.endsWith(separator)) - exampleUrl += separator; - exampleUrl += placeholder; - } - - // Construct a path to the example; <install path>/<example name> - QString pathRoot; - QStringMultiMap *metaTagMap = en->doc().metaTagMap(); - if (metaTagMap) - pathRoot = metaTagMap->value(QLatin1String("installpath")); - if (pathRoot.isEmpty()) - pathRoot = Config::instance().getString(CONFIG_EXAMPLESINSTALLPATH); - QStringList path = QStringList() << pathRoot << en->name(); - path.removeAll({}); - - Text text; - text << Atom::ParaLeft - << Atom(Atom::Link, exampleUrl.replace(placeholder, path.join(separator))) - << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << Atom(Atom::String, link) - << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << Atom::ParaRight; - - generateText(text, nullptr, marker); -} - -void Generator::addImageToCopy(const ExampleNode *en, const QString &file) -{ - QDir dirInfo; - QString userFriendlyFilePath; - const QString prefix("/images/used-in-examples/"); - QString srcPath = Config::findFile(en->location(), QStringList(), exampleDirs, file, - exampleImgExts, &userFriendlyFilePath); - outFileNames_ << prefix.mid(1) + userFriendlyFilePath; - userFriendlyFilePath.truncate(userFriendlyFilePath.lastIndexOf('/')); - QString imgOutDir = outDir_ + prefix + userFriendlyFilePath; - if (!dirInfo.mkpath(imgOutDir)) - en->location().fatal(QStringLiteral("Cannot create output directory '%1'").arg(imgOutDir)); - Config::copyFile(en->location(), srcPath, file, imgOutDir); -} - -/*! - This function is called when the documentation for an example is - being formatted. It outputs a list of files for the example, which - can be the example's source files or the list of images used by the - example. The images are copied into a subtree of - \c{...doc/html/images/used-in-examples/...} -*/ -void Generator::generateFileList(const ExampleNode *en, CodeMarker *marker, bool images) -{ - Text text; - OpenedList openedList(OpenedList::Bullet); - QString tag; - QStringList paths; - Atom::AtomType atomType = Atom::ExampleFileLink; - - if (images) { - paths = en->images(); - tag = "Images:"; - atomType = Atom::ExampleImageLink; - } else { // files - paths = en->files(); - tag = "Files:"; - } - std::sort(paths.begin(), paths.end(), Generator::comparePaths); - - text << Atom::ParaLeft << tag << Atom::ParaRight; - text << Atom(Atom::ListLeft, openedList.styleString()); - - QString path; - for (const auto &file : qAsConst(paths)) { - if (images) { - if (!file.isEmpty()) - addImageToCopy(en, file); - } else { - generateExampleFilePage(en, file, marker); - } - - openedList.next(); - text << Atom(Atom::ListItemNumber, openedList.numberString()) - << Atom(Atom::ListItemLeft, openedList.styleString()) << Atom::ParaLeft - << Atom(atomType, file) << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << file - << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << Atom::ParaRight - << Atom(Atom::ListItemRight, openedList.styleString()); - path = file; - } - text << Atom(Atom::ListRight, openedList.styleString()); - if (!paths.isEmpty()) - generateText(text, en, marker); -} - -void Generator::generateInherits(const ClassNode *classe, CodeMarker *marker) -{ - if (!classe->baseClasses().isEmpty()) { - Text text; - text << Atom::ParaLeft << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD) - << "Inherits: " << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD); - - int index = 0; - const QList<RelatedClass> &baseClasses = classe->baseClasses(); - for (const auto &cls : baseClasses) { - if (cls.m_node) { - appendFullName(text, cls.m_node, classe); - - if (cls.m_access == Access::Protected) { - text << " (protected)"; - } else if (cls.m_access == Access::Private) { - text << " (private)"; - } - text << separator(index++, classe->baseClasses().count()); - } - } - text << Atom::ParaRight; - generateText(text, classe, marker); - } -} - -/*! - Recursive writing of HTML files from the root \a node. - */ -void Generator::generateDocumentation(Node *node) -{ - if (!node->url().isNull()) - return; - if (node->isIndexNode()) - return; - if (node->isInternal() && !m_showInternal) - return; - if (node->isExternalPage()) - return; - - /* - Obtain a code marker for the source file. - */ - CodeMarker *marker = CodeMarker::markerForFileName(node->location().filePath()); - - if (node->parent() != nullptr) { - if (node->isCollectionNode()) { - /* - A collection node collects: groups, C++ modules, - QML modules or JavaScript modules. Testing for a - CollectionNode must be done before testing for a - TextPageNode because a CollectionNode is a PageNode - at this point. - - Don't output an HTML page for the collection - node unless the \group, \module, \qmlmodule or - \jsmodule command was actually seen by qdoc in - the qdoc comment for the node. - - A key prerequisite in this case is the call to - mergeCollections(cn). We must determine whether - this group, module, QML module, or JavaScript - module has members in other modules. We know at - this point that cn's members list contains only - members in the current module. Therefore, before - outputting the page for cn, we must search for - members of cn in the other modules and add them - to the members list. - */ - CollectionNode *cn = static_cast<CollectionNode *>(node); - if (cn->wasSeen()) { - m_qdb->mergeCollections(cn); - beginSubPage(node, fileName(node)); - generateCollectionNode(cn, marker); - endSubPage(); - } else if (cn->isGenericCollection()) { - // Currently used only for the module's related orphans page - // but can be generalized for other kinds of collections if - // other use cases pop up. - QString name = cn->name().toLower(); - name.replace(QChar(' '), QString("-")); - QString filename = - cn->tree()->physicalModuleName() + "-" + name + "." + fileExtension(); - beginSubPage(node, filename); - generateGenericCollectionPage(cn, marker); - endSubPage(); - } - } else if (node->isTextPageNode()) { - beginSubPage(node, fileName(node)); - generatePageNode(static_cast<PageNode *>(node), marker); - endSubPage(); - } else if (node->isAggregate()) { - if ((node->isClassNode() || node->isHeader() || node->isNamespace()) - && node->docMustBeGenerated()) { - beginSubPage(node, fileName(node)); - generateCppReferencePage(static_cast<Aggregate *>(node), marker); - endSubPage(); - } else if (node->isQmlType() || node->isJsType()) { - beginSubPage(node, fileName(node)); - QmlTypeNode *qcn = static_cast<QmlTypeNode *>(node); - generateQmlTypePage(qcn, marker); - endSubPage(); - } else if (node->isQmlBasicType() || node->isJsBasicType()) { - beginSubPage(node, fileName(node)); - QmlBasicTypeNode *qbtn = static_cast<QmlBasicTypeNode *>(node); - generateQmlBasicTypePage(qbtn, marker); - endSubPage(); - } else if (node->isProxyNode()) { - beginSubPage(node, fileName(node)); - generateProxyPage(static_cast<Aggregate *>(node), marker); - endSubPage(); - } - } - } - - if (node->isAggregate()) { - Aggregate *aggregate = static_cast<Aggregate *>(node); - const NodeList &children = aggregate->childNodes(); - for (auto *node : children) { - if (node->isPageNode() && !node->isPrivate()) - generateDocumentation(node); - } - } -} - -/*! - Generate a list of maintainers in the output - */ -void Generator::generateMaintainerList(const Aggregate *node, CodeMarker *marker) -{ - QStringList sl = getMetadataElements(node, "maintainer"); - - if (!sl.isEmpty()) { - Text text; - text << Atom::ParaLeft << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD) - << "Maintained by: " << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD); - - for (int i = 0; i < sl.size(); ++i) - text << sl.at(i) << separator(i, sl.size()); - - text << Atom::ParaRight; - generateText(text, node, marker); - } -} - -/*! - Output the "Inherit by" list for the QML element, - if it is inherited by any other elements. - */ -void Generator::generateQmlInheritedBy(const QmlTypeNode *qcn, CodeMarker *marker) -{ - if (qcn) { - NodeList subs; - QmlTypeNode::subclasses(qcn, subs); - if (!subs.isEmpty()) { - Text text; - text << Atom::ParaLeft << "Inherited by "; - appendSortedQmlNames(text, qcn, subs); - text << Atom::ParaRight; - generateText(text, qcn, marker); - } - } -} - -/*! - Extract sections of markup text surrounded by \e qmltext - and \e endqmltext and output them. - */ -bool Generator::generateQmlText(const Text &text, const Node *relative, CodeMarker *marker, - const QString & /* qmlName */) -{ - const Atom *atom = text.firstAtom(); - bool result = false; - - if (atom != nullptr) { - initializeTextOutput(); - while (atom) { - if (atom->type() != Atom::QmlText) - atom = atom->next(); - else { - atom = atom->next(); - while (atom && (atom->type() != Atom::EndQmlText)) { - int n = 1 + generateAtom(atom, relative, marker); - while (n-- > 0) - atom = atom->next(); - } - } - } - result = true; - } - return result; -} - -void Generator::generateReimplementsClause(const FunctionNode *fn, CodeMarker *marker) -{ - if (!fn->overridesThis().isEmpty()) { - if (fn->parent()->isClassNode()) { - ClassNode *cn = static_cast<ClassNode *>(fn->parent()); - const FunctionNode *overrides = cn->findOverriddenFunction(fn); - if (overrides && !overrides->isPrivate() && !overrides->parent()->isPrivate()) { - if (overrides->hasDoc()) { - Text text; - text << Atom::ParaLeft << "Reimplements: "; - QString fullName = - overrides->parent()->name() + "::" + overrides->signature(false, true); - appendFullName(text, overrides->parent(), fullName, overrides); - text << "." << Atom::ParaRight; - generateText(text, fn, marker); - } else { - fn->doc().location().warning( - QStringLiteral("Illegal \\reimp; no documented virtual function for %1") - .arg(overrides->plainSignature())); - } - return; - } - const PropertyNode *sameName = cn->findOverriddenProperty(fn); - if (sameName && sameName->hasDoc()) { - Text text; - text << Atom::ParaLeft << "Reimplements an access function for property: "; - QString fullName = sameName->parent()->name() + "::" + sameName->name(); - appendFullName(text, sameName->parent(), fullName, sameName); - text << "." << Atom::ParaRight; - generateText(text, fn, marker); - } - } - } -} - -QString Generator::formatSince(const Node *node) -{ - QStringList since = node->since().split(QLatin1Char(' ')); - - // If there is only one argument, assume it is the Qt version number. - if (since.count() == 1) - return "Qt " + since[0]; - - // Otherwise, use the original <project> <version> string. - return node->since(); -} - -void Generator::generateSince(const Node *node, CodeMarker *marker) -{ - if (!node->since().isEmpty()) { - Text text; - text << Atom::ParaLeft << "This " << typeString(node) << " was introduced "; - if (node->isEnumType()) - text << "or modified "; - text << "in " << formatSince(node) << "." << Atom::ParaRight; - generateText(text, node, marker); - } -} - -void Generator::generateStatus(const Node *node, CodeMarker *marker) -{ - Text text; - - switch (node->status()) { - case Node::Active: - // Do nothing. - break; - case Node::Preliminary: - text << Atom::ParaLeft << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD) << "This " - << typeString(node) << " is under development and is subject to change." - << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD) << Atom::ParaRight; - break; - case Node::Deprecated: - text << Atom::ParaLeft; - if (node->isAggregate()) - text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD); - text << "This " << typeString(node) << " is deprecated."; - if (node->isAggregate()) - text << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD); - text << Atom::ParaRight; - break; - case Node::Obsolete: - text << Atom::ParaLeft; - if (node->isAggregate()) - text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD); - text << "This " << typeString(node) << " is obsolete."; - if (node->isAggregate()) - text << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD); - text << " It is provided to keep old source code working. " - << "We strongly advise against " - << "using it in new code." << Atom::ParaRight; - break; - case Node::Internal: - default: - break; - } - generateText(text, node, marker); -} - -/*! - Generates an addendum note of type \a type for \a node, using \a marker - as the code marker. -*/ -void Generator::generateAddendum(const Node *node, Addendum type, CodeMarker *marker, - bool generateNote) -{ - Q_ASSERT(node && !node->name().isEmpty()); - Text text; - text << Atom::ParaLeft; - - if (generateNote) { - text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD) - << "Note: " << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD); - } - - switch (type) { - case Invokable: - text << "This function can be invoked via the meta-object system and from QML. See " - << Atom(Atom::Link, "Q_INVOKABLE") - << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << "Q_INVOKABLE" - << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << "."; - break; - case PrivateSignal: - text << "This is a private signal. It can be used in signal connections " - "but cannot be emitted by the user."; - break; - case QmlSignalHandler: - { - QString handler(node->name()); - handler[0] = handler[0].toTitleCase(); - handler.prepend(QLatin1String("on")); - text << "The corresponding handler is " - << Atom(Atom::FormattingLeft, ATOM_FORMATTING_TELETYPE) << handler - << Atom(Atom::FormattingRight, ATOM_FORMATTING_TELETYPE) << "."; - break; - } - case AssociatedProperties: - { - if (!node->isFunction()) - return; - const FunctionNode *fn = static_cast<const FunctionNode *>(node); - NodeList nodes = fn->associatedProperties(); - if (nodes.isEmpty()) - return; - std::sort(nodes.begin(), nodes.end(), Node::nodeNameLessThan); - for (const auto *n : qAsConst(nodes)) { - QString msg; - const PropertyNode *pn = static_cast<const PropertyNode *>(n); - switch (pn->role(fn)) { - case PropertyNode::Getter: - msg = QStringLiteral("Getter function"); - break; - case PropertyNode::Setter: - msg = QStringLiteral("Setter function"); - break; - case PropertyNode::Resetter: - msg = QStringLiteral("Resetter function"); - break; - case PropertyNode::Notifier: - msg = QStringLiteral("Notifier signal"); - break; - default: - continue; - } - text << msg << " for property " << Atom(Atom::Link, pn->name()) - << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << pn->name() - << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << ". "; - } - break; - } - case TypeAlias: - { - if (!node->isTypeAlias()) - return; - const auto *ta = static_cast<const TypeAliasNode *>(node); - text << "This is a type alias for "; - if (ta->aliasedNode() && ta->aliasedNode()->isInAPI()) { - text << Atom(Atom::LinkNode, CodeMarker::stringForNode(ta->aliasedNode())) - << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) - << Atom(Atom::String, ta->aliasedNode()->plainFullName(ta->parent())) - << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << "."; - } else { - text << Atom(Atom::String, ta->aliasedType()) << "."; - } - break; - } - case BindableProperty: - { - text << "This property supports " - << Atom(Atom::Link, "QProperty") - << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << "QProperty" - << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK); - text << " bindings."; - break; - } - default: - return; - } - - text << Atom::ParaRight; - generateText(text, node, marker); -} - -/*! - Generate the documentation for \a relative. i.e. \a relative - is the node that represents the entity where a qdoc comment - was found, and \a text represents the qdoc comment. - */ -bool Generator::generateText(const Text &text, const Node *relative, CodeMarker *marker) -{ - bool result = false; - if (text.firstAtom() != nullptr) { - int numAtoms = 0; - initializeTextOutput(); - generateAtomList(text.firstAtom(), relative, marker, true, numAtoms); - result = true; - } - return result; -} - -/* - The node is an aggregate, typically a class node, which has - a threadsafeness level. This function checks all the children - of the node to see if they are exceptions to the node's - threadsafeness. If there are any exceptions, the exceptions - are added to the appropriate set (reentrant, threadsafe, and - nonreentrant, and true is returned. If there are no exceptions, - the three node lists remain empty and false is returned. - */ -bool Generator::hasExceptions(const Node *node, NodeList &reentrant, NodeList &threadsafe, - NodeList &nonreentrant) -{ - bool result = false; - Node::ThreadSafeness ts = node->threadSafeness(); - const NodeList &children = static_cast<const Aggregate *>(node)->childNodes(); - for (auto child : children) { - if (!child->isObsolete()) { - switch (child->threadSafeness()) { - case Node::Reentrant: - reentrant.append(child); - if (ts == Node::ThreadSafe) - result = true; - break; - case Node::ThreadSafe: - threadsafe.append(child); - if (ts == Node::Reentrant) - result = true; - break; - case Node::NonReentrant: - nonreentrant.append(child); - result = true; - break; - default: - break; - } - } - } - return result; -} - -static void startNote(Text &text) -{ - text << Atom::ParaLeft << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD) - << "Note:" << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD) << " "; -} - -/*! - Generates text that explains how threadsafe and/or reentrant - \a node is. - */ -void Generator::generateThreadSafeness(const Node *node, CodeMarker *marker) -{ - Text text, rlink, tlink; - NodeList reentrant; - NodeList threadsafe; - NodeList nonreentrant; - Node::ThreadSafeness ts = node->threadSafeness(); - bool exceptions = false; - - rlink << Atom(Atom::Link, "reentrant") << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) - << "reentrant" << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK); - - tlink << Atom(Atom::Link, "thread-safe") << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) - << "thread-safe" << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK); - - switch (ts) { - case Node::UnspecifiedSafeness: - break; - case Node::NonReentrant: - text << Atom::ParaLeft << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD) - << "Warning:" << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD) << " This " - << typeString(node) << " is not " << rlink << "." << Atom::ParaRight; - break; - case Node::Reentrant: - case Node::ThreadSafe: - startNote(text); - if (node->isAggregate()) { - exceptions = hasExceptions(node, reentrant, threadsafe, nonreentrant); - text << "All functions in this " << typeString(node) << " are "; - if (ts == Node::ThreadSafe) - text << tlink; - else - text << rlink; - - if (!exceptions || (ts == Node::Reentrant && !threadsafe.isEmpty())) - text << "."; - else - text << " with the following exceptions:"; - } else { - text << "This " << typeString(node) << " is "; - if (ts == Node::ThreadSafe) - text << tlink; - else - text << rlink; - text << "."; - } - text << Atom::ParaRight; - break; - default: - break; - } - generateText(text, node, marker); - - if (exceptions) { - text.clear(); - if (ts == Node::Reentrant) { - if (!nonreentrant.isEmpty()) { - startNote(text); - text << "These functions are not " << rlink << ":" << Atom::ParaRight; - signatureList(nonreentrant, node, marker); - } - if (!threadsafe.isEmpty()) { - text.clear(); - startNote(text); - text << "These functions are also " << tlink << ":" << Atom::ParaRight; - generateText(text, node, marker); - signatureList(threadsafe, node, marker); - } - } else { // thread-safe - if (!reentrant.isEmpty()) { - startNote(text); - text << "These functions are only " << rlink << ":" << Atom::ParaRight; - signatureList(reentrant, node, marker); - } - if (!nonreentrant.isEmpty()) { - text.clear(); - startNote(text); - text << "These functions are not " << rlink << ":" << Atom::ParaRight; - signatureList(nonreentrant, node, marker); - } - } - } -} - -/*! - Returns the string containing an example code of the input node, - if it is an overloaded signal. Otherwise, returns an empty string. - */ -QString Generator::getOverloadedSignalCode(const Node *node) -{ - if (!node->isFunction()) - return QString(); - const auto func = static_cast<const FunctionNode *>(node); - if (!func->isSignal() || !func->hasOverloads()) - return QString(); - - // Compute a friendly name for the object of that instance. - // e.g: "QAbstractSocket" -> "abstractSocket" - QString objectName = node->parent()->name(); - if (objectName.size() >= 2) { - if (objectName[0] == 'Q') - objectName = objectName.mid(1); - objectName[0] = objectName[0].toLower(); - } - - // We have an overloaded signal, show an example. Note, for const - // overloaded signals, one should use Q{Const,NonConst}Overload, but - // it is very unlikely that we will ever have public API overloading - // signals by const. - QString code = "connect(" + objectName + ", QOverload<"; - code += func->parameters().generateTypeList(); - code += ">::of(&" + func->parent()->name() + "::" + func->name() + "),\n [=]("; - code += func->parameters().generateTypeAndNameList(); - code += "){ /* ... */ });"; - - return code; -} - -/*! - If the node is an overloaded signal, add a node with an example on how to connect to it - */ -void Generator::generateOverloadedSignal(const Node *node, CodeMarker *marker) -{ - QString code = getOverloadedSignalCode(node); - if (code.isEmpty()) - return; - - Text text; - text << Atom::ParaLeft << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD) - << "Note:" << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD) << " Signal " - << Atom(Atom::FormattingLeft, ATOM_FORMATTING_ITALIC) << node->name() - << Atom(Atom::FormattingRight, ATOM_FORMATTING_ITALIC) - << " is overloaded in this class. " - "To connect to this signal by using the function pointer syntax, Qt " - "provides a convenient helper for obtaining the function pointer as " - "shown in this example:" - << Atom(Atom::Code, marker->markedUpCode(code, node, node->location())); - - generateText(text, node, marker); -} - -/*! - Traverses the database recursively to generate all the documentation. - */ -void Generator::generateDocs() -{ - currentGenerator_ = this; - generateDocumentation(m_qdb->primaryTreeRoot()); -} - -Generator *Generator::generatorForFormat(const QString &format) -{ - for (const auto &generator : qAsConst(generators)) { - if (generator->format() == format) - return generator; - } - return nullptr; -} - -/*! - Looks up the tag \a t in the map of metadata values for the - current topic in \a inner. If values for the tag are found, - they are returned in a string list. - - \note If \a t is found in the metadata map, all the pairs - having the key \a t are erased. i.e. Once you call this - function for a particular \a t, you consume \a t. - */ -QStringList Generator::getMetadataElements(const Aggregate *inner, const QString &t) -{ - QStringList result; - QStringMultiMap *metaTagMap = inner->doc().metaTagMap(); - if (metaTagMap) - result = metaTagMap->values(t); - if (!result.isEmpty()) - metaTagMap->remove(t); - return result; -} - -/*! - Returns a relative path name for an image. - */ -QString Generator::imageFileName(const Node *relative, const QString &fileBase) -{ - QString userFriendlyFilePath; - QString filePath = Config::findFile(relative->doc().location(), imageFiles, imageDirs, fileBase, - imgFileExts[format()], &userFriendlyFilePath); - - if (filePath.isEmpty()) - return QString(); - - QString path = Config::copyFile(relative->doc().location(), filePath, userFriendlyFilePath, - outputDir() + QLatin1String("/images")); - int images_slash = path.lastIndexOf("images/"); - QString relImagePath; - if (images_slash != -1) - relImagePath = path.mid(images_slash); - return relImagePath; -} - -QString Generator::indent(int level, const QString &markedCode) -{ - if (level == 0) - return markedCode; - - QString t; - int column = 0; - - int i = 0; - while (i < markedCode.length()) { - if (markedCode.at(i) == QLatin1Char('\n')) { - column = 0; - } else { - if (column == 0) { - for (int j = 0; j < level; j++) - t += QLatin1Char(' '); - } - column++; - } - t += markedCode.at(i++); - } - return t; -} - -void Generator::initialize() -{ - Config &config = Config::instance(); - outputFormats = config.getOutputFormats(); - redirectDocumentationToDevNull_ = config.getBool(CONFIG_REDIRECTDOCUMENTATIONTODEVNULL); - - imageFiles = config.getCanonicalPathList(CONFIG_IMAGES); - imageDirs = config.getCanonicalPathList(CONFIG_IMAGEDIRS); - exampleDirs = config.getCanonicalPathList(CONFIG_EXAMPLEDIRS); - exampleImgExts = config.getStringList(CONFIG_EXAMPLES + Config::dot + CONFIG_IMAGEEXTENSIONS); - - QString imagesDotFileExtensions = CONFIG_IMAGES + Config::dot + CONFIG_FILEEXTENSIONS; - for (const auto &ext : config.subVars(imagesDotFileExtensions)) - imgFileExts[ext] = config.getStringList(imagesDotFileExtensions + Config::dot + ext); - - for (auto &g : generators) { - if (outputFormats.contains(g->format())) { - currentGenerator_ = g; - g->initializeGenerator(); - } - } - - for (const auto &n : config.subVars(CONFIG_FORMATTING)) { - QString formattingDotName = CONFIG_FORMATTING + Config::dot + n; - for (const auto &f : config.subVars(formattingDotName)) { - QString def = config.getString(formattingDotName + Config::dot + f); - if (!def.isEmpty()) { - int numParams = Config::numParams(def); - int numOccs = def.count("\1"); - if (numParams != 1) { - config.lastLocation().warning(QStringLiteral("Formatting '%1' must " - "have exactly one " - "parameter (found %2)") - .arg(n) - .arg(numParams)); - } else if (numOccs > 1) { - config.lastLocation().fatal(QStringLiteral("Formatting '%1' must " - "contain exactly one " - "occurrence of '\\1' " - "(found %2)") - .arg(n) - .arg(numOccs)); - } else { - int paramPos = def.indexOf("\1"); - fmtLeftMaps[f].insert(n, def.left(paramPos)); - fmtRightMaps[f].insert(n, def.mid(paramPos + 1)); - } - } - } - } - - project_ = config.getString(CONFIG_PROJECT); - outDir_ = config.getOutputDir(); - outSubdir_ = outDir_.mid(outDir_.lastIndexOf('/') + 1); - - outputPrefixes.clear(); - QStringList items = config.getStringList(CONFIG_OUTPUTPREFIXES); - if (!items.isEmpty()) { - for (const auto &prefix : items) - outputPrefixes[prefix] = config.getString(CONFIG_OUTPUTPREFIXES + Config::dot + prefix); - } else { - outputPrefixes[QLatin1String("QML")] = QLatin1String("qml-"); - outputPrefixes[QLatin1String("JS")] = QLatin1String("js-"); - } - - outputSuffixes.clear(); - for (const auto &suffix : config.getStringList(CONFIG_OUTPUTSUFFIXES)) - outputSuffixes[suffix] = config.getString(CONFIG_OUTPUTSUFFIXES + Config::dot + suffix); - - noLinkErrors_ = config.getBool(CONFIG_NOLINKERRORS); - autolinkErrors_ = config.getBool(CONFIG_AUTOLINKERRORS); -} - -/*! - Creates template-specific subdirs (e.g. /styles and /scripts for HTML) - and copies the files to them. - */ -void Generator::copyTemplateFiles(const QString &configVar, const QString &subDir) -{ - Config &config = Config::instance(); - QStringList files = config.getCanonicalPathList(configVar, true); - if (!files.isEmpty()) { - QDir dirInfo; - QString templateDir = outDir_ + QLatin1Char('/') + subDir; - if (!dirInfo.exists(templateDir) && !dirInfo.mkdir(templateDir)) { - config.lastLocation().fatal( - QStringLiteral("Cannot create %1 directory '%2'").arg(subDir, templateDir)); - } else { - for (const auto &file : files) { - if (!file.isEmpty()) - Config::copyFile(config.lastLocation(), file, file, templateDir); - } - } - } -} - -/*! - Reads format-specific variables from config, sets output - (sub)directories, creates them on the filesystem and copies the - template-specific files. - */ -void Generator::initializeFormat() -{ - Config &config = Config::instance(); - outFileNames_.clear(); - useOutputSubdirs_ = true; - if (config.getBool(format() + Config::dot + "nosubdirs")) - resetUseOutputSubdirs(); - - if (outputFormats.isEmpty()) - return; - - outDir_ = config.getOutputDir(format()); - if (outDir_.isEmpty()) { - config.lastLocation().fatal(QStringLiteral("No output directory specified in " - "configuration file or on the command line")); - } else { - outSubdir_ = outDir_.mid(outDir_.lastIndexOf('/') + 1); - } - - QDir dirInfo; - if (dirInfo.exists(outDir_)) { - if (!config.generating() && Generator::useOutputSubdirs()) { - if (!Config::removeDirContents(outDir_)) - config.lastLocation().error( - QStringLiteral("Cannot empty output directory '%1'").arg(outDir_)); - } - } else if (!dirInfo.mkpath(outDir_)) { - config.lastLocation().fatal( - QStringLiteral("Cannot create output directory '%1'").arg(outDir_)); - } - - // Output directory exists, which is enough for prepare phase. - if (config.preparing()) - return; - - if (!dirInfo.exists(outDir_ + "/images") && !dirInfo.mkdir(outDir_ + "/images")) - config.lastLocation().fatal( - QStringLiteral("Cannot create images directory '%1'").arg(outDir_ + "/images")); - - copyTemplateFiles(format() + Config::dot + CONFIG_STYLESHEETS, "style"); - copyTemplateFiles(format() + Config::dot + CONFIG_SCRIPTS, "scripts"); - copyTemplateFiles(format() + Config::dot + CONFIG_EXTRAIMAGES, "images"); - - // Use a format-specific .quotinginformation if defined, otherwise a global value - if (config.subVars(format()).contains(CONFIG_QUOTINGINFORMATION)) - m_quoting = config.getBool(format() + Config::dot + CONFIG_QUOTINGINFORMATION); - else - m_quoting = config.getBool(CONFIG_QUOTINGINFORMATION); -} - -/*! - Appends each directory path in \a moreImageDirs to the - list of image directories. - */ -void Generator::augmentImageDirs(QSet<QString> &moreImageDirs) -{ - if (moreImageDirs.isEmpty()) - return; - for (const auto &it : moreImageDirs) - imageDirs.append(it); -} - -/*! - Updates the generator's m_showInternal from the Config. - */ -void Generator::initializeGenerator() -{ - m_showInternal = Config::instance().showInternal(); -} - -bool Generator::matchAhead(const Atom *atom, Atom::AtomType expectedAtomType) -{ - return atom->next() && atom->next()->type() == expectedAtomType; -} - -/*! - Used for writing to the current output stream. Returns a - reference to the current output stream, which is then used - with the \c {<<} operator for writing. - */ -QTextStream &Generator::out() -{ - return *outStreamStack.top(); -} - -QString Generator::outFileName() -{ - return QFileInfo(static_cast<QFile *>(out().device())->fileName()).fileName(); -} - -QString Generator::outputPrefix(const Node *node) -{ - // Prefix is applied to QML and JS types - if (node->isQmlType() || node->isQmlBasicType()) - return outputPrefixes[QLatin1String("QML")]; - if (node->isJsType() || node->isJsBasicType()) - return outputPrefixes[QLatin1String("JS")]; - return QString(); -} - -QString Generator::outputSuffix(const Node *node) -{ - // Suffix is applied to QML and JS types, as - // well as module pages. - if (node->isQmlModule() || node->isQmlType() || node->isQmlBasicType()) - return outputSuffixes[QLatin1String("QML")]; - if (node->isJsModule() || node->isJsType() || node->isJsBasicType()) - return outputSuffixes[QLatin1String("JS")]; - return QString(); -} - -bool Generator::parseArg(const QString &src, const QString &tag, int *pos, int n, - QStringView *contents, QStringView *par1, bool debug) -{ -#define SKIP_CHAR(c) \ - if (debug) \ - qDebug() << "looking for " << c << " at " << QString(src.data() + i, n - i); \ - if (i >= n || src[i] != c) { \ - if (debug) \ - qDebug() << " char '" << c << "' not found"; \ - return false; \ - } \ - ++i; - -#define SKIP_SPACE \ - while (i < n && src[i] == ' ') \ - ++i; - - int i = *pos; - int j = i; - - // assume "<@" has been parsed outside - // SKIP_CHAR('<'); - // SKIP_CHAR('@'); - - if (tag != QStringView(src).mid(i, tag.length())) { - return false; - } - - if (debug) - qDebug() << "haystack:" << src << "needle:" << tag << "i:" << i; - - // skip tag - i += tag.length(); - - // parse stuff like: linkTag("(<@link node=\"([^\"]+)\">).*(</@link>)"); - if (par1) { - SKIP_SPACE; - // read parameter name - j = i; - while (i < n && src[i].isLetter()) - ++i; - if (src[i] == '=') { - if (debug) - qDebug() << "read parameter" << QString(src.data() + j, i - j); - SKIP_CHAR('='); - SKIP_CHAR('"'); - // skip parameter name - j = i; - while (i < n && src[i] != '"') - ++i; - *par1 = QStringView(src).mid(j, i - j); - SKIP_CHAR('"'); - SKIP_SPACE; - } else { - if (debug) - qDebug() << "no optional parameter found"; - } - } - SKIP_SPACE; - SKIP_CHAR('>'); - - // find contents up to closing "</@tag> - j = i; - for (; true; ++i) { - if (i + 4 + tag.length() > n) - return false; - if (src[i] != '<') - continue; - if (src[i + 1] != '/') - continue; - if (src[i + 2] != '@') - continue; - if (tag != QStringView(src).mid(i + 3, tag.length())) - continue; - if (src[i + 3 + tag.length()] != '>') - continue; - break; - } - - *contents = QStringView(src).mid(j, i - j); - - i += tag.length() + 4; - - *pos = i; - if (debug) - qDebug() << " tag " << tag << " found: pos now: " << i; - return true; -#undef SKIP_CHAR -} - -QString Generator::plainCode(const QString &markedCode) -{ - QString t = markedCode; - t.replace(tag, QString()); - t.replace(quot, QLatin1String("\"")); - t.replace(gt, QLatin1String(">")); - t.replace(lt, QLatin1String("<")); - t.replace(amp, QLatin1String("&")); - return t; -} - -void Generator::setImageFileExtensions(const QStringList &extensions) -{ - imgFileExts[format()] = extensions; -} - -void Generator::singularPlural(Text &text, const NodeList &nodes) -{ - if (nodes.count() == 1) - text << " is"; - else - text << " are"; -} - -int Generator::skipAtoms(const Atom *atom, Atom::AtomType type) const -{ - int skipAhead = 0; - atom = atom->next(); - while (atom && atom->type() != type) { - skipAhead++; - atom = atom->next(); - } - return skipAhead; -} - -/*! - Resets the variables used during text output. - */ -void Generator::initializeTextOutput() -{ - m_inLink = false; - m_inContents = false; - m_inSectionHeading = false; - m_inTableHeader = false; - m_numTableRows = 0; - m_threeColumnEnumValueTable = true; - m_link.clear(); - m_sectionNumber.clear(); -} - -void Generator::supplementAlsoList(const Node *node, QList<Text> &alsoList) -{ - if (node->isFunction() && !node->isMacro()) { - const auto fn = static_cast<const FunctionNode *>(node); - if (fn->overloadNumber() == 0) { - QString alternateName; - const FunctionNode *alternateFunc = nullptr; - - if (fn->name().startsWith("set") && fn->name().size() >= 4) { - alternateName = fn->name()[3].toLower(); - alternateName += fn->name().mid(4); - alternateFunc = fn->parent()->findFunctionChild(alternateName, QString()); - - if (!alternateFunc) { - alternateName = "is" + fn->name().mid(3); - alternateFunc = fn->parent()->findFunctionChild(alternateName, QString()); - if (!alternateFunc) { - alternateName = "has" + fn->name().mid(3); - alternateFunc = fn->parent()->findFunctionChild(alternateName, QString()); - } - } - } else if (!fn->name().isEmpty()) { - alternateName = "set"; - alternateName += fn->name()[0].toUpper(); - alternateName += fn->name().mid(1); - alternateFunc = fn->parent()->findFunctionChild(alternateName, QString()); - } - - if (alternateFunc && alternateFunc->access() != Access::Private) { - int i; - for (i = 0; i < alsoList.size(); ++i) { - if (alsoList.at(i).toString().contains(alternateName)) - break; - } - - if (i == alsoList.size()) { - alternateName += "()"; - - Text also; - also << Atom(Atom::Link, alternateName) - << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << alternateName - << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK); - alsoList.prepend(also); - } - } - } - } -} - -void Generator::terminate() -{ - for (const auto &generator : qAsConst(generators)) { - if (outputFormats.contains(generator->format())) - generator->terminateGenerator(); - } - - fmtLeftMaps.clear(); - fmtRightMaps.clear(); - imgFileExts.clear(); - imageFiles.clear(); - imageDirs.clear(); - outDir_.clear(); -} - -void Generator::terminateGenerator() {} - -/*! - Trims trailing whitespace off the \a string and returns - the trimmed string. - */ -QString Generator::trimmedTrailing(const QString &string, const QString &prefix, - const QString &suffix) -{ - QString trimmed = string; - while (trimmed.length() > 0 && trimmed[trimmed.length() - 1].isSpace()) - trimmed.truncate(trimmed.length() - 1); - - trimmed.append(suffix); - trimmed.prepend(prefix); - return trimmed; -} - -QString Generator::typeString(const Node *node) -{ - switch (node->nodeType()) { - case Node::Namespace: - return "namespace"; - case Node::Class: - return "class"; - case Node::Struct: - return "struct"; - case Node::Union: - return "union"; - case Node::QmlType: - case Node::QmlBasicType: - case Node::JsBasicType: - return "type"; - case Node::Page: - return "documentation"; - case Node::Enum: - return "enum"; - case Node::Typedef: - return "typedef"; - case Node::TypeAlias: - return "alias"; - case Node::Function: { - const auto fn = static_cast<const FunctionNode *>(node); - switch (fn->metaness()) { - case FunctionNode::JsSignal: - case FunctionNode::QmlSignal: - return "signal"; - case FunctionNode::JsSignalHandler: - case FunctionNode::QmlSignalHandler: - return "signal handler"; - case FunctionNode::JsMethod: - case FunctionNode::QmlMethod: - return "method"; - default: - break; - } - return "function"; - } - case Node::Property: - case Node::QmlProperty: - return "property"; - case Node::Module: - case Node::JsModule: - case Node::QmlModule: - return "module"; - case Node::SharedComment: { - const auto &collective = static_cast<const SharedCommentNode *>(node)->collective(); - return collective.first()->nodeTypeString(); - } - default: - return "documentation"; - } -} - -void Generator::unknownAtom(const Atom *atom) -{ - Location::internalError(QStringLiteral("unknown atom type '%1' in %2 generator") - .arg(atom->typeString()) - .arg(format())); -} - -QT_END_NAMESPACE |